feat(smart-app): implement complete mobile app MVP

- App.tsx: full navigation (Auth stack + Main tabs with 5 screens)
- Auth: LoginScreen, RegisterScreen, ForgotPasswordScreen
- HomeScreen: dashboard with IoT metrics, weather widget, alerts, quick actions, sensors
- MapScreen: interactive map with layer toggles (6 layers)
- MarketplaceScreen: categories (6), products (5), search
- ChatScreen: AI chat with quick prompts (4), bot responses
- ProfileScreen: user info, stats, menu (9 items), logout
- AlertsScreen: alert list with severity, acknowledge
- SensorsScreen: sensor list with type filters (6 types), search
- ZonesScreen: zone cards with stats
- SettingsScreen: language picker (FR/EN/ES/DE), privacy, about
- Stores: iotStore (sensors, zones, alerts), notificationStore, uiStore + i18n
- Hooks: useSensors, useAlerts, useNotifications, useLocation
- Components: Card, Button, LoadingSpinner, ErrorBoundary, Header
- Services: iotService, notificationService (with axios API client)
- Utils: formatters (temp, AQI, noise, dates), validators (email, password, IBAN)
- Theme: colors.ts with full design system (Blue Ocean palette)
- Ditto: fixed MongoDB connection, new JWT secrets, official gateway image
This commit is contained in:
Eric FELIXINE
2026-06-01 18:00:35 -04:00
parent 08ca495bde
commit e30ae8ed09
35578 changed files with 3703534 additions and 43 deletions

View File

@@ -0,0 +1,6 @@
---
Checks: '>
clang-diagnostic-*,
'
InheritParentConfig: true
...

View File

@@ -0,0 +1,40 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
add_compile_options(
-fexceptions
-frtti
-std=c++20
-Wall
-Wpedantic
-DLOG_TAG=\"ReactNative\")
file(GLOB react_nativemodule_core_SRC CONFIGURE_DEPENDS
ReactCommon/*.cpp
platform/android/ReactCommon/*.cpp)
add_library(react_nativemodule_core
SHARED
${react_nativemodule_core_SRC})
target_include_directories(react_nativemodule_core
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/platform/android/
)
target_link_libraries(react_nativemodule_core
fbjni
folly_runtime
glog
jsi
react_bridging
react_debug
react_utils
reactperflogger
reactnativejni)

View File

@@ -0,0 +1,11 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
// This header is left here for compatibility reasons.
#include <react/bridging/CallbackWrapper.h>

View File

@@ -0,0 +1,32 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "CxxTurboModuleUtils.h"
namespace facebook::react {
std::unordered_map<
std::string,
std::function<
std::shared_ptr<TurboModule>(std::shared_ptr<CallInvoker> jsInvoker)>>&
globalExportedCxxTurboModuleMap() {
static std::unordered_map<
std::string,
std::function<std::shared_ptr<TurboModule>(
std::shared_ptr<CallInvoker> jsInvoker)>>
map;
return map;
}
void registerCxxModuleToGlobalModuleMap(
std::string name,
std::function<std::shared_ptr<TurboModule>(
std::shared_ptr<CallInvoker> jsInvoker)> moduleProviderFunc) {
globalExportedCxxTurboModuleMap()[name] = moduleProviderFunc;
}
} // namespace facebook::react

View File

@@ -0,0 +1,67 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <string>
#include <unordered_map>
#include <ReactCommon/CallInvoker.h>
#include <ReactCommon/TurboModule.h>
namespace facebook::react {
std::unordered_map<
std::string,
std::function<
std::shared_ptr<TurboModule>(std::shared_ptr<CallInvoker> jsInvoker)>>&
globalExportedCxxTurboModuleMap();
void registerCxxModuleToGlobalModuleMap(
std::string name,
std::function<std::shared_ptr<TurboModule>(
std::shared_ptr<CallInvoker> jsInvoker)> moduleProviderFunc);
} // namespace facebook::react
/*
* You can use this macro to register your C++ TurboModule in your .cpp
* implementation if you do not have access to getTurboModule:jsInvoker:
* callback. This will register the module before main() is called,
* so it will incur a startup cost.
*
* RCT_EXPORT_CXX_MODULE_EXPERIMENTAL(ModuleExample) becomes:
*
* #pragma clang diagnostic push
* #pragma clang diagnostic ignored "-Wglobal-constructors"
* struct ModuleExampleLoad {
* ModuleExampleLoad() {
* facebook::react::registerCxxModule(name,
* [&](std::shared_ptr<facebook::react::CallInvoker> jsInvoker) {
* return
* std::make_shared<facebook::react::ModuleExample>(jsInvoker);
* });
* }
* };
* static ModuleExampleLoad moduleExampleLoad;
* #pragma clang diagnostic pop
*
*/
#define RCT_EXPORT_CXX_MODULE_EXPERIMENTAL(name) \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wglobal-constructors\"") struct \
name##Load { \
name##Load() { \
facebook::react::registerCxxModuleToGlobalModuleMap( \
#name, \
[&](std::shared_ptr<facebook::react::CallInvoker> jsInvoker) { \
return std::make_shared<facebook::react::name>(jsInvoker); \
}); \
} \
}; \
static name##Load _##name##Load; \
_Pragma("clang diagnostic pop")

View File

@@ -0,0 +1,11 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
// This header is left here for compatibility reasons.
#include <react/bridging/LongLivedObject.h>

View File

@@ -0,0 +1,228 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "TurboCxxModule.h"
#include <vector>
#include <ReactCommon/TurboModuleUtils.h>
#include <glog/logging.h>
#include <jsi/JSIDynamic.h>
using namespace facebook;
using namespace facebook::xplat::module;
namespace facebook::react {
namespace {
CxxModule::Callback makeTurboCxxModuleCallback(
jsi::Runtime& runtime,
std::weak_ptr<CallbackWrapper> weakWrapper) {
return [weakWrapper,
wrapperWasCalled = false](std::vector<folly::dynamic> args) mutable {
if (wrapperWasCalled) {
LOG(FATAL) << "callback arg cannot be called more than once";
}
auto strongWrapper = weakWrapper.lock();
if (!strongWrapper) {
return;
}
strongWrapper->jsInvoker().invokeAsync([weakWrapper, args]() {
auto strongWrapper2 = weakWrapper.lock();
if (!strongWrapper2) {
return;
}
std::vector<jsi::Value> innerArgs;
for (auto& a : args) {
innerArgs.push_back(
jsi::valueFromDynamic(strongWrapper2->runtime(), a));
}
strongWrapper2->callback().call(
strongWrapper2->runtime(),
(const jsi::Value*)innerArgs.data(),
innerArgs.size());
strongWrapper2->destroy();
});
wrapperWasCalled = true;
};
}
} // namespace
TurboCxxModule::TurboCxxModule(
std::unique_ptr<CxxModule> cxxModule,
std::shared_ptr<CallInvoker> jsInvoker)
: TurboModule(cxxModule->getName(), std::move(jsInvoker)),
cxxMethods_(cxxModule->getMethods()),
cxxModule_(std::move(cxxModule)) {}
jsi::Value TurboCxxModule::get(
jsi::Runtime& runtime,
const jsi::PropNameID& propName) {
std::string propNameUtf8 = propName.utf8(runtime);
auto result = jsi::Value::undefined();
if (propNameUtf8 == "getConstants") {
// This is special cased because `getConstants()` is already a part of
// CxxModule.
result = jsi::Function::createFromHostFunction(
runtime,
propName,
0,
[this](
jsi::Runtime& rt,
const jsi::Value& thisVal,
const jsi::Value* args,
size_t count) {
jsi::Object result(rt);
auto constants = cxxModule_->getConstants();
for (auto& pair : constants) {
result.setProperty(
rt, pair.first.c_str(), jsi::valueFromDynamic(rt, pair.second));
}
return result;
});
} else {
for (auto& method : cxxMethods_) {
if (method.name == propNameUtf8) {
result = jsi::Function::createFromHostFunction(
runtime,
propName,
0,
[this, propNameUtf8](
jsi::Runtime& rt,
const jsi::Value& thisVal,
const jsi::Value* args,
size_t count) {
return invokeMethod(rt, propNameUtf8, args, count);
});
}
}
}
// If we have a JS wrapper, cache the result of this lookup
if (jsRepresentation_) {
auto jsRepresentation = jsRepresentation_->lock(runtime);
if (!jsRepresentation.isUndefined()) {
std::move(jsRepresentation)
.asObject(runtime)
.setProperty(runtime, propName, result);
}
}
return result;
}
std::vector<jsi::PropNameID> TurboCxxModule::getPropertyNames(
jsi::Runtime& runtime) {
std::vector<jsi::PropNameID> result;
result.reserve(cxxMethods_.size() + 1);
result.push_back(jsi::PropNameID::forUtf8(runtime, "getConstants"));
for (auto it = cxxMethods_.begin(); it != cxxMethods_.end(); it++) {
result.push_back(jsi::PropNameID::forUtf8(runtime, it->name));
}
return result;
}
jsi::Value TurboCxxModule::invokeMethod(
jsi::Runtime& runtime,
const std::string& methodName,
const jsi::Value* args,
size_t count) {
auto it = cxxMethods_.begin();
for (; it != cxxMethods_.end(); it++) {
auto method = *it;
if (method.name == methodName) {
break;
}
}
if (it == cxxMethods_.end()) {
throw std::runtime_error(
"Function '" + methodName + "' cannot be found on cxxmodule: " + name_);
}
auto method = *it;
if (method.syncFunc) {
auto innerArgs = folly::dynamic::array();
for (size_t i = 0; i < count; i++) {
innerArgs.push_back(jsi::dynamicFromValue(runtime, args[i]));
}
return jsi::valueFromDynamic(
runtime, method.syncFunc(std::move(innerArgs)));
} else if (method.func && !method.isPromise) {
// Async method.
CxxModule::Callback first;
CxxModule::Callback second;
if (count < method.callbacks) {
throw std::invalid_argument(folly::to<std::string>(
"Expected ",
method.callbacks,
" callbacks, but only ",
count,
" parameters provided"));
}
if (method.callbacks == 1) {
auto wrapper = CallbackWrapper::createWeak(
args[count - 1].getObject(runtime).getFunction(runtime),
runtime,
jsInvoker_);
first = makeTurboCxxModuleCallback(runtime, wrapper);
} else if (method.callbacks == 2) {
auto wrapper1 = CallbackWrapper::createWeak(
args[count - 2].getObject(runtime).getFunction(runtime),
runtime,
jsInvoker_);
auto wrapper2 = CallbackWrapper::createWeak(
args[count - 1].getObject(runtime).getFunction(runtime),
runtime,
jsInvoker_);
first = makeTurboCxxModuleCallback(runtime, wrapper1);
second = makeTurboCxxModuleCallback(runtime, wrapper2);
}
auto innerArgs = folly::dynamic::array();
for (size_t i = 0; i < count - method.callbacks; i++) {
innerArgs.push_back(jsi::dynamicFromValue(runtime, args[i]));
}
method.func(std::move(innerArgs), first, second);
} else if (method.isPromise) {
return createPromiseAsJSIValue(
runtime,
[method, args, count, this](
jsi::Runtime& rt, std::shared_ptr<Promise> promise) {
auto resolveWrapper = CallbackWrapper::createWeak(
promise->resolve_.getFunction(rt), rt, jsInvoker_);
auto rejectWrapper = CallbackWrapper::createWeak(
promise->reject_.getFunction(rt), rt, jsInvoker_);
CxxModule::Callback resolve =
makeTurboCxxModuleCallback(rt, resolveWrapper);
CxxModule::Callback reject =
makeTurboCxxModuleCallback(rt, rejectWrapper);
auto innerArgs = folly::dynamic::array();
for (size_t i = 0; i < count; i++) {
innerArgs.push_back(jsi::dynamicFromValue(rt, args[i]));
}
method.func(std::move(innerArgs), resolve, reject);
});
}
return jsi::Value::undefined();
}
} // namespace facebook::react

View File

@@ -0,0 +1,49 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <memory>
#include <vector>
#include <cxxreact/CxxModule.h>
#include "TurboModule.h"
namespace facebook::react {
/**
* A helper class to convert the legacy CxxModule instance to a TurboModule
* instance. This should be used only for migration purpose (to TurboModule),
* since it's not very performant due to a lot of back-and-forth value
* conversions between folly::dynamic and jsi::Value.
*/
class JSI_EXPORT TurboCxxModule : public TurboModule {
public:
TurboCxxModule(
std::unique_ptr<facebook::xplat::module::CxxModule> cxxModule,
std::shared_ptr<CallInvoker> jsInvoker);
facebook::jsi::Value get(
facebook::jsi::Runtime& runtime,
const facebook::jsi::PropNameID& propName) override;
std::vector<facebook::jsi::PropNameID> getPropertyNames(
facebook::jsi::Runtime& runtime) override;
jsi::Value invokeMethod(
jsi::Runtime& runtime,
const std::string& methodName,
const jsi::Value* args,
size_t count);
private:
std::vector<facebook::xplat::module::CxxModule::Method> cxxMethods_;
std::unique_ptr<facebook::xplat::module::CxxModule> cxxModule_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,66 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "TurboModule.h"
#include <react/debug/react_native_assert.h>
namespace facebook::react {
TurboModuleMethodValueKind getTurboModuleMethodValueKind(
jsi::Runtime& rt,
const jsi::Value* value) {
if (!value || value->isUndefined() || value->isNull()) {
return VoidKind;
} else if (value->isBool()) {
return BooleanKind;
} else if (value->isNumber()) {
return NumberKind;
} else if (value->isString()) {
return StringKind;
} else if (value->isObject()) {
auto object = value->asObject(rt);
if (object.isArray(rt)) {
return ArrayKind;
} else if (object.isFunction(rt)) {
return FunctionKind;
}
return ObjectKind;
}
react_native_assert(false && "Unsupported jsi::Value kind");
return VoidKind;
}
TurboModule::TurboModule(
std::string name,
std::shared_ptr<CallInvoker> jsInvoker)
: name_(std::move(name)), jsInvoker_(std::move(jsInvoker)) {}
void TurboModule::emitDeviceEvent(
jsi::Runtime& runtime,
const std::string& eventName,
ArgFactory argFactory) {
jsInvoker_->invokeAsync([&runtime, eventName, argFactory]() {
jsi::Value emitter =
runtime.global().getProperty(runtime, "__rctDeviceEventEmitter");
if (!emitter.isUndefined()) {
jsi::Object emitterObject = emitter.asObject(runtime);
// TODO: consider caching these
jsi::Function emitFunction =
emitterObject.getPropertyAsFunction(runtime, "emit");
std::vector<jsi::Value> args;
args.emplace_back(
jsi::String::createFromAscii(runtime, eventName.c_str()));
if (argFactory) {
argFactory(runtime, args);
}
emitFunction.callWithThis(
runtime, emitterObject, (const jsi::Value*)args.data(), args.size());
}
});
}
} // namespace facebook::react

View File

@@ -0,0 +1,148 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <string>
#include <unordered_map>
#include <jsi/jsi.h>
#include <ReactCommon/CallInvoker.h>
namespace facebook::react {
/**
* For now, support the same set of return types as existing impl.
* This can be improved to support richer typed objects.
*/
enum TurboModuleMethodValueKind {
VoidKind,
BooleanKind,
NumberKind,
StringKind,
ObjectKind,
ArrayKind,
FunctionKind,
PromiseKind,
};
/**
* Determines TurboModuleMethodValueKind based on the jsi::Value type.
*/
TurboModuleMethodValueKind getTurboModuleMethodValueKind(
jsi::Runtime& rt,
const jsi::Value* value);
class TurboCxxModule;
class TurboModuleBinding;
/**
* Base HostObject class for every module to be exposed to JS
*/
class JSI_EXPORT TurboModule : public facebook::jsi::HostObject {
public:
TurboModule(std::string name, std::shared_ptr<CallInvoker> jsInvoker);
// Note: keep this method declared inline to avoid conflicts
// between RTTI and non-RTTI compilation units
facebook::jsi::Value get(
facebook::jsi::Runtime& runtime,
const facebook::jsi::PropNameID& propName) override {
{
auto prop = create(runtime, propName);
// If we have a JS wrapper, cache the result of this lookup
// We don't cache misses, to allow for methodMap_ to dynamically be
// extended
if (jsRepresentation_ && !prop.isUndefined()) {
jsRepresentation_->lock(runtime).asObject(runtime).setProperty(
runtime, propName, prop);
}
return prop;
}
}
virtual std::vector<facebook::jsi::PropNameID> getPropertyNames(
facebook::jsi::Runtime& runtime) override {
std::vector<jsi::PropNameID> result;
result.reserve(methodMap_.size());
for (auto it = methodMap_.cbegin(); it != methodMap_.cend(); ++it) {
result.push_back(jsi::PropNameID::forUtf8(runtime, it->first));
}
return result;
}
protected:
const std::string name_;
std::shared_ptr<CallInvoker> jsInvoker_;
struct MethodMetadata {
size_t argCount;
facebook::jsi::Value (*invoker)(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count);
};
std::unordered_map<std::string, MethodMetadata> methodMap_;
using ArgFactory =
std::function<void(jsi::Runtime& runtime, std::vector<jsi::Value>& args)>;
/**
* Calls RCTDeviceEventEmitter.emit to JavaScript, with given event name and
* an optional list of arguments.
* If present, argFactory is a callback used to construct extra arguments,
* e.g.
*
* emitDeviceEvent(rt, "myCustomEvent",
* [](jsi::Runtime& rt, std::vector<jsi::Value>& args) {
* args.emplace_back(jsi::Value(true));
* args.emplace_back(jsi::Value(42));
* });
*/
void emitDeviceEvent(
jsi::Runtime& runtime,
const std::string& eventName,
ArgFactory argFactory = nullptr);
virtual jsi::Value create(
jsi::Runtime& runtime,
const jsi::PropNameID& propName) {
std::string propNameUtf8 = propName.utf8(runtime);
auto p = methodMap_.find(propNameUtf8);
if (p == methodMap_.end()) {
// Method was not found, let JS decide what to do.
return facebook::jsi::Value::undefined();
} else {
const MethodMetadata& meta = p->second;
return jsi::Function::createFromHostFunction(
runtime,
propName,
static_cast<unsigned int>(meta.argCount),
[this, meta](
jsi::Runtime& rt,
[[maybe_unused]] const jsi::Value& thisVal,
const jsi::Value* args,
size_t count) { return meta.invoker(rt, *this, args, count); });
}
}
private:
friend class TurboCxxModule;
friend class TurboModuleBinding;
std::unique_ptr<jsi::WeakObject> jsRepresentation_;
};
/**
* An app/platform-specific provider function to get an instance of a module
* given a name.
*/
using TurboModuleProviderFunctionType =
std::function<std::shared_ptr<TurboModule>(const std::string& name)>;
} // namespace facebook::react

View File

@@ -0,0 +1,176 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "TurboModuleBinding.h"
#include <cxxreact/SystraceSection.h>
#include <react/utils/jsi.h>
#include <stdexcept>
#include <string>
using namespace facebook;
namespace facebook::react {
class BridgelessNativeModuleProxy : public jsi::HostObject {
std::unique_ptr<TurboModuleBinding> binding_;
public:
BridgelessNativeModuleProxy(std::unique_ptr<TurboModuleBinding> binding)
: binding_(std::move(binding)) {}
jsi::Value get(jsi::Runtime& runtime, const jsi::PropNameID& name) override {
/**
* BatchedBridge/NativeModules.js contains this line:
*
* module.exports = global.nativeModuleProxy
*
* This means that NativeModuleProxy is exported as a module from
* 'NativeModules.js'. Whenever some JavaScript requires 'NativeModule.js',
* Metro checks this module's __esModule property to see if the module is an
* ES6 module.
*
* We return false from this property access, so that we can fail on the
* actual NativeModule require that happens later, which is more actionable.
*/
std::string moduleName = name.utf8(runtime);
if (moduleName == "__esModule") {
return jsi::Value(false);
}
if (binding_) {
return binding_->getModule(runtime, moduleName);
}
throw jsi::JSError(
runtime,
"Tried to access NativeModule \"" + name.utf8(runtime) +
"\" from the bridge. This isn't allowed in Bridgeless mode.");
}
void set(
jsi::Runtime& runtime,
const jsi::PropNameID& /*name*/,
const jsi::Value& /*value*/) override {
throw jsi::JSError(
runtime,
"Tried to insert a NativeModule into the bridge's NativeModule proxy.");
}
};
/**
* Public API to install the TurboModule system.
*/
TurboModuleBinding::TurboModuleBinding(
TurboModuleProviderFunctionType&& moduleProvider,
std::shared_ptr<LongLivedObjectCollection> longLivedObjectCollection)
: moduleProvider_(std::move(moduleProvider)),
longLivedObjectCollection_(std::move(longLivedObjectCollection)) {}
void TurboModuleBinding::install(
jsi::Runtime& runtime,
TurboModuleProviderFunctionType&& moduleProvider,
TurboModuleProviderFunctionType&& legacyModuleProvider,
std::shared_ptr<LongLivedObjectCollection> longLivedObjectCollection) {
runtime.global().setProperty(
runtime,
"__turboModuleProxy",
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "__turboModuleProxy"),
1,
[binding = TurboModuleBinding(
std::move(moduleProvider), longLivedObjectCollection)](
jsi::Runtime& rt,
const jsi::Value& thisVal,
const jsi::Value* args,
size_t count) {
if (count < 1) {
throw std::invalid_argument(
"__turboModuleProxy must be called with at least 1 argument");
}
std::string moduleName = args[0].getString(rt).utf8(rt);
return binding.getModule(rt, moduleName);
}));
if (runtime.global().hasProperty(runtime, "RN$Bridgeless")) {
bool rnTurboInterop = legacyModuleProvider != nullptr;
auto turboModuleBinding = legacyModuleProvider
? std::make_unique<TurboModuleBinding>(
std::move(legacyModuleProvider), longLivedObjectCollection)
: nullptr;
auto nativeModuleProxy = std::make_shared<BridgelessNativeModuleProxy>(
std::move(turboModuleBinding));
defineReadOnlyGlobal(
runtime, "RN$TurboInterop", jsi::Value(rnTurboInterop));
defineReadOnlyGlobal(
runtime,
"nativeModuleProxy",
jsi::Object::createFromHostObject(runtime, nativeModuleProxy));
}
}
TurboModuleBinding::~TurboModuleBinding() {
if (longLivedObjectCollection_) {
longLivedObjectCollection_->clear();
} else {
LongLivedObjectCollection::get().clear();
}
}
jsi::Value TurboModuleBinding::getModule(
jsi::Runtime& runtime,
const std::string& moduleName) const {
std::shared_ptr<TurboModule> module;
{
SystraceSection s(
"TurboModuleBinding::moduleProvider", "module", moduleName);
module = moduleProvider_(moduleName);
}
if (module) {
// What is jsRepresentation? A cache for the TurboModule's properties
// Henceforth, always return the cache (i.e: jsRepresentation) to JavaScript
//
// If a jsRepresentation is found on the TurboModule, return it.
//
// Note: TurboModules are cached by name in TurboModuleManagers. Hence,
// jsRepresentation is also cached by by name by the TurboModuleManager
auto& weakJsRepresentation = module->jsRepresentation_;
if (weakJsRepresentation) {
auto jsRepresentation = weakJsRepresentation->lock(runtime);
if (!jsRepresentation.isUndefined()) {
return jsRepresentation;
}
}
// Status: No jsRepresentation found on TurboModule
// Create a brand new jsRepresentation, and attach it to TurboModule
jsi::Object jsRepresentation(runtime);
weakJsRepresentation =
std::make_unique<jsi::WeakObject>(runtime, jsRepresentation);
// Lazily populate the jsRepresentation, on property access.
//
// How does this work?
// 1. Initially jsRepresentation is empty: {}
// 2. If property lookup on jsRepresentation fails, the JS runtime will
// search jsRepresentation's prototype: jsi::Object(TurboModule).
// 3. TurboModule::get(runtime, propKey) executes. This creates the
// property, caches it on jsRepresentation, then returns it to
// JavaScript.
auto hostObject =
jsi::Object::createFromHostObject(runtime, std::move(module));
jsRepresentation.setProperty(runtime, "__proto__", std::move(hostObject));
return jsRepresentation;
} else {
return jsi::Value::null();
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,56 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <string>
#include <ReactCommon/LongLivedObject.h>
#include <ReactCommon/TurboModule.h>
#include <jsi/jsi.h>
namespace facebook::react {
class BridgelessNativeModuleProxy;
/**
* Represents the JavaScript binding for the TurboModule system.
*/
class TurboModuleBinding {
public:
/*
* Installs TurboModuleBinding into JavaScript runtime.
* Thread synchronization must be enforced externally.
*/
static void install(
jsi::Runtime& runtime,
TurboModuleProviderFunctionType&& moduleProvider,
TurboModuleProviderFunctionType&& legacyModuleProvider = nullptr,
std::shared_ptr<LongLivedObjectCollection> longLivedObjectCollection =
nullptr);
TurboModuleBinding(
TurboModuleProviderFunctionType&& moduleProvider,
std::shared_ptr<LongLivedObjectCollection> longLivedObjectCollection);
virtual ~TurboModuleBinding();
private:
friend BridgelessNativeModuleProxy;
/**
* A lookup function exposed to JS to get an instance of a TurboModule
* for the given name.
*/
jsi::Value getModule(jsi::Runtime& runtime, const std::string& moduleName)
const;
TurboModuleProviderFunctionType moduleProvider_;
std::shared_ptr<LongLivedObjectCollection> longLivedObjectCollection_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,329 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "TurboModulePerfLogger.h"
namespace facebook::react {
namespace TurboModulePerfLogger {
std::unique_ptr<NativeModulePerfLogger> g_perfLogger = nullptr;
void enableLogging(std::unique_ptr<NativeModulePerfLogger>&& newPerfLogger) {
g_perfLogger = std::move(newPerfLogger);
}
void disableLogging() {
g_perfLogger = nullptr;
}
void moduleDataCreateStart(const char* moduleName, int32_t id) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->moduleDataCreateStart(moduleName, id);
}
}
void moduleDataCreateEnd(const char* moduleName, int32_t id) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->moduleDataCreateEnd(moduleName, id);
}
}
void moduleCreateStart(const char* moduleName, int32_t id) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->moduleCreateStart(moduleName, id);
}
}
void moduleCreateCacheHit(const char* moduleName, int32_t id) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->moduleCreateCacheHit(moduleName, id);
}
}
void moduleCreateConstructStart(const char* moduleName, int32_t id) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->moduleCreateConstructStart(moduleName, id);
}
}
void moduleCreateConstructEnd(const char* moduleName, int32_t id) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->moduleCreateConstructEnd(moduleName, id);
}
}
void moduleCreateSetUpStart(const char* moduleName, int32_t id) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->moduleCreateSetUpStart(moduleName, id);
}
}
void moduleCreateSetUpEnd(const char* moduleName, int32_t id) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->moduleCreateSetUpEnd(moduleName, id);
}
}
void moduleCreateEnd(const char* moduleName, int32_t id) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->moduleCreateEnd(moduleName, id);
}
}
void moduleCreateFail(const char* moduleName, int32_t id) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->moduleCreateFail(moduleName, id);
}
}
void moduleJSRequireBeginningStart(const char* moduleName) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->moduleJSRequireBeginningStart(moduleName);
}
}
void moduleJSRequireBeginningCacheHit(const char* moduleName) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->moduleJSRequireBeginningCacheHit(moduleName);
}
}
void moduleJSRequireBeginningEnd(const char* moduleName) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->moduleJSRequireBeginningEnd(moduleName);
}
}
void moduleJSRequireBeginningFail(const char* moduleName) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->moduleJSRequireBeginningFail(moduleName);
}
}
void moduleJSRequireEndingStart(const char* moduleName) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->moduleJSRequireEndingStart(moduleName);
}
}
void moduleJSRequireEndingEnd(const char* moduleName) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->moduleJSRequireEndingEnd(moduleName);
}
}
void moduleJSRequireEndingFail(const char* moduleName) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->moduleJSRequireEndingFail(moduleName);
}
}
void syncMethodCallStart(const char* moduleName, const char* methodName) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->syncMethodCallStart(moduleName, methodName);
}
}
void syncMethodCallArgConversionStart(
const char* moduleName,
const char* methodName) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->syncMethodCallArgConversionStart(moduleName, methodName);
}
}
void syncMethodCallArgConversionEnd(
const char* moduleName,
const char* methodName) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->syncMethodCallArgConversionEnd(moduleName, methodName);
}
}
void syncMethodCallExecutionStart(
const char* moduleName,
const char* methodName) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->syncMethodCallExecutionStart(moduleName, methodName);
}
}
void syncMethodCallExecutionEnd(
const char* moduleName,
const char* methodName) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->syncMethodCallExecutionEnd(moduleName, methodName);
}
}
void syncMethodCallReturnConversionStart(
const char* moduleName,
const char* methodName) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->syncMethodCallReturnConversionStart(moduleName, methodName);
}
}
void syncMethodCallReturnConversionEnd(
const char* moduleName,
const char* methodName) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->syncMethodCallReturnConversionEnd(moduleName, methodName);
}
}
void syncMethodCallEnd(const char* moduleName, const char* methodName) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->syncMethodCallEnd(moduleName, methodName);
}
}
void syncMethodCallFail(const char* moduleName, const char* methodName) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->syncMethodCallFail(moduleName, methodName);
}
}
void asyncMethodCallStart(const char* moduleName, const char* methodName) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->asyncMethodCallStart(moduleName, methodName);
}
}
void asyncMethodCallArgConversionStart(
const char* moduleName,
const char* methodName) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->asyncMethodCallArgConversionStart(moduleName, methodName);
}
}
void asyncMethodCallArgConversionEnd(
const char* moduleName,
const char* methodName) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->asyncMethodCallArgConversionEnd(moduleName, methodName);
}
}
void asyncMethodCallDispatch(const char* moduleName, const char* methodName) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->asyncMethodCallDispatch(moduleName, methodName);
}
}
void asyncMethodCallEnd(const char* moduleName, const char* methodName) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->asyncMethodCallEnd(moduleName, methodName);
}
}
void asyncMethodCallFail(const char* moduleName, const char* methodName) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->asyncMethodCallFail(moduleName, methodName);
}
}
void asyncMethodCallBatchPreprocessStart() {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->asyncMethodCallBatchPreprocessStart();
}
}
void asyncMethodCallBatchPreprocessEnd(int batchSize) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->asyncMethodCallBatchPreprocessEnd(batchSize);
}
}
void asyncMethodCallExecutionStart(
const char* moduleName,
const char* methodName,
int32_t id) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->asyncMethodCallExecutionStart(moduleName, methodName, id);
}
}
void asyncMethodCallExecutionArgConversionStart(
const char* moduleName,
const char* methodName,
int32_t id) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->asyncMethodCallExecutionArgConversionStart(
moduleName, methodName, id);
}
}
void asyncMethodCallExecutionArgConversionEnd(
const char* moduleName,
const char* methodName,
int32_t id) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->asyncMethodCallExecutionArgConversionEnd(
moduleName, methodName, id);
}
}
void asyncMethodCallExecutionEnd(
const char* moduleName,
const char* methodName,
int32_t id) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->asyncMethodCallExecutionEnd(moduleName, methodName, id);
}
}
void asyncMethodCallExecutionFail(
const char* moduleName,
const char* methodName,
int32_t id) {
NativeModulePerfLogger* logger = g_perfLogger.get();
if (logger != nullptr) {
logger->asyncMethodCallExecutionFail(moduleName, methodName, id);
}
}
} // namespace TurboModulePerfLogger
} // namespace facebook::react

View File

@@ -0,0 +1,110 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <reactperflogger/NativeModulePerfLogger.h>
#include <memory>
namespace facebook::react {
namespace TurboModulePerfLogger {
void enableLogging(std::unique_ptr<NativeModulePerfLogger>&& logger);
void disableLogging();
void moduleDataCreateStart(const char* moduleName, int32_t id);
void moduleDataCreateEnd(const char* moduleName, int32_t id);
/**
* Create NativeModule platform object
*/
void moduleCreateStart(const char* moduleName, int32_t id);
void moduleCreateCacheHit(const char* moduleName, int32_t id);
void moduleCreateConstructStart(const char* moduleName, int32_t id);
void moduleCreateConstructEnd(const char* moduleName, int32_t id);
void moduleCreateSetUpStart(const char* moduleName, int32_t id);
void moduleCreateSetUpEnd(const char* moduleName, int32_t id);
void moduleCreateEnd(const char* moduleName, int32_t id);
void moduleCreateFail(const char* moduleName, int32_t id);
/**
* JS require beginning
*/
void moduleJSRequireBeginningStart(const char* moduleName);
void moduleJSRequireBeginningCacheHit(const char* moduleName);
void moduleJSRequireBeginningEnd(const char* moduleName);
void moduleJSRequireBeginningFail(const char* moduleName);
/**
* JS require ending
*/
void moduleJSRequireEndingStart(const char* moduleName);
void moduleJSRequireEndingEnd(const char* moduleName);
void moduleJSRequireEndingFail(const char* moduleName);
// Sync method calls
void syncMethodCallStart(const char* moduleName, const char* methodName);
void syncMethodCallArgConversionStart(
const char* moduleName,
const char* methodName);
void syncMethodCallArgConversionEnd(
const char* moduleName,
const char* methodName);
void syncMethodCallExecutionStart(
const char* moduleName,
const char* methodName);
void syncMethodCallExecutionEnd(const char* moduleName, const char* methodName);
void syncMethodCallReturnConversionStart(
const char* moduleName,
const char* methodName);
void syncMethodCallReturnConversionEnd(
const char* moduleName,
const char* methodName);
void syncMethodCallEnd(const char* moduleName, const char* methodName);
void syncMethodCallFail(const char* moduleName, const char* methodName);
// Async method calls
void asyncMethodCallStart(const char* moduleName, const char* methodName);
void asyncMethodCallArgConversionStart(
const char* moduleName,
const char* methodName);
void asyncMethodCallArgConversionEnd(
const char* moduleName,
const char* methodName);
void asyncMethodCallDispatch(const char* moduleName, const char* methodName);
void asyncMethodCallEnd(const char* moduleName, const char* methodName);
void asyncMethodCallFail(const char* moduleName, const char* methodName);
/**
* Pre-processing async method call batch
*/
void asyncMethodCallBatchPreprocessStart();
void asyncMethodCallBatchPreprocessEnd(int batchSize);
// Async method call execution
void asyncMethodCallExecutionStart(
const char* moduleName,
const char* methodName,
int32_t id);
void asyncMethodCallExecutionArgConversionStart(
const char* moduleName,
const char* methodName,
int32_t id);
void asyncMethodCallExecutionArgConversionEnd(
const char* moduleName,
const char* methodName,
int32_t id);
void asyncMethodCallExecutionEnd(
const char* moduleName,
const char* methodName,
int32_t id);
void asyncMethodCallExecutionFail(
const char* moduleName,
const char* methodName,
int32_t id);
} // namespace TurboModulePerfLogger
} // namespace facebook::react

View File

@@ -0,0 +1,103 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "TurboModuleUtils.h"
namespace facebook::react {
static jsi::Value deepCopyJSIValue(jsi::Runtime& rt, const jsi::Value& value) {
if (value.isNull()) {
return jsi::Value::null();
}
if (value.isBool()) {
return jsi::Value(value.getBool());
}
if (value.isNumber()) {
return jsi::Value(value.getNumber());
}
if (value.isString()) {
return value.getString(rt);
}
if (value.isObject()) {
jsi::Object o = value.getObject(rt);
if (o.isArray(rt)) {
return deepCopyJSIArray(rt, o.getArray(rt));
}
if (o.isFunction(rt)) {
return o.getFunction(rt);
}
return deepCopyJSIObject(rt, o);
}
return jsi::Value::undefined();
}
jsi::Object deepCopyJSIObject(jsi::Runtime& rt, const jsi::Object& obj) {
jsi::Object copy(rt);
jsi::Array propertyNames = obj.getPropertyNames(rt);
size_t size = propertyNames.size(rt);
for (size_t i = 0; i < size; i++) {
jsi::String name = propertyNames.getValueAtIndex(rt, i).getString(rt);
jsi::Value value = obj.getProperty(rt, name);
copy.setProperty(rt, name, deepCopyJSIValue(rt, value));
}
return copy;
}
jsi::Array deepCopyJSIArray(jsi::Runtime& rt, const jsi::Array& arr) {
size_t size = arr.size(rt);
jsi::Array copy(rt, size);
for (size_t i = 0; i < size; i++) {
copy.setValueAtIndex(
rt, i, deepCopyJSIValue(rt, arr.getValueAtIndex(rt, i)));
}
return copy;
}
Promise::Promise(jsi::Runtime& rt, jsi::Function resolve, jsi::Function reject)
: runtime_(rt), resolve_(std::move(resolve)), reject_(std::move(reject)) {}
void Promise::resolve(const jsi::Value& result) {
resolve_.call(runtime_, result);
}
void Promise::reject(const std::string& message) {
jsi::Object error(runtime_);
error.setProperty(
runtime_, "message", jsi::String::createFromUtf8(runtime_, message));
reject_.call(runtime_, error);
}
jsi::Value createPromiseAsJSIValue(
jsi::Runtime& rt,
PromiseSetupFunctionType&& func) {
jsi::Function JSPromise = rt.global().getPropertyAsFunction(rt, "Promise");
jsi::Function fn = jsi::Function::createFromHostFunction(
rt,
jsi::PropNameID::forAscii(rt, "fn"),
2,
[func = std::move(func)](
jsi::Runtime& rt2,
const jsi::Value& thisVal,
const jsi::Value* args,
size_t count) {
jsi::Function resolve = args[0].getObject(rt2).getFunction(rt2);
jsi::Function reject = args[1].getObject(rt2).getFunction(rt2);
auto wrapper = std::make_shared<Promise>(
rt2, std::move(resolve), std::move(reject));
func(rt2, wrapper);
return jsi::Value::undefined();
});
return JSPromise.callAsConstructor(rt, fn);
}
} // namespace facebook::react

View File

@@ -0,0 +1,59 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <cassert>
#include <string>
#include <jsi/jsi.h>
#include <ReactCommon/CallInvoker.h>
#include <ReactCommon/CallbackWrapper.h>
namespace facebook::react {
jsi::Object deepCopyJSIObject(jsi::Runtime& rt, const jsi::Object& obj);
jsi::Array deepCopyJSIArray(jsi::Runtime& rt, const jsi::Array& arr);
struct Promise : public LongLivedObject {
Promise(jsi::Runtime& rt, jsi::Function resolve, jsi::Function reject);
void resolve(const jsi::Value& result);
void reject(const std::string& error);
jsi::Runtime& runtime_;
jsi::Function resolve_;
jsi::Function reject_;
};
using PromiseSetupFunctionType =
std::function<void(jsi::Runtime& rt, std::shared_ptr<Promise>)>;
jsi::Value createPromiseAsJSIValue(
jsi::Runtime& rt,
PromiseSetupFunctionType&& func);
// Deprecated. Use AsyncCallback instead.
class RAIICallbackWrapperDestroyer {
public:
RAIICallbackWrapperDestroyer(std::weak_ptr<CallbackWrapper> callbackWrapper)
: callbackWrapper_(callbackWrapper) {}
~RAIICallbackWrapperDestroyer() {
auto strongWrapper = callbackWrapper_.lock();
if (!strongWrapper) {
return;
}
strongWrapper->destroy();
}
private:
std::weak_ptr<CallbackWrapper> callbackWrapper_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,190 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "JavaInteropTurboModule.h"
namespace facebook::react {
namespace {
// This is used for generating short exception strings.
std::string getType(jsi::Runtime& rt, const jsi::Value& v) {
if (v.isUndefined()) {
return "undefined";
} else if (v.isNull()) {
return "null";
} else if (v.isBool()) {
return v.getBool() ? "true" : "false";
} else if (v.isNumber()) {
return "number";
} else if (v.isString()) {
return "string";
} else if (v.isSymbol()) {
return "symbol";
} else if (v.isBigInt()) {
return "bigint";
} else if (v.isObject()) {
return v.getObject(rt).isFunction(rt) ? "function" : "object";
} else {
return "unknown";
}
}
} // namespace
JavaInteropTurboModule::JavaInteropTurboModule(
const JavaTurboModule::InitParams& params,
std::vector<MethodDescriptor> methodDescriptors)
: JavaTurboModule(params),
methodDescriptors_(methodDescriptors),
methodIDs_(methodDescriptors.size()),
constantsCache_(jsi::Value::undefined()) {
for (const auto& methodDescriptor : methodDescriptors) {
methodMap_[methodDescriptor.methodName] = MethodMetadata{
static_cast<size_t>(methodDescriptor.jsArgCount), nullptr};
}
}
jsi::Value JavaInteropTurboModule::create(
jsi::Runtime& runtime,
const jsi::PropNameID& propName) {
for (size_t i = 0; i < methodDescriptors_.size(); i += 1) {
if (methodDescriptors_[i].methodName == propName.utf8(runtime)) {
if (propName.utf8(runtime) == "getConstants") {
return jsi::Function::createFromHostFunction(
runtime,
propName,
static_cast<unsigned int>(methodDescriptors_[i].jsArgCount),
[this, i](
jsi::Runtime& rt,
const jsi::Value& thisVal,
const jsi::Value* args,
size_t count) mutable {
if (!this->constantsCache_.isUndefined()) {
return jsi::Value(rt, this->constantsCache_);
}
jsi::Value ret = this->invokeJavaMethod(
rt,
this->methodDescriptors_[i].jsiReturnKind,
this->methodDescriptors_[i].methodName,
this->methodDescriptors_[i].jniSignature,
args,
count,
this->methodIDs_[i]);
bool isRetValid = ret.isUndefined() || ret.isNull() ||
(ret.isObject() && !ret.asObject(rt).isFunction(rt));
if (!isRetValid) {
throw new jsi::JSError(
rt,
"Expected NativeModule " + this->name_ +
".getConstants() to return: null, undefined, or an object. But, got: " +
getType(rt, ret));
}
if (ret.isUndefined() || ret.isNull()) {
this->constantsCache_ = jsi::Object(rt);
} else {
this->constantsCache_ = jsi::Value(rt, ret);
}
return ret;
});
}
return jsi::Function::createFromHostFunction(
runtime,
propName,
static_cast<unsigned int>(methodDescriptors_[i].jsArgCount),
[this, i](
jsi::Runtime& rt,
const jsi::Value& thisVal,
const jsi::Value* args,
size_t count) {
return this->invokeJavaMethod(
rt,
this->methodDescriptors_[i].jsiReturnKind,
this->methodDescriptors_[i].methodName,
this->methodDescriptors_[i].jniSignature,
args,
count,
this->methodIDs_[i]);
});
}
}
jsi::Object constants = getConstants(runtime).asObject(runtime);
jsi::Value constant = constants.getProperty(runtime, propName);
if (!constant.isUndefined()) {
// TODO(T145105887): Output warning. Tried to access a constant as a
// property on the native module object. Please migrate to getConstants().
}
return constant;
}
bool JavaInteropTurboModule::exportsConstants() {
for (size_t i = 0; i < methodDescriptors_.size(); i += 1) {
if (methodDescriptors_[i].methodName == "getConstants") {
return true;
}
}
return false;
}
const jsi::Value& JavaInteropTurboModule::getConstants(jsi::Runtime& runtime) {
if (!constantsCache_.isUndefined()) {
return constantsCache_;
}
if (!exportsConstants()) {
constantsCache_ = jsi::Object(runtime);
return constantsCache_;
}
jsi::Value getConstantsProp =
get(runtime, jsi::PropNameID::forAscii(runtime, "getConstants"));
if (getConstantsProp.isObject()) {
jsi::Object getConstantsObj = getConstantsProp.asObject(runtime);
if (getConstantsObj.isFunction(runtime)) {
jsi::Function getConstantsFn = getConstantsObj.asFunction(runtime);
getConstantsFn.call(runtime);
return constantsCache_;
}
}
// Unable to invoke the getConstants() method.
// Maybe the module didn't define a getConstants() method.
// Default constants to {}, so no constants are spread into the NativeModule
constantsCache_ = jsi::Object(runtime);
return constantsCache_;
}
std::vector<facebook::jsi::PropNameID> JavaInteropTurboModule::getPropertyNames(
facebook::jsi::Runtime& runtime) {
std::vector<facebook::jsi::PropNameID> propNames =
JavaTurboModule::getPropertyNames(runtime);
jsi::Object constants = getConstants(runtime).asObject(runtime);
jsi::Array constantNames = constants.getPropertyNames(runtime);
for (size_t i = 0; i < constantNames.size(runtime); i += 1) {
jsi::Value constantName = constantNames.getValueAtIndex(runtime, i);
if (constantName.isString()) {
propNames.push_back(
jsi::PropNameID::forString(runtime, constantName.asString(runtime)));
}
}
return propNames;
}
} // namespace facebook::react

View File

@@ -0,0 +1,50 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <string>
#include <vector>
#include <ReactCommon/TurboModule.h>
#include <fbjni/fbjni.h>
#include <jsi/jsi.h>
#include "JavaTurboModule.h"
namespace facebook::react {
class JSI_EXPORT JavaInteropTurboModule : public JavaTurboModule {
public:
struct MethodDescriptor {
std::string methodName;
std::string jniSignature;
TurboModuleMethodValueKind jsiReturnKind;
int jsArgCount;
};
JavaInteropTurboModule(
const JavaTurboModule::InitParams& params,
std::vector<MethodDescriptor> methodDescriptors);
std::vector<facebook::jsi::PropNameID> getPropertyNames(
facebook::jsi::Runtime& runtime) override;
protected:
jsi::Value create(jsi::Runtime& runtime, const jsi::PropNameID& propName)
override;
private:
std::vector<MethodDescriptor> methodDescriptors_;
std::vector<jmethodID> methodIDs_;
jsi::Value constantsCache_;
const jsi::Value& getConstants(jsi::Runtime& runtime);
bool exportsConstants();
};
} // namespace facebook::react

View File

@@ -0,0 +1,947 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <memory>
#include <string>
#include <cxxreact/SystraceSection.h>
#include <fbjni/fbjni.h>
#include <glog/logging.h>
#include <jsi/jsi.h>
#include <ReactCommon/TurboModule.h>
#include <ReactCommon/TurboModulePerfLogger.h>
#include <ReactCommon/TurboModuleUtils.h>
#include <jsi/JSIDynamic.h>
#include <react/bridging/Bridging.h>
#include <react/debug/react_native_assert.h>
#include <react/jni/NativeMap.h>
#include <react/jni/ReadableNativeMap.h>
#include <react/jni/WritableNativeMap.h>
#include "JavaTurboModule.h"
namespace facebook::react {
namespace TMPL = TurboModulePerfLogger;
JavaTurboModule::JavaTurboModule(const InitParams& params)
: TurboModule(params.moduleName, params.jsInvoker),
instance_(jni::make_global(params.instance)),
nativeMethodCallInvoker_(params.nativeMethodCallInvoker),
shouldVoidMethodsExecuteSync_(params.shouldVoidMethodsExecuteSync) {}
JavaTurboModule::~JavaTurboModule() {
/**
* TODO(T75896241): In E2E tests, instance_ is null. Investigate why. Can we
* get rid of this null check?
*/
if (!instance_) {
return;
}
nativeMethodCallInvoker_->invokeAsync(
"~" + name_, [instance = std::move(instance_)]() mutable {
/**
* Reset the global NativeModule ref on the NativeModules thread. Why:
* - ~JavaTurboModule() can be called on a non-JVM thread. If we reset
* the global ref in ~JavaTurboModule(), we might access the JVM from a
* non-JVM thread, which will crash the app.
*/
instance.reset();
});
}
namespace {
constexpr auto kReactFeatureFlagsJavaDescriptor =
"com/facebook/react/config/ReactFeatureFlags";
bool getFeatureFlagBoolValue(const char* name) {
static const auto reactFeatureFlagsClass =
facebook::jni::findClassStatic(kReactFeatureFlagsJavaDescriptor);
const auto field = reactFeatureFlagsClass->getStaticField<jboolean>(name);
return reactFeatureFlagsClass->getStaticFieldValue(field);
}
bool traceTurboModulePromiseRejections() {
static bool traceRejections =
getFeatureFlagBoolValue("traceTurboModulePromiseRejections");
return traceRejections;
}
bool rejectTurboModulePromiseOnNativeError() {
static bool rejectOnError =
getFeatureFlagBoolValue("rejectTurboModulePromiseOnNativeError");
return rejectOnError;
}
struct JNIArgs {
JNIArgs(size_t count) : args_(count) {}
std::vector<jvalue> args_;
std::vector<jobject> globalRefs_;
};
auto createJavaCallback(
jsi::Runtime& rt,
jsi::Function&& function,
std::shared_ptr<CallInvoker> jsInvoker) {
std::optional<AsyncCallback<>> callback(
{rt, std::move(function), std::move(jsInvoker)});
return JCxxCallbackImpl::newObjectCxxArgs(
[callback = std::move(callback)](folly::dynamic args) mutable {
if (!callback) {
LOG(FATAL) << "Callback arg cannot be called more than once";
return;
}
callback->call([args = std::move(args)](
jsi::Runtime& rt, jsi::Function& jsFunction) {
std::vector<jsi::Value> jsArgs;
jsArgs.reserve(args.size());
for (const auto& val : args) {
jsArgs.emplace_back(jsi::valueFromDynamic(rt, val));
}
jsFunction.call(rt, (const jsi::Value*)jsArgs.data(), jsArgs.size());
});
callback = std::nullopt;
});
}
struct JPromiseImpl : public jni::JavaClass<JPromiseImpl> {
constexpr static auto kJavaDescriptor =
"Lcom/facebook/react/bridge/PromiseImpl;";
static jni::local_ref<javaobject> create(
jni::local_ref<JCallback::javaobject> resolve,
jni::local_ref<JCallback::javaobject> reject) {
return newInstance(resolve, reject);
}
};
// This is used for generating short exception strings.
std::string stringifyJSIValue(const jsi::Value& v, jsi::Runtime* rt = nullptr) {
if (v.isUndefined()) {
return "undefined";
}
if (v.isNull()) {
return "null";
}
if (v.isBool()) {
return std::string("a boolean (") + (v.getBool() ? "true" : "false") + ")";
}
if (v.isNumber()) {
return "a number (" + std::to_string(v.getNumber()) + ")";
}
if (v.isString()) {
return "a string (\"" + v.getString(*rt).utf8(*rt) + "\")";
}
react_native_assert(v.isObject() && "Expecting object.");
return rt != nullptr && v.getObject(*rt).isFunction(*rt) ? "a function"
: "an object";
}
class JavaTurboModuleArgumentConversionException : public std::runtime_error {
public:
JavaTurboModuleArgumentConversionException(
const std::string& expectedType,
int index,
const std::string& methodName,
const jsi::Value* arg,
jsi::Runtime* rt)
: std::runtime_error(
"Expected argument " + std::to_string(index) + " of method \"" +
methodName + "\" to be a " + expectedType + ", but got " +
stringifyJSIValue(*arg, rt)) {}
};
class JavaTurboModuleInvalidArgumentTypeException : public std::runtime_error {
public:
JavaTurboModuleInvalidArgumentTypeException(
const std::string& actualType,
int argIndex,
const std::string& methodName)
: std::runtime_error(
"Called method \"" + methodName + "\" with unsupported type " +
actualType + " at argument " + std::to_string(argIndex)) {}
};
class JavaTurboModuleInvalidArgumentCountException : public std::runtime_error {
public:
JavaTurboModuleInvalidArgumentCountException(
const std::string& methodName,
int actualArgCount,
int expectedArgCount)
: std::runtime_error(
"TurboModule method \"" + methodName + "\" called with " +
std::to_string(actualArgCount) +
" arguments (expected argument count: " +
std::to_string(expectedArgCount) + ").") {}
};
/**
* See
* https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/types.html
* for a description of Java method signature structure.
*/
std::vector<std::string> getMethodArgTypesFromSignature(
const std::string& methodSignature) {
std::vector<std::string> methodArgs;
for (auto it = methodSignature.begin(); it != methodSignature.end();
it += 1) {
if (*it == '(') {
continue;
}
if (*it == ')') {
break;
}
std::string type;
if (*it == '[') {
type += *it;
it += 1;
}
if (*it == 'L') {
for (; it != methodSignature.end(); it += 1) {
type += *it;
if (*it == ';') {
break;
}
}
} else {
type += *it;
}
methodArgs.push_back(type);
}
return methodArgs;
}
int32_t getUniqueId() {
static int32_t counter = 0;
return counter++;
}
// fbjni already does this conversion, but since we are using plain JNI, this
// needs to be done again
// TODO (axe) Reuse existing implementation as needed - the exist in
// MethodInvoker.cpp
JNIArgs convertJSIArgsToJNIArgs(
JNIEnv* env,
jsi::Runtime& rt,
const std::string& methodName,
const std::vector<std::string>& methodArgTypes,
const jsi::Value* args,
size_t count,
const std::shared_ptr<CallInvoker>& jsInvoker,
TurboModuleMethodValueKind valueKind) {
size_t expectedArgumentCount = valueKind == PromiseKind
? methodArgTypes.size() - 1
: methodArgTypes.size();
if (expectedArgumentCount != count) {
throw JavaTurboModuleInvalidArgumentCountException(
methodName,
static_cast<int>(count),
static_cast<int>(expectedArgumentCount));
}
JNIArgs jniArgs(valueKind == PromiseKind ? count + 1 : count);
auto& jargs = jniArgs.args_;
auto& globalRefs = jniArgs.globalRefs_;
auto makeGlobalIfNecessary =
[&globalRefs, env, valueKind](jobject obj) -> jobject {
if (valueKind == VoidKind || valueKind == PromiseKind) {
jobject globalObj = env->NewGlobalRef(obj);
globalRefs.push_back(globalObj);
env->DeleteLocalRef(obj);
return globalObj;
}
return obj;
};
for (unsigned int argIndex = 0; argIndex < count; argIndex += 1) {
const std::string& type = methodArgTypes.at(argIndex);
const jsi::Value* arg = &args[argIndex];
jvalue* jarg = &jargs[argIndex];
if (type == "D") {
if (!arg->isNumber()) {
throw JavaTurboModuleArgumentConversionException(
"number", argIndex, methodName, arg, &rt);
}
jarg->d = arg->getNumber();
continue;
}
if (type == "F") {
if (!arg->isNumber()) {
throw JavaTurboModuleArgumentConversionException(
"number", argIndex, methodName, arg, &rt);
}
jarg->f = (float)arg->getNumber();
continue;
}
if (type == "I") {
if (!arg->isNumber()) {
throw JavaTurboModuleArgumentConversionException(
"number", argIndex, methodName, arg, &rt);
}
jarg->i = (int)arg->getNumber();
continue;
}
if (type == "Z") {
if (!arg->isBool()) {
throw JavaTurboModuleArgumentConversionException(
"boolean", argIndex, methodName, arg, &rt);
}
jarg->z = (jboolean)arg->getBool();
continue;
}
if (arg->isNull() || arg->isUndefined()) {
jarg->l = nullptr;
} else if (type == "Ljava/lang/Double;") {
if (!arg->isNumber()) {
throw JavaTurboModuleArgumentConversionException(
"number", argIndex, methodName, arg, &rt);
}
jarg->l = makeGlobalIfNecessary(
jni::JDouble::valueOf(arg->getNumber()).release());
} else if (type == "Ljava/lang/Float;") {
if (!arg->isNumber()) {
throw JavaTurboModuleArgumentConversionException(
"number", argIndex, methodName, arg, &rt);
}
jarg->l = makeGlobalIfNecessary(
jni::JFloat::valueOf(arg->getNumber()).release());
} else if (type == "Ljava/lang/Integer;") {
if (!arg->isNumber()) {
throw JavaTurboModuleArgumentConversionException(
"number", argIndex, methodName, arg, &rt);
}
jarg->l = makeGlobalIfNecessary(
jni::JInteger::valueOf(arg->getNumber()).release());
} else if (type == "Ljava/lang/Boolean;") {
if (!arg->isBool()) {
throw JavaTurboModuleArgumentConversionException(
"boolean", argIndex, methodName, arg, &rt);
}
jarg->l = makeGlobalIfNecessary(
jni::JBoolean::valueOf(arg->getBool()).release());
} else if (type == "Ljava/lang/String;") {
if (!arg->isString()) {
throw JavaTurboModuleArgumentConversionException(
"string", argIndex, methodName, arg, &rt);
}
jarg->l = makeGlobalIfNecessary(
env->NewStringUTF(arg->getString(rt).utf8(rt).c_str()));
} else if (type == "Lcom/facebook/react/bridge/Callback;") {
if (!(arg->isObject() && arg->getObject(rt).isFunction(rt))) {
throw JavaTurboModuleArgumentConversionException(
"Function", argIndex, methodName, arg, &rt);
}
jsi::Function fn = arg->getObject(rt).getFunction(rt);
jarg->l = makeGlobalIfNecessary(
createJavaCallback(rt, std::move(fn), jsInvoker).release());
} else if (type == "Lcom/facebook/react/bridge/ReadableArray;") {
if (!(arg->isObject() && arg->getObject(rt).isArray(rt))) {
throw JavaTurboModuleArgumentConversionException(
"Array", argIndex, methodName, arg, &rt);
}
auto dynamicFromValue = jsi::dynamicFromValue(rt, *arg);
auto jParams =
ReadableNativeArray::newObjectCxxArgs(std::move(dynamicFromValue));
jarg->l = makeGlobalIfNecessary(jParams.release());
} else if (type == "Lcom/facebook/react/bridge/ReadableMap;") {
if (!(arg->isObject())) {
throw JavaTurboModuleArgumentConversionException(
"Object", argIndex, methodName, arg, &rt);
}
auto dynamicFromValue = jsi::dynamicFromValue(rt, *arg);
auto jParams =
ReadableNativeMap::createWithContents(std::move(dynamicFromValue));
jarg->l = makeGlobalIfNecessary(jParams.release());
} else {
throw JavaTurboModuleInvalidArgumentTypeException(
type, argIndex, methodName);
}
}
return jniArgs;
}
jsi::Value convertFromJMapToValue(JNIEnv* env, jsi::Runtime& rt, jobject arg) {
// We currently use Java Argument.makeNativeMap() method to do this conversion
// This could also be done purely in C++, but iterative over map methods
// but those may end up calling reflection methods anyway
// TODO (axe) Investigate the best way to convert Java Map to Value
jclass jArguments = env->FindClass("com/facebook/react/bridge/Arguments");
static jmethodID jMakeNativeMap = env->GetStaticMethodID(
jArguments,
"makeNativeMap",
"(Ljava/util/Map;)Lcom/facebook/react/bridge/WritableNativeMap;");
auto constants = env->CallStaticObjectMethod(jArguments, jMakeNativeMap, arg);
auto jResult = jni::adopt_local(constants);
auto result = jni::static_ref_cast<NativeMap::jhybridobject>(jResult);
return jsi::valueFromDynamic(rt, result->cthis()->consume());
}
jsi::Value createJSRuntimeError(
jsi::Runtime& runtime,
const std::string& message) {
return runtime.global()
.getPropertyAsFunction(runtime, "Error")
.call(runtime, message);
}
/**
* Creates JSError with current JS runtime stack and Throwable stack trace.
*/
jsi::JSError convertThrowableToJSError(
jsi::Runtime& runtime,
jni::alias_ref<jni::JThrowable> throwable) {
auto stackTrace = throwable->getStackTrace();
jsi::Array stackElements(runtime, stackTrace->size());
for (int i = 0; i < stackTrace->size(); ++i) {
auto frame = stackTrace->getElement(i);
jsi::Object frameObject(runtime);
frameObject.setProperty(runtime, "className", frame->getClassName());
frameObject.setProperty(runtime, "fileName", frame->getFileName());
frameObject.setProperty(runtime, "lineNumber", frame->getLineNumber());
frameObject.setProperty(runtime, "methodName", frame->getMethodName());
stackElements.setValueAtIndex(runtime, i, std::move(frameObject));
}
jsi::Object cause(runtime);
auto name = throwable->getClass()->getCanonicalName()->toStdString();
auto message = throwable->getMessage()->toStdString();
cause.setProperty(runtime, "name", name);
cause.setProperty(runtime, "message", message);
cause.setProperty(runtime, "stackElements", std::move(stackElements));
jsi::Value error =
createJSRuntimeError(runtime, "Exception in HostFunction: " + message);
error.asObject(runtime).setProperty(runtime, "cause", std::move(cause));
return {runtime, std::move(error)};
}
void rejectWithException(
AsyncCallback<>& reject,
std::exception_ptr exception,
std::optional<std::string>& jsInvocationStack) {
auto throwable = jni::getJavaExceptionForCppException(exception);
reject.call([jsInvocationStack, throwable = jni::make_global(throwable)](
jsi::Runtime& rt, jsi::Function& jsFunction) {
auto jsError = convertThrowableToJSError(rt, throwable);
if (jsInvocationStack.has_value()) {
jsError.value().asObject(rt).setProperty(
rt, "stack", jsInvocationStack.value());
}
jsFunction.call(rt, jsError.value());
});
}
} // namespace
jsi::Value JavaTurboModule::invokeJavaMethod(
jsi::Runtime& runtime,
TurboModuleMethodValueKind valueKind,
const std::string& methodNameStr,
const std::string& methodSignature,
const jsi::Value* args,
size_t argCount,
jmethodID& methodID) {
const char* methodName = methodNameStr.c_str();
const char* moduleName = name_.c_str();
bool isMethodSync =
(valueKind == VoidKind && shouldVoidMethodsExecuteSync_) ||
!(valueKind == VoidKind || valueKind == PromiseKind);
if (isMethodSync) {
TMPL::syncMethodCallStart(moduleName, methodName);
TMPL::syncMethodCallArgConversionStart(moduleName, methodName);
} else {
TMPL::asyncMethodCallStart(moduleName, methodName);
TMPL::asyncMethodCallArgConversionStart(moduleName, methodName);
}
JNIEnv* env = jni::Environment::current();
auto instance = instance_.get();
/**
* To account for jclasses and other misc LocalReferences we create.
*/
unsigned int buffer = 6;
/**
* For promises, we have to create a resolve fn, a reject fn, and a promise
* object. For normal returns, we just create the return object.
*/
unsigned int maxReturnObjects = 3;
/**
* When the return type is void, all JNI LocalReferences are converted to
* GlobalReferences. The LocalReferences are then promptly deleted
* after the conversion.
*/
unsigned int actualArgCount =
valueKind == VoidKind ? 0 : static_cast<unsigned int>(argCount);
unsigned int estimatedLocalRefCount =
actualArgCount + maxReturnObjects + buffer;
/**
* This will push a new JNI stack frame for the LocalReferences in this
* function call. When the stack frame for invokeJavaMethod is popped,
* all LocalReferences are deleted.
*
* In total, there can be at most kJniLocalRefMax (= 512) Jni
* LocalReferences alive at a time. estimatedLocalRefCount is provided
* so that PushLocalFrame can throw an out of memory error when the total
* number of alive LocalReferences is estimatedLocalRefCount smaller than
* kJniLocalRefMax.
*/
jni::JniLocalScope scope(env, estimatedLocalRefCount);
auto checkJNIErrorForMethodCall = [&]() -> void {
try {
FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
} catch (...) {
if (isMethodSync) {
TMPL::syncMethodCallFail(moduleName, methodName);
} else {
TMPL::asyncMethodCallFail(moduleName, methodName);
}
auto exception = std::current_exception();
auto throwable = jni::getJavaExceptionForCppException(exception);
throw convertThrowableToJSError(runtime, throwable);
}
};
if (!methodID) {
jclass cls = env->GetObjectClass(instance);
methodID = env->GetMethodID(cls, methodName, methodSignature.c_str());
// If the method signature doesn't match, show a redbox here instead of
// crashing later.
checkJNIErrorForMethodCall();
}
// TODO(T43933641): Refactor to remove this special-casing
if (methodNameStr == "getConstants") {
TMPL::syncMethodCallArgConversionEnd(moduleName, methodName);
TMPL::syncMethodCallExecutionStart(moduleName, methodName);
auto constantsMap = (jobject)env->CallObjectMethod(instance, methodID);
checkJNIErrorForMethodCall();
TMPL::syncMethodCallExecutionEnd(moduleName, methodName);
TMPL::syncMethodCallReturnConversionStart(moduleName, methodName);
jsi::Value returnValue = constantsMap == nullptr
? jsi::Value::undefined()
: convertFromJMapToValue(env, runtime, constantsMap);
TMPL::syncMethodCallReturnConversionEnd(moduleName, methodName);
TMPL::syncMethodCallEnd(moduleName, methodName);
return returnValue;
}
std::vector<std::string> methodArgTypes =
getMethodArgTypesFromSignature(methodSignature);
JNIArgs jniArgs = convertJSIArgsToJNIArgs(
env,
runtime,
methodNameStr,
methodArgTypes,
args,
argCount,
jsInvoker_,
valueKind);
if (isMethodSync && valueKind != PromiseKind) {
TMPL::syncMethodCallArgConversionEnd(moduleName, methodName);
TMPL::syncMethodCallExecutionStart(moduleName, methodName);
}
auto& jargs = jniArgs.args_;
auto& globalRefs = jniArgs.globalRefs_;
switch (valueKind) {
case BooleanKind: {
std::string returnType =
methodSignature.substr(methodSignature.find_last_of(')') + 1);
if (returnType == "Ljava/lang/Boolean;") {
auto returnObject =
env->CallObjectMethodA(instance, methodID, jargs.data());
checkJNIErrorForMethodCall();
TMPL::syncMethodCallExecutionEnd(moduleName, methodName);
TMPL::syncMethodCallReturnConversionStart(moduleName, methodName);
auto returnValue = jsi::Value::null();
if (returnObject) {
auto booleanObj = jni::adopt_local(
static_cast<jni::JBoolean::javaobject>(returnObject));
returnValue = jsi::Value(static_cast<bool>(booleanObj->value()));
}
TMPL::syncMethodCallReturnConversionEnd(moduleName, methodName);
TMPL::syncMethodCallEnd(moduleName, methodName);
return returnValue;
} else {
bool returnBoolean =
(bool)env->CallBooleanMethodA(instance, methodID, jargs.data());
checkJNIErrorForMethodCall();
TMPL::syncMethodCallExecutionEnd(moduleName, methodName);
TMPL::syncMethodCallReturnConversionStart(moduleName, methodName);
jsi::Value returnValue = jsi::Value(returnBoolean);
TMPL::syncMethodCallReturnConversionEnd(moduleName, methodName);
TMPL::syncMethodCallEnd(moduleName, methodName);
return returnValue;
}
}
case NumberKind: {
std::string returnType =
methodSignature.substr(methodSignature.find_last_of(')') + 1);
if (returnType == "Ljava/lang/Double;" ||
returnType == "Ljava/lang/Float;" ||
returnType == "Ljava/lang/Integer;") {
auto returnObject =
env->CallObjectMethodA(instance, methodID, jargs.data());
checkJNIErrorForMethodCall();
TMPL::syncMethodCallExecutionEnd(moduleName, methodName);
TMPL::syncMethodCallReturnConversionStart(moduleName, methodName);
auto returnValue = jsi::Value::null();
if (returnObject) {
if (returnType == "Ljava/lang/Double;") {
auto doubleObj = jni::adopt_local(
static_cast<jni::JDouble::javaobject>(returnObject));
returnValue = jsi::Value(doubleObj->value());
} else if (returnType == "Ljava/lang/Float;") {
auto floatObj = jni::adopt_local(
static_cast<jni::JFloat::javaobject>(returnObject));
returnValue = jsi::Value((double)floatObj->value());
} else if (returnType == "Ljava/lang/Integer;") {
auto intObj = jni::adopt_local(
static_cast<jni::JInteger::javaobject>(returnObject));
returnValue = jsi::Value(intObj->value());
}
}
TMPL::syncMethodCallReturnConversionEnd(moduleName, methodName);
TMPL::syncMethodCallEnd(moduleName, methodName);
return returnValue;
} else if (returnType == "D" || returnType == "F" || returnType == "I") {
jsi::Value returnValue = jsi::Value::undefined();
if (returnType == "D") {
double returnDouble =
(double)env->CallDoubleMethodA(instance, methodID, jargs.data());
checkJNIErrorForMethodCall();
TMPL::syncMethodCallExecutionEnd(moduleName, methodName);
TMPL::syncMethodCallReturnConversionStart(moduleName, methodName);
returnValue = jsi::Value(returnDouble);
} else if (returnType == "F") {
float returnFloat =
(float)env->CallFloatMethodA(instance, methodID, jargs.data());
checkJNIErrorForMethodCall();
TMPL::syncMethodCallExecutionEnd(moduleName, methodName);
TMPL::syncMethodCallReturnConversionStart(moduleName, methodName);
returnValue = jsi::Value((double)returnFloat);
} else if (returnType == "I") {
int returnInt =
(int)env->CallIntMethodA(instance, methodID, jargs.data());
checkJNIErrorForMethodCall();
TMPL::syncMethodCallExecutionEnd(moduleName, methodName);
TMPL::syncMethodCallReturnConversionStart(moduleName, methodName);
returnValue = jsi::Value(returnInt);
}
TMPL::syncMethodCallReturnConversionEnd(moduleName, methodName);
TMPL::syncMethodCallEnd(moduleName, methodName);
return returnValue;
}
}
case StringKind: {
auto returnString =
(jstring)env->CallObjectMethodA(instance, methodID, jargs.data());
checkJNIErrorForMethodCall();
TMPL::syncMethodCallExecutionEnd(moduleName, methodName);
TMPL::syncMethodCallReturnConversionStart(moduleName, methodName);
jsi::Value returnValue = jsi::Value::null();
if (returnString != nullptr) {
const char* js = env->GetStringUTFChars(returnString, nullptr);
std::string result = js;
env->ReleaseStringUTFChars(returnString, js);
returnValue =
jsi::Value(runtime, jsi::String::createFromUtf8(runtime, result));
}
TMPL::syncMethodCallReturnConversionEnd(moduleName, methodName);
TMPL::syncMethodCallEnd(moduleName, methodName);
return returnValue;
}
case ObjectKind: {
auto returnObject =
env->CallObjectMethodA(instance, methodID, jargs.data());
checkJNIErrorForMethodCall();
TMPL::syncMethodCallExecutionEnd(moduleName, methodName);
TMPL::syncMethodCallReturnConversionStart(moduleName, methodName);
jsi::Value returnValue = jsi::Value::null();
if (returnObject != nullptr) {
auto jResult = jni::adopt_local(returnObject);
auto result = jni::static_ref_cast<NativeMap::jhybridobject>(jResult);
returnValue =
jsi::valueFromDynamic(runtime, result->cthis()->consume());
}
TMPL::syncMethodCallReturnConversionEnd(moduleName, methodName);
TMPL::syncMethodCallEnd(moduleName, methodName);
return returnValue;
}
case ArrayKind: {
auto returnObject =
(jobject)env->CallObjectMethodA(instance, methodID, jargs.data());
checkJNIErrorForMethodCall();
TMPL::syncMethodCallExecutionEnd(moduleName, methodName);
TMPL::syncMethodCallReturnConversionStart(moduleName, methodName);
jsi::Value returnValue = jsi::Value::null();
if (returnObject != nullptr) {
auto jResult = jni::adopt_local(returnObject);
auto result = jni::static_ref_cast<NativeArray::jhybridobject>(jResult);
returnValue =
jsi::valueFromDynamic(runtime, result->cthis()->consume());
}
TMPL::syncMethodCallReturnConversionEnd(moduleName, methodName);
TMPL::syncMethodCallEnd(moduleName, methodName);
return returnValue;
}
case VoidKind: {
if (shouldVoidMethodsExecuteSync_) {
env->CallVoidMethodA(instance, methodID, jargs.data());
checkJNIErrorForMethodCall();
TMPL::syncMethodCallExecutionEnd(moduleName, methodName);
TMPL::syncMethodCallEnd(moduleName, methodName);
return jsi::Value::undefined();
}
TMPL::asyncMethodCallArgConversionEnd(moduleName, methodName);
TMPL::asyncMethodCallDispatch(moduleName, methodName);
nativeMethodCallInvoker_->invokeAsync(
methodName,
[jargs,
globalRefs,
methodID,
instance_ = jni::make_weak(instance_),
moduleNameStr = name_,
methodNameStr,
id = getUniqueId()]() mutable {
SystraceSection s(
"JavaTurboModuleAsyncMethodInvocation",
"module",
moduleNameStr,
"method",
methodNameStr,
"returnType",
"void");
auto instance = instance_.lockLocal();
if (!instance) {
return;
}
// Require the env from the current scope, which may be
// different from the original invocation's scope
JNIEnv* env = jni::Environment::current();
const char* moduleName = moduleNameStr.c_str();
const char* methodName = methodNameStr.c_str();
TMPL::asyncMethodCallExecutionStart(moduleName, methodName, id);
env->CallVoidMethodA(instance.get(), methodID, jargs.data());
try {
FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
} catch (...) {
TMPL::asyncMethodCallExecutionFail(moduleName, methodName, id);
throw;
}
for (auto globalRef : globalRefs) {
env->DeleteGlobalRef(globalRef);
}
TMPL::asyncMethodCallExecutionEnd(moduleName, methodName, id);
});
TMPL::asyncMethodCallEnd(moduleName, methodName);
return jsi::Value::undefined();
}
case PromiseKind: {
// We could use AsyncPromise here, but this avoids the overhead of
// the shared_ptr for PromiseHolder
jsi::Function Promise =
runtime.global().getPropertyAsFunction(runtime, "Promise");
// The callback is used for auto rejecting if error is caught from method
// invocation
std::optional<AsyncCallback<>> nativeRejectCallback;
// The promise constructor runs its arg immediately, so this is safe
jobject javaPromise;
jsi::Value jsPromise = Promise.callAsConstructor(
runtime,
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "fn"),
2,
[&](jsi::Runtime& runtime,
const jsi::Value&,
const jsi::Value* args,
size_t argCount) {
if (argCount != 2) {
throw jsi::JSError(runtime, "Incorrect number of arguments");
}
if (rejectTurboModulePromiseOnNativeError()) {
nativeRejectCallback = AsyncCallback(
runtime,
args[1].getObject(runtime).getFunction(runtime),
jsInvoker_);
}
auto resolve = createJavaCallback(
runtime,
args[0].getObject(runtime).getFunction(runtime),
jsInvoker_);
auto reject = createJavaCallback(
runtime,
args[1].getObject(runtime).getFunction(runtime),
jsInvoker_);
javaPromise = JPromiseImpl::create(resolve, reject).release();
return jsi::Value::undefined();
}));
jobject globalPromise = env->NewGlobalRef(javaPromise);
globalRefs.push_back(globalPromise);
env->DeleteLocalRef(javaPromise);
jargs[argCount].l = globalPromise;
// JS Stack at the time when the promise is created.
std::optional<std::string> jsInvocationStack;
if (traceTurboModulePromiseRejections()) {
jsInvocationStack = createJSRuntimeError(runtime, "")
.asObject(runtime)
.getProperty(runtime, "stack")
.toString(runtime)
.utf8(runtime);
}
const char* moduleName = name_.c_str();
const char* methodName = methodNameStr.c_str();
TMPL::asyncMethodCallArgConversionEnd(moduleName, methodName);
TMPL::asyncMethodCallDispatch(moduleName, methodName);
nativeMethodCallInvoker_->invokeAsync(
methodName,
[jargs,
rejectCallback = std::move(nativeRejectCallback),
jsInvocationStack = std::move(jsInvocationStack),
globalRefs,
methodID,
instance_ = jni::make_weak(instance_),
moduleNameStr = name_,
methodNameStr,
id = getUniqueId()]() mutable {
SystraceSection s(
"JavaTurboModuleAsyncMethodInvocation",
"module",
moduleNameStr,
"method",
methodNameStr,
"returnType",
"promise");
auto instance = instance_.lockLocal();
if (!instance) {
return;
}
// Require the env from the current scope, which may be
// different from the original invocation's scope
JNIEnv* env = jni::Environment::current();
const char* moduleName = moduleNameStr.c_str();
const char* methodName = methodNameStr.c_str();
TMPL::asyncMethodCallExecutionStart(moduleName, methodName, id);
env->CallVoidMethodA(instance.get(), methodID, jargs.data());
try {
FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
} catch (...) {
TMPL::asyncMethodCallExecutionFail(moduleName, methodName, id);
if (rejectTurboModulePromiseOnNativeError() && rejectCallback) {
auto exception = std::current_exception();
rejectWithException(
*rejectCallback, exception, jsInvocationStack);
rejectCallback = std::nullopt;
} else {
throw;
}
}
for (auto globalRef : globalRefs) {
env->DeleteGlobalRef(globalRef);
}
TMPL::asyncMethodCallExecutionEnd(moduleName, methodName, id);
});
TMPL::asyncMethodCallEnd(moduleName, methodName);
return jsPromise;
}
default:
throw std::runtime_error(
"Unable to find method module: " + methodNameStr + "(" +
methodSignature + ")");
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,55 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <string>
#include <ReactCommon/CallInvoker.h>
#include <ReactCommon/TurboModule.h>
#include <jsi/jsi.h>
#include <react/bridging/CallbackWrapper.h>
#include <react/jni/JCallback.h>
namespace facebook::react {
struct JTurboModule : jni::JavaClass<JTurboModule> {
static auto constexpr kJavaDescriptor =
"Lcom/facebook/react/turbomodule/core/interfaces/TurboModule;";
};
class JSI_EXPORT JavaTurboModule : public TurboModule {
public:
// TODO(T65603471): Should we unify this with a Fabric abstraction?
struct InitParams {
std::string moduleName;
jni::alias_ref<jobject> instance;
std::shared_ptr<CallInvoker> jsInvoker;
std::shared_ptr<NativeMethodCallInvoker> nativeMethodCallInvoker;
bool shouldVoidMethodsExecuteSync;
};
JavaTurboModule(const InitParams& params);
virtual ~JavaTurboModule();
jsi::Value invokeJavaMethod(
jsi::Runtime& runtime,
TurboModuleMethodValueKind valueKind,
const std::string& methodName,
const std::string& methodSignature,
const jsi::Value* args,
size_t argCount,
jmethodID& cachedMethodID);
private:
// instance_ can be of type JTurboModule, or JNativeModule
jni::global_ref<jobject> instance_;
std::shared_ptr<NativeMethodCallInvoker> nativeMethodCallInvoker_;
bool shouldVoidMethodsExecuteSync_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,62 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
require "json"
package = JSON.parse(File.read(File.join(__dir__, "..", "..", "..", "..", "..", "..", "package.json")))
version = package['version']
source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which were presumably in.
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
else
source[:tag] = "v#{version}"
end
folly_config = get_folly_config()
folly_compiler_flags = folly_config[:compiler_flags]
folly_version = folly_config[:version]
boost_compiler_flags = '-Wno-documentation'
using_hermes = ENV['USE_HERMES'] == nil || ENV['USE_HERMES'] == "1"
Pod::Spec.new do |s|
s.name = "React-NativeModulesApple"
s.module_name = "React_NativeModulesApple"
s.header_dir = "ReactCommon" # Use global header_dir for all subspecs for use_frameworks! compatibility
s.version = version
s.summary = "-"
s.homepage = "https://reactnative.dev/"
s.license = package["license"]
s.author = "Meta Platforms, Inc. and its affiliates"
s.platforms = min_supported_versions
s.source = source
s.compiler_flags = folly_compiler_flags + ' ' + boost_compiler_flags
s.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\" \"$(PODS_ROOT)/RCT-Folly\" \"$(PODS_ROOT)/DoubleConversion\" \"$(PODS_ROOT)/fmt/include\" \"$(PODS_ROOT)/Headers/Private/React-Core\"",
"USE_HEADERMAP" => "YES",
"CLANG_CXX_LANGUAGE_STANDARD" => "c++20",
"GCC_WARN_PEDANTIC" => "YES" }
if ENV['USE_FRAMEWORKS']
s.header_mappings_dir = './'
end
s.source_files = "ReactCommon/**/*.{mm,cpp,h}"
s.dependency "glog"
s.dependency "ReactCommon/turbomodule/core"
s.dependency "ReactCommon/turbomodule/bridging"
s.dependency "React-callinvoker"
s.dependency "React-Core"
s.dependency "React-cxxreact"
s.dependency "React-jsi"
s.dependency "React-runtimeexecutor"
add_dependency(s, "React-jsinspector", :framework_name => 'jsinspector_modern')
if using_hermes
s.dependency "hermes-engine"
else
s.dependency "React-jsc"
end
end

View File

@@ -0,0 +1,95 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#import <string>
#import <vector>
#import <ReactCommon/TurboModule.h>
#import <jsi/jsi.h>
#import "RCTTurboModule.h"
namespace facebook {
namespace react {
class JSI_EXPORT ObjCInteropTurboModule : public ObjCTurboModule {
public:
struct MethodDescriptor {
std::string methodName;
SEL selector;
size_t jsArgCount;
TurboModuleMethodValueKind jsReturnKind;
};
ObjCInteropTurboModule(const ObjCTurboModule::InitParams &params);
std::vector<facebook::jsi::PropNameID> getPropertyNames(facebook::jsi::Runtime &runtime) override;
protected:
jsi::Value create(jsi::Runtime &runtime, const jsi::PropNameID &propName) override;
/**
* Why is this overriden?
*
* Purpose: Converts native module method returns from Objective C values to JavaScript values.
*
* ObjCTurboModule converts returns by returnType. But, Legacy native modules convert returns by the Objective C type:
* React Native cannot infer a method's returnType from the RCT_EXPORT_METHOD annotations.
*/
jsi::Value convertReturnIdToJSIValue(
jsi::Runtime &runtime,
const char *methodName,
TurboModuleMethodValueKind returnType,
id result) override;
/**
* Why is this overriden?
*
* Purpose: Get a native module method's argument's type, given the method name, and argument index.
*
* This override is meant to serve as a performance optimization.
*
* ObjCTurboModule computes the method argument types from the RCT_EXPORT_METHOD macros lazily.
* ObjCInteropTurboModule computes all the method argument types eagerly on module init.
*
* ObjCInteropTurboModule overrides getArgumentTypeName, so ObjCTurboModule doesn't end up re-computing the argument
* type names again.
*/
NSString *getArgumentTypeName(jsi::Runtime &runtime, NSString *methodName, int argIndex) override;
/**
* Why is this overriden?
*
* Purpose: Convert arguments from JavaScript values to Objective C values. Assign the Objective C argument to the
* method invocation.
*
* ObjCTurboModule tries to minimize reliance on RCTConvert for argument conversion. Why: RCTConvert relies on the
* RCT_EXPORT_METHOD macros, which we want to remove long term. But, Legacy native modules rely heavily on RCTConvert
* for argument conversion.
*/
void setInvocationArg(
jsi::Runtime &runtime,
const char *methodName,
const std::string &objCArgType,
const jsi::Value &arg,
size_t i,
NSInvocation *inv,
NSMutableArray *retainedObjectsForInvocation) override;
private:
std::vector<MethodDescriptor> methodDescriptors_;
NSDictionary<NSString *, NSArray<NSString *> *> *methodArgumentTypeNames_;
jsi::Value constantsCache_;
const jsi::Value &getConstants(jsi::Runtime &runtime);
bool exportsConstants();
};
} // namespace react
} // namespace facebook

View File

@@ -0,0 +1,654 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "RCTInteropTurboModule.h"
#import <objc/message.h>
#import <objc/runtime.h>
#import <React/RCTAssert.h>
#import <React/RCTConvert.h>
#import <React/RCTCxxConvert.h>
#import <React/RCTManagedPointer.h>
#import <React/RCTModuleMethod.h>
#import <React/RCTParserUtils.h>
namespace facebook {
namespace react {
namespace {
// This is used for generating short exception strings.
std::string getType(jsi::Runtime &rt, const jsi::Value &v)
{
if (v.isUndefined()) {
return "undefined";
} else if (v.isNull()) {
return "null";
} else if (v.isBool()) {
return v.getBool() ? "true" : "false";
} else if (v.isNumber()) {
return "number";
} else if (v.isString()) {
return "string";
} else if (v.isSymbol()) {
return "symbol";
} else if (v.isBigInt()) {
return "bigint";
} else if (v.isObject()) {
jsi::Object vObj = v.getObject(rt);
return vObj.isFunction(rt) ? "function" : vObj.isArray(rt) ? "array" : "object";
} else {
return "unknown";
}
}
std::vector<const RCTMethodInfo *> getMethodInfos(Class moduleClass)
{
std::vector<const RCTMethodInfo *> methodInfos;
Class cls = moduleClass;
while (cls && cls != [NSObject class] && cls != [NSProxy class]) {
unsigned int methodCount;
Method *methods = class_copyMethodList(object_getClass(cls), &methodCount);
for (unsigned int i = 0; i < methodCount; i++) {
Method method = methods[i];
SEL selector = method_getName(method);
if ([NSStringFromSelector(selector) hasPrefix:@"__rct_export__"]) {
IMP imp = method_getImplementation(method);
const RCTMethodInfo *methodInfo = ((const RCTMethodInfo *(*)(id, SEL))imp)(moduleClass, selector);
methodInfos.push_back(methodInfo);
}
}
free(methods);
cls = class_getSuperclass(cls);
}
return methodInfos;
}
NSString *getJSMethodName(const RCTMethodInfo *methodInfo)
{
std::string jsName = methodInfo->jsName;
if (jsName != "") {
return @(jsName.c_str());
}
NSString *methodName = @(methodInfo->objcName);
NSRange colonRange = [methodName rangeOfString:@":"];
if (colonRange.location != NSNotFound) {
methodName = [methodName substringToIndex:colonRange.location];
}
methodName = [methodName stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
RCTAssert(
methodName.length,
@"%s is not a valid JS function name, please"
" supply an alternative using RCT_REMAP_METHOD()",
methodInfo->objcName);
return methodName;
}
class ObjCInteropTurboModuleParseException : public std::runtime_error {
public:
ObjCInteropTurboModuleParseException(std::string moduleName, std::string methodName, std::string message)
: std::runtime_error(
"Failed to create module \"" + moduleName + "\": Error while parsing method " + moduleName + "." +
methodName + ": " + message)
{
}
};
struct ExportedMethod {
NSString *methodName;
NSArray<NSString *> *argumentTypes;
std::string returnType;
SEL selector;
};
std::vector<ExportedMethod> parseExportedMethods(std::string moduleName, Class moduleClass)
{
std::vector<const RCTMethodInfo *> methodInfos = getMethodInfos(moduleClass);
std::vector<ExportedMethod> methods;
methods.reserve(methodInfos.size());
for (const RCTMethodInfo *methodInfo : methodInfos) {
NSString *jsMethodName = getJSMethodName(methodInfo);
NSArray<RCTMethodArgument *> *arguments;
SEL objCMethodSelector = NSSelectorFromString(RCTParseMethodSignature(methodInfo->objcName, &arguments));
NSMethodSignature *objCMethodSignature = [moduleClass instanceMethodSignatureForSelector:objCMethodSelector];
std::string objCMethodReturnType = [objCMethodSignature methodReturnType];
if (objCMethodSignature.numberOfArguments - 2 != [arguments count]) {
std::string message = "Parsed argument count (i.e: " + std::to_string([arguments count]) +
") != Objective C method signature argument count (i.e: " +
std::to_string(objCMethodSignature.numberOfArguments - 2) + ").";
throw ObjCInteropTurboModuleParseException(moduleName, [jsMethodName UTF8String], message);
}
NSMutableArray<NSString *> *argumentTypes = [NSMutableArray new];
for (NSUInteger i = 0; i < [arguments count]; i += 1) {
[argumentTypes addObject:arguments[i].type];
}
if ([argumentTypes count] == 1) {
std::string lastArgType = [argumentTypes[[argumentTypes count] - 1] UTF8String];
if (lastArgType == "RCTPromiseResolveBlock" || lastArgType == "RCTPromiseRejectBlock") {
std::string message =
"Methods that return promises must accept a RCTPromiseResolveBlock followed by a RCTPromiseRejectBlock. This method just accepts a " +
lastArgType + ".";
throw ObjCInteropTurboModuleParseException(moduleName, [jsMethodName UTF8String], message);
}
} else if ([argumentTypes count] > 1) {
std::string lastArgType = [argumentTypes[[argumentTypes count] - 1] UTF8String];
std::string secondLastArgType = [argumentTypes[[argumentTypes count] - 2] UTF8String];
if ((secondLastArgType == "RCTPromiseResolveBlock" && lastArgType != "RCTPromiseRejectBlock") ||
(secondLastArgType != "RCTPromiseResolveBlock" && lastArgType == "RCTPromiseRejectBlock")) {
std::string message =
"Methods that return promises must accept a RCTPromiseResolveBlock followed by a RCTPromiseRejectBlock. This method accepts a " +
secondLastArgType + " followed by a " + lastArgType + ".";
throw ObjCInteropTurboModuleParseException(moduleName, [jsMethodName UTF8String], message);
}
}
methods.push_back(
{.methodName = jsMethodName,
.argumentTypes = argumentTypes,
.returnType = objCMethodReturnType,
.selector = objCMethodSelector});
}
return methods;
}
SEL selectorForType(NSString *type)
{
const char *input = type.UTF8String;
return NSSelectorFromString([RCTParseType(&input) stringByAppendingString:@":"]);
}
template <typename T>
T RCTConvertTo(SEL selector, id json)
{
T (*convert)(id, SEL, id) = (__typeof__(convert))objc_msgSend;
return convert([RCTConvert class], selector, json);
}
} // namespace
ObjCInteropTurboModule::ObjCInteropTurboModule(const ObjCTurboModule::InitParams &params)
: ObjCTurboModule(params), constantsCache_(jsi::Value::undefined())
{
std::vector<ExportedMethod> methods = parseExportedMethods(name_, [params.instance class]);
methodDescriptors_.reserve(methods.size());
NSMutableDictionary<NSString *, NSArray<NSString *> *> *methodArgTypeNames = [NSMutableDictionary new];
methodArgumentTypeNames_ = methodArgTypeNames;
for (const ExportedMethod &method : methods) {
const size_t numArgs = [method.argumentTypes count];
const bool isPromiseMethod =
numArgs >= 2 && [method.argumentTypes[numArgs - 1] isEqualToString:@"RCTPromiseRejectBlock"];
const size_t jsArgCount = isPromiseMethod ? numArgs - 2 : numArgs;
/**
* In the TurboModule system, only promises and voids are special. So, set those.
* In the else case, just assume ObjectKind. This will be ignored by the interop layer.
* In the else case, the interop layer will just call into ::convertReturnIdToJSIValue()
*/
const TurboModuleMethodValueKind returnKind = isPromiseMethod ? PromiseKind
: method.returnType == @encode(void) ? VoidKind
: ObjectKind;
methodMap_[[method.methodName UTF8String]] = MethodMetadata{static_cast<size_t>(jsArgCount), nullptr};
for (NSUInteger i = 0; i < numArgs; i += 1) {
NSString *typeName = method.argumentTypes[i];
if ([typeName hasPrefix:@"JS::"]) {
NSString *rctCxxConvertSelector =
[[typeName stringByReplacingOccurrencesOfString:@"::" withString:@"_"] stringByAppendingString:@":"];
setMethodArgConversionSelector(method.methodName, i, rctCxxConvertSelector);
}
}
methodArgTypeNames[method.methodName] = method.argumentTypes;
methodDescriptors_.push_back({
.methodName = [method.methodName UTF8String],
.selector = method.selector,
.jsArgCount = jsArgCount,
.jsReturnKind = returnKind,
});
}
if ([params.instance respondsToSelector:@selector(constantsToExport)]) {
methodDescriptors_.push_back({
.methodName = "getConstants", .selector = @selector(constantsToExport), .jsArgCount = 0,
.jsReturnKind = ObjectKind,
});
} else {
static SEL getConstantsSelector = NSSelectorFromString(@"getConstants");
if ([params.instance respondsToSelector:getConstantsSelector]) {
methodDescriptors_.push_back({
.methodName = "getConstants",
.selector = getConstantsSelector,
.jsArgCount = 0,
.jsReturnKind = ObjectKind,
});
}
}
}
jsi::Value ObjCInteropTurboModule::create(jsi::Runtime &runtime, const jsi::PropNameID &propName)
{
for (size_t i = 0; i < methodDescriptors_.size(); i += 1) {
if (methodDescriptors_[i].methodName == propName.utf8(runtime)) {
if (propName.utf8(runtime) == "getConstants") {
return jsi::Function::createFromHostFunction(
runtime,
propName,
static_cast<unsigned int>(methodDescriptors_[i].jsArgCount),
[this, i](jsi::Runtime &rt, const jsi::Value &thisVal, const jsi::Value *args, size_t count) mutable {
if (!this->constantsCache_.isUndefined()) {
return jsi::Value(rt, this->constantsCache_);
}
// TODO: Dispatch getConstants to the main queue, if the module requires main queue setup
jsi::Value ret = this->invokeObjCMethod(
rt,
this->methodDescriptors_[i].jsReturnKind,
this->methodDescriptors_[i].methodName,
this->methodDescriptors_[i].selector,
args,
count);
bool isRetValid = ret.isUndefined() || ret.isNull() ||
(ret.isObject() && !ret.asObject(rt).isFunction(rt) && !ret.asObject(rt).isArray(rt));
if (!isRetValid) {
std::string methodJsSignature = name_ + ".getConstants()";
std::string errorPrefix = methodJsSignature + ": ";
throw jsi::JSError(
rt,
errorPrefix + "Expected return value to be null, undefined, or a plain object. But, got: " +
getType(rt, ret));
}
if (ret.isUndefined() || ret.isNull()) {
this->constantsCache_ = jsi::Object(rt);
} else {
this->constantsCache_ = jsi::Value(rt, ret);
}
return ret;
});
}
return jsi::Function::createFromHostFunction(
runtime,
propName,
static_cast<unsigned int>(methodDescriptors_[i].jsArgCount),
[this, i](jsi::Runtime &rt, const jsi::Value &thisVal, const jsi::Value *args, size_t count) {
return this->invokeObjCMethod(
rt,
this->methodDescriptors_[i].jsReturnKind,
this->methodDescriptors_[i].methodName,
this->methodDescriptors_[i].selector,
args,
count);
});
}
}
jsi::Object constants = getConstants(runtime).asObject(runtime);
jsi::Value constant = constants.getProperty(runtime, propName);
if (!constant.isUndefined()) {
// TODO(T145105887): Output warning. Tried to access a constant as a
// property on the native module object. Please migrate to getConstants().
}
return constant;
}
void ObjCInteropTurboModule::setInvocationArg(
jsi::Runtime &runtime,
const char *methodNameCStr,
const std::string &objCArgType,
const jsi::Value &jsiArg,
size_t index,
NSInvocation *inv,
NSMutableArray *retainedObjectsForInvocation)
{
NSString *methodName = @(methodNameCStr);
std::string methodJsSignature = name_ + "." + methodNameCStr + "()";
NSString *argumentType = getArgumentTypeName(runtime, methodName, static_cast<int>(index));
std::string errorPrefix = methodJsSignature + ": Error while converting JavaScript argument " +
std::to_string(index) + " to Objective C type " + [argumentType UTF8String] + ". ";
SEL selector = selectorForType(argumentType);
if ([RCTConvert respondsToSelector:selector]) {
id objCArg = TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_);
if (objCArgType == @encode(char)) {
char arg = RCTConvertTo<char>(selector, objCArg);
[inv setArgument:&arg atIndex:(index) + 2];
return;
}
if (objCArgType == @encode(unsigned char)) {
unsigned char arg = RCTConvertTo<unsigned char>(selector, objCArg);
[inv setArgument:&arg atIndex:(index) + 2];
return;
}
if (objCArgType == @encode(short)) {
short arg = RCTConvertTo<short>(selector, objCArg);
[inv setArgument:&arg atIndex:(index) + 2];
return;
}
if (objCArgType == @encode(unsigned short)) {
unsigned short arg = RCTConvertTo<unsigned short>(selector, objCArg);
[inv setArgument:&arg atIndex:(index) + 2];
return;
}
if (objCArgType == @encode(int)) {
int arg = RCTConvertTo<int>(selector, objCArg);
[inv setArgument:&arg atIndex:(index) + 2];
return;
}
if (objCArgType == @encode(unsigned int)) {
unsigned int arg = RCTConvertTo<unsigned int>(selector, objCArg);
[inv setArgument:&arg atIndex:(index) + 2];
return;
}
if (objCArgType == @encode(long)) {
long arg = RCTConvertTo<long>(selector, objCArg);
[inv setArgument:&arg atIndex:(index) + 2];
return;
}
if (objCArgType == @encode(unsigned long)) {
unsigned long arg = RCTConvertTo<unsigned long>(selector, objCArg);
[inv setArgument:&arg atIndex:(index) + 2];
return;
}
if (objCArgType == @encode(long long)) {
long long arg = RCTConvertTo<long long>(selector, objCArg);
[inv setArgument:&arg atIndex:(index) + 2];
return;
}
if (objCArgType == @encode(unsigned long long)) {
unsigned long long arg = RCTConvertTo<unsigned long long>(selector, objCArg);
[inv setArgument:&arg atIndex:(index) + 2];
return;
}
if (objCArgType == @encode(float)) {
float arg = RCTConvertTo<float>(selector, objCArg);
[inv setArgument:&arg atIndex:(index) + 2];
return;
}
if (objCArgType == @encode(double)) {
double arg = RCTConvertTo<double>(selector, objCArg);
[inv setArgument:&arg atIndex:(index) + 2];
return;
}
if (objCArgType == @encode(BOOL)) {
BOOL arg = RCTConvertTo<BOOL>(selector, objCArg);
[inv setArgument:&arg atIndex:(index) + 2];
return;
}
if (objCArgType == @encode(SEL)) {
SEL arg = RCTConvertTo<SEL>(selector, objCArg);
[inv setArgument:&arg atIndex:(index) + 2];
return;
}
if (objCArgType == @encode(const char *)) {
const char *arg = RCTConvertTo<const char *>(selector, objCArg);
[inv setArgument:&arg atIndex:(index) + 2];
return;
}
if (objCArgType == @encode(void *)) {
void *arg = RCTConvertTo<void *>(selector, objCArg);
[inv setArgument:&arg atIndex:(index) + 2];
return;
}
if (objCArgType == @encode(id)) {
id arg = RCTConvertTo<id>(selector, objCArg);
if (arg) {
[retainedObjectsForInvocation addObject:arg];
}
[inv setArgument:&arg atIndex:(index) + 2];
return;
}
if (objCArgType[0] == _C_STRUCT_B) {
NSMethodSignature *typeSignature = [RCTConvert methodSignatureForSelector:selector];
NSInvocation *typeInvocation = [NSInvocation invocationWithMethodSignature:typeSignature];
typeInvocation.selector = selector;
typeInvocation.target = [RCTConvert class];
void *returnValue = malloc(typeSignature.methodReturnLength);
if (!returnValue) {
// CWE - 391 : Unchecked error condition
// https://www.cvedetails.com/cwe-details/391/Unchecked-Error-Condition.html
// https://eli.thegreenplace.net/2009/10/30/handling-out-of-memory-conditions-in-c
abort();
}
[typeInvocation setArgument:&objCArg atIndex:2];
[typeInvocation invoke];
[typeInvocation getReturnValue:returnValue];
[inv setArgument:returnValue atIndex:index + 2];
free(returnValue);
return;
}
const char *BLOCK_TYPE = @encode(__typeof__(^{
}));
if (objCArgType == BLOCK_TYPE) {
/**
* RCTModuleMethod doesn't actually call into RCTConvert in this case.
*/
id arg = [objCArg copy];
if (arg) {
[retainedObjectsForInvocation addObject:arg];
}
[inv setArgument:&arg atIndex:(index) + 2];
return;
}
throw jsi::JSError(runtime, errorPrefix + "Objective C type " + [argumentType UTF8String] + " is unsupported.");
}
if ([argumentType isEqualToString:@"RCTResponseSenderBlock"]) {
if (!(jsiArg.isObject() && jsiArg.asObject(runtime).isFunction(runtime))) {
throw jsi::JSError(
runtime, errorPrefix + "JavaScript argument must be a function. Got " + getType(runtime, jsiArg));
}
RCTResponseSenderBlock arg =
(RCTResponseSenderBlock)TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_);
if (arg) {
[retainedObjectsForInvocation addObject:arg];
}
[inv setArgument:&arg atIndex:(index) + 2];
return;
}
if ([argumentType isEqualToString:@"RCTResponseErrorBlock"]) {
if (!(jsiArg.isObject() && jsiArg.asObject(runtime).isFunction(runtime))) {
throw jsi::JSError(
runtime, errorPrefix + "JavaScript argument must be a function. Got " + getType(runtime, jsiArg));
}
RCTResponseSenderBlock senderBlock =
(RCTResponseSenderBlock)TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_);
RCTResponseErrorBlock arg = ^(NSError *error) {
senderBlock(@[ RCTJSErrorFromNSError(error) ]);
};
[retainedObjectsForInvocation addObject:arg];
[inv setArgument:&arg atIndex:(index) + 2];
return;
}
if ([argumentType isEqualToString:@"RCTPromiseResolveBlock"] ||
[argumentType isEqualToString:@"RCTPromiseRejectBlock"]) {
throw jsi::JSError(
runtime,
errorPrefix + "The TurboModule interop layer should not convert JavaScript arguments to " +
[argumentType UTF8String] +
" inside ObjCinteropTurboModule::setInvocationArg(). Please report this as an issue.");
}
if ([argumentType hasPrefix:@"JS::"]) {
NSString *selectorNameForCxxType =
[[argumentType stringByReplacingOccurrencesOfString:@"::" withString:@"_"] stringByAppendingString:@":"];
selector = NSSelectorFromString(selectorNameForCxxType);
bool isPlainObject = jsiArg.isObject() && !jsiArg.asObject(runtime).isFunction(runtime) &&
!jsiArg.asObject(runtime).isArray(runtime);
if (!isPlainObject) {
throw jsi::JSError(
runtime, errorPrefix + "JavaScript argument must be a plain object. Got " + getType(runtime, jsiArg));
}
id arg = TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_);
RCTManagedPointer *(*convert)(id, SEL, id) = (__typeof__(convert))objc_msgSend;
RCTManagedPointer *box = convert([RCTCxxConvert class], selector, arg);
void *pointer = box.voidPointer;
[inv setArgument:&pointer atIndex:index + 2];
[retainedObjectsForInvocation addObject:box];
return;
}
}
jsi::Value ObjCInteropTurboModule::convertReturnIdToJSIValue(
jsi::Runtime &runtime,
const char *methodNameCStr,
TurboModuleMethodValueKind returnType,
id result)
{
std::string methodJsSignature = name_ + "." + methodNameCStr + "()";
std::string errorPrefix =
methodJsSignature + ": Error while converting return Objective C value to JavaScript type. ";
if (returnType == VoidKind) {
return jsi::Value::undefined();
}
if (result == (id)kCFNull || result == nil) {
return jsi::Value::null();
}
jsi::Value returnValue = TurboModuleConvertUtils::convertObjCObjectToJSIValue(runtime, result);
if (!returnValue.isUndefined()) {
return returnValue;
}
throw jsi::JSError(runtime, methodJsSignature + "Objective C type was unsupported.");
}
NSString *ObjCInteropTurboModule::getArgumentTypeName(jsi::Runtime &runtime, NSString *methodName, int argIndex)
{
const char *methodNameCStr = [methodName UTF8String];
std::string methodJsSignature = name_ + "." + methodNameCStr + "()";
std::string errorPrefix =
methodJsSignature + ": Error while trying to get Objective C type of parameter " + std::to_string(argIndex) + ".";
if (methodArgumentTypeNames_[methodName] == nil) {
throw jsi::JSError(runtime, errorPrefix + "No parameter types found for method.");
}
if ([methodArgumentTypeNames_[methodName] count] <= argIndex) {
size_t paramCount = [methodArgumentTypeNames_[methodName] count];
throw jsi::JSError(runtime, errorPrefix + "Method has only " + std::to_string(paramCount) + " parameter types.");
}
return methodArgumentTypeNames_[methodName][argIndex];
}
bool ObjCInteropTurboModule::exportsConstants()
{
for (size_t i = 0; i < methodDescriptors_.size(); i += 1) {
if (methodDescriptors_[i].methodName == "getConstants") {
return true;
}
}
return false;
}
const jsi::Value &ObjCInteropTurboModule::getConstants(jsi::Runtime &runtime)
{
if (!constantsCache_.isUndefined()) {
return constantsCache_;
}
if (!exportsConstants()) {
constantsCache_ = jsi::Object(runtime);
return constantsCache_;
}
jsi::Value getConstantsProp = get(runtime, jsi::PropNameID::forAscii(runtime, "getConstants"));
if (getConstantsProp.isObject()) {
jsi::Object getConstantsObj = getConstantsProp.asObject(runtime);
if (getConstantsObj.isFunction(runtime)) {
jsi::Function getConstantsFn = getConstantsObj.asFunction(runtime);
getConstantsFn.call(runtime);
return constantsCache_;
}
}
// Unable to invoke the getConstants() method.
// Maybe the module didn't define a getConstants() method.
// Default constants to {}, so no constants are spread into the NativeModule
constantsCache_ = jsi::Object(runtime);
return constantsCache_;
}
std::vector<facebook::jsi::PropNameID> ObjCInteropTurboModule::getPropertyNames(facebook::jsi::Runtime &runtime)
{
std::vector<facebook::jsi::PropNameID> propNames = ObjCTurboModule::getPropertyNames(runtime);
jsi::Object constants = getConstants(runtime).asObject(runtime);
jsi::Array constantNames = constants.getPropertyNames(runtime);
for (size_t i = 0; i < constantNames.size(runtime); i += 1) {
jsi::Value constantName = constantNames.getValueAtIndex(runtime, i);
if (constantName.isString()) {
propNames.push_back(jsi::PropNameID::forString(runtime, constantName.asString(runtime)));
}
}
return propNames;
}
} // namespace react
} // namespace facebook

View File

@@ -0,0 +1,30 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <ReactCommon/RuntimeExecutor.h>
#import <jsi/jsi.h>
NS_ASSUME_NONNULL_BEGIN
typedef void (^RCTJSIRuntimeHandlingBlock)(facebook::jsi::Runtime &runtime);
@interface RCTRuntimeExecutor : NSObject
- (instancetype)init NS_UNAVAILABLE;
/**
Initializes an object that wraps ways to access the RuntimeExecutor.
@param runtimeExecutor The instance of RuntimeExecutor.
*/
- (instancetype)initWithRuntimeExecutor:(facebook::react::RuntimeExecutor)runtimeExecutor NS_DESIGNATED_INITIALIZER;
- (void)execute:(RCTJSIRuntimeHandlingBlock)block;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,34 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTRuntimeExecutor.h"
@implementation RCTRuntimeExecutor {
facebook::react::RuntimeExecutor _runtimeExecutor;
}
#pragma mark - Initializer
- (instancetype)initWithRuntimeExecutor:(facebook::react::RuntimeExecutor)runtimeExecutor
{
if (self = [super init]) {
_runtimeExecutor = runtimeExecutor;
}
return self;
}
#pragma mark - Public API
- (void)execute:(RCTJSIRuntimeHandlingBlock)block
{
if (_runtimeExecutor) {
_runtimeExecutor([=](facebook::jsi::Runtime &runtime) { block(runtime); });
}
}
@end

View File

@@ -0,0 +1,173 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#import <memory>
#import <Foundation/Foundation.h>
#import <React/RCTBridge.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTModuleMethod.h>
#import <ReactCommon/CallInvoker.h>
#import <ReactCommon/TurboModule.h>
#import <string>
#import <unordered_map>
#define RCT_IS_TURBO_MODULE_CLASS(klass) \
((RCTTurboModuleEnabled() && [(klass) conformsToProtocol:@protocol(RCTTurboModule)]))
#define RCT_IS_TURBO_MODULE_INSTANCE(module) RCT_IS_TURBO_MODULE_CLASS([(module) class])
namespace facebook::react {
class CallbackWrapper;
class Instance;
namespace TurboModuleConvertUtils {
jsi::Value convertObjCObjectToJSIValue(jsi::Runtime &runtime, id value);
id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &value, std::shared_ptr<CallInvoker> jsInvoker);
}
/**
* ObjC++ specific TurboModule base class.
*/
class JSI_EXPORT ObjCTurboModule : public TurboModule {
public:
// TODO(T65603471): Should we unify this with a Fabric abstraction?
struct InitParams {
std::string moduleName;
id<RCTBridgeModule> instance;
std::shared_ptr<CallInvoker> jsInvoker;
std::shared_ptr<NativeMethodCallInvoker> nativeMethodCallInvoker;
bool isSyncModule;
bool shouldVoidMethodsExecuteSync;
};
ObjCTurboModule(const InitParams &params);
jsi::Value invokeObjCMethod(
jsi::Runtime &runtime,
TurboModuleMethodValueKind returnType,
const std::string &methodName,
SEL selector,
const jsi::Value *args,
size_t count);
id<RCTBridgeModule> instance_;
std::shared_ptr<NativeMethodCallInvoker> nativeMethodCallInvoker_;
protected:
void setMethodArgConversionSelector(NSString *methodName, size_t argIndex, NSString *fnName);
/**
* Why is this virtual?
*
* Purpose: Converts native module method returns from Objective C values to JavaScript values.
*
* ObjCTurboModule uses TurboModuleMethodValueKind to convert returns from Objective C values to JavaScript values.
* ObjCInteropTurboModule just blindly converts returns from Objective C values to JavaScript values by runtime type,
* because it cannot infer TurboModuleMethodValueKind from the RCT_EXPORT_METHOD annotations.
*/
virtual jsi::Value convertReturnIdToJSIValue(
jsi::Runtime &runtime,
const char *methodName,
TurboModuleMethodValueKind returnType,
id result);
/**
* Why is this virtual?
*
* Purpose: Get a native module method's argument's type, given the method name, and argument index.
*
* ObjCInteropTurboModule computes the argument type names eagerly on module init. So, make this method virtual. That
* way, ObjCInteropTurboModule doesn't end up computing the argument types twice: once on module init, and second on
* method dispatch.
*/
virtual NSString *getArgumentTypeName(jsi::Runtime &runtime, NSString *methodName, int argIndex);
/**
* Why is this virtual?
*
* Purpose: Convert arguments from JavaScript values to Objective C values. Assign the Objective C argument to the
* method invocation.
*
* ObjCInteropTurboModule relies heavily on RCTConvert to convert arguments from JavaScript values to Objective C
* values. ObjCTurboModule tries to minimize reliance on RCTConvert: RCTConvert uses the RCT_EXPORT_METHOD macros,
* which we want to remove long term from React Native.
*/
virtual void setInvocationArg(
jsi::Runtime &runtime,
const char *methodName,
const std::string &objCArgType,
const jsi::Value &arg,
size_t i,
NSInvocation *inv,
NSMutableArray *retainedObjectsForInvocation);
private:
// Does the NativeModule dispatch async methods to the JS thread?
const bool isSyncModule_;
// Should void methods execute synchronously?
const bool shouldVoidMethodsExecuteSync_;
/**
* TODO(ramanpreet):
* Investigate an optimization that'll let us get rid of this NSMutableDictionary.
* Perhaps, have the code-generated TurboModule subclass implement
* getMethodArgConversionSelector below.
*/
NSMutableDictionary<NSString *, NSMutableArray *> *methodArgConversionSelectors_;
NSDictionary<NSString *, NSArray<NSString *> *> *methodArgumentTypeNames_;
bool isMethodSync(TurboModuleMethodValueKind returnType);
BOOL hasMethodArgConversionSelector(NSString *methodName, size_t argIndex);
SEL getMethodArgConversionSelector(NSString *methodName, size_t argIndex);
NSInvocation *createMethodInvocation(
jsi::Runtime &runtime,
bool isSync,
const char *methodName,
SEL selector,
const jsi::Value *args,
size_t count,
NSMutableArray *retainedObjectsForInvocation);
id performMethodInvocation(
jsi::Runtime &runtime,
bool isSync,
const char *methodName,
NSInvocation *inv,
NSMutableArray *retainedObjectsForInvocation);
void performVoidMethodInvocation(
jsi::Runtime &runtime,
const char *methodName,
NSInvocation *inv,
NSMutableArray *retainedObjectsForInvocation);
using PromiseInvocationBlock = void (^)(RCTPromiseResolveBlock resolveWrapper, RCTPromiseRejectBlock rejectWrapper);
jsi::Value createPromise(jsi::Runtime &runtime, std::string methodName, PromiseInvocationBlock invoke);
};
} // namespace facebook::react
@protocol RCTTurboModule <NSObject>
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params;
@end
/**
* These methods are all implemented by RCTCxxBridge, which subclasses RCTBridge. Hence, they must only be used in
* contexts where the concrete class of an RCTBridge instance is RCTCxxBridge. This happens, for example, when
* [RCTCxxBridgeDelegate jsExecutorFactoryForBridge:(RCTBridge *)] is invoked by RCTCxxBridge.
*
* TODO: Consolidate this extension with the one in RCTSurfacePresenter.
*/
@interface RCTBridge (RCTTurboModule)
- (std::shared_ptr<facebook::react::CallInvoker>)jsCallInvoker;
- (std::shared_ptr<facebook::react::NativeMethodCallInvoker>)decorateNativeMethodCallInvoker:
(std::shared_ptr<facebook::react::NativeMethodCallInvoker>)nativeMethodCallInvoker;
@end

View File

@@ -0,0 +1,817 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTTurboModule.h"
#import <React/RCTBridgeModule.h>
#import <React/RCTConvert.h>
#import <React/RCTCxxConvert.h>
#import <React/RCTManagedPointer.h>
#import <React/RCTModuleMethod.h>
#import <React/RCTUtils.h>
#import <ReactCommon/CallInvoker.h>
#import <ReactCommon/TurboModule.h>
#import <ReactCommon/TurboModulePerfLogger.h>
#import <cxxreact/SystraceSection.h>
#import <react/bridging/Bridging.h>
#include <glog/logging.h>
#import <objc/message.h>
#import <objc/runtime.h>
#import <atomic>
#import <iostream>
#import <sstream>
#import <vector>
using namespace facebook;
using namespace facebook::react;
using namespace facebook::react::TurboModuleConvertUtils;
static int32_t getUniqueId()
{
static int32_t counter = 0;
return counter++;
}
namespace facebook {
namespace react {
namespace TurboModuleConvertUtils {
/**
* All static helper functions are ObjC++ specific.
*/
static jsi::Value convertNSNumberToJSIBoolean(jsi::Runtime &runtime, NSNumber *value)
{
return jsi::Value((bool)[value boolValue]);
}
static jsi::Value convertNSNumberToJSINumber(jsi::Runtime &runtime, NSNumber *value)
{
return jsi::Value([value doubleValue]);
}
static jsi::String convertNSStringToJSIString(jsi::Runtime &runtime, NSString *value)
{
return jsi::String::createFromUtf8(runtime, [value UTF8String] ?: "");
}
static jsi::Object convertNSDictionaryToJSIObject(jsi::Runtime &runtime, NSDictionary *value)
{
jsi::Object result = jsi::Object(runtime);
for (NSString *k in value) {
result.setProperty(runtime, convertNSStringToJSIString(runtime, k), convertObjCObjectToJSIValue(runtime, value[k]));
}
return result;
}
static jsi::Array convertNSArrayToJSIArray(jsi::Runtime &runtime, NSArray *value)
{
jsi::Array result = jsi::Array(runtime, value.count);
for (size_t i = 0; i < value.count; i++) {
result.setValueAtIndex(runtime, i, convertObjCObjectToJSIValue(runtime, value[i]));
}
return result;
}
static std::vector<jsi::Value> convertNSArrayToStdVector(jsi::Runtime &runtime, NSArray *value)
{
std::vector<jsi::Value> result;
for (size_t i = 0; i < value.count; i++) {
result.emplace_back(convertObjCObjectToJSIValue(runtime, value[i]));
}
return result;
}
jsi::Value convertObjCObjectToJSIValue(jsi::Runtime &runtime, id value)
{
if ([value isKindOfClass:[NSString class]]) {
return convertNSStringToJSIString(runtime, (NSString *)value);
} else if ([value isKindOfClass:[NSNumber class]]) {
if ([value isKindOfClass:[@YES class]]) {
return convertNSNumberToJSIBoolean(runtime, (NSNumber *)value);
}
return convertNSNumberToJSINumber(runtime, (NSNumber *)value);
} else if ([value isKindOfClass:[NSDictionary class]]) {
return convertNSDictionaryToJSIObject(runtime, (NSDictionary *)value);
} else if ([value isKindOfClass:[NSArray class]]) {
return convertNSArrayToJSIArray(runtime, (NSArray *)value);
} else if (value == (id)kCFNull) {
return jsi::Value::null();
}
return jsi::Value::undefined();
}
static NSString *convertJSIStringToNSString(jsi::Runtime &runtime, const jsi::String &value)
{
return [NSString stringWithUTF8String:value.utf8(runtime).c_str()];
}
static NSArray *
convertJSIArrayToNSArray(jsi::Runtime &runtime, const jsi::Array &value, std::shared_ptr<CallInvoker> jsInvoker)
{
size_t size = value.size(runtime);
NSMutableArray *result = [NSMutableArray new];
for (size_t i = 0; i < size; i++) {
// Insert kCFNull when it's `undefined` value to preserve the indices.
[result
addObject:convertJSIValueToObjCObject(runtime, value.getValueAtIndex(runtime, i), jsInvoker) ?: (id)kCFNull];
}
return [result copy];
}
static NSDictionary *
convertJSIObjectToNSDictionary(jsi::Runtime &runtime, const jsi::Object &value, std::shared_ptr<CallInvoker> jsInvoker)
{
jsi::Array propertyNames = value.getPropertyNames(runtime);
size_t size = propertyNames.size(runtime);
NSMutableDictionary *result = [NSMutableDictionary new];
for (size_t i = 0; i < size; i++) {
jsi::String name = propertyNames.getValueAtIndex(runtime, i).getString(runtime);
NSString *k = convertJSIStringToNSString(runtime, name);
id v = convertJSIValueToObjCObject(runtime, value.getProperty(runtime, name), jsInvoker);
if (v) {
result[k] = v;
}
}
return [result copy];
}
static RCTResponseSenderBlock
convertJSIFunctionToCallback(jsi::Runtime &rt, jsi::Function &&function, std::shared_ptr<CallInvoker> jsInvoker)
{
__block std::optional<AsyncCallback<>> callback({rt, std::move(function), std::move(jsInvoker)});
return ^(NSArray *args) {
if (!callback) {
LOG(FATAL) << "Callback arg cannot be called more than once";
return;
}
callback->call([args](jsi::Runtime &rt, jsi::Function &jsFunction) {
auto jsArgs = convertNSArrayToStdVector(rt, args);
jsFunction.call(rt, (const jsi::Value *)jsArgs.data(), jsArgs.size());
});
callback = std::nullopt;
};
}
id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &value, std::shared_ptr<CallInvoker> jsInvoker)
{
if (value.isUndefined() || value.isNull()) {
return nil;
}
if (value.isBool()) {
return @(value.getBool());
}
if (value.isNumber()) {
return @(value.getNumber());
}
if (value.isString()) {
return convertJSIStringToNSString(runtime, value.getString(runtime));
}
if (value.isObject()) {
jsi::Object o = value.getObject(runtime);
if (o.isArray(runtime)) {
return convertJSIArrayToNSArray(runtime, o.getArray(runtime), jsInvoker);
}
if (o.isFunction(runtime)) {
return convertJSIFunctionToCallback(runtime, o.getFunction(runtime), jsInvoker);
}
return convertJSIObjectToNSDictionary(runtime, o, jsInvoker);
}
throw std::runtime_error("Unsupported jsi::jsi::Value kind");
}
static jsi::Value createJSRuntimeError(jsi::Runtime &runtime, const std::string &message)
{
return runtime.global().getPropertyAsFunction(runtime, "Error").call(runtime, message);
}
/**
* Creates JSError with current JS runtime and NSException stack trace.
*/
static jsi::JSError convertNSExceptionToJSError(jsi::Runtime &runtime, NSException *exception)
{
std::string reason = [exception.reason UTF8String];
jsi::Object cause(runtime);
cause.setProperty(runtime, "name", [exception.name UTF8String]);
cause.setProperty(runtime, "message", reason);
cause.setProperty(runtime, "stackSymbols", convertNSArrayToJSIArray(runtime, exception.callStackSymbols));
cause.setProperty(
runtime, "stackReturnAddresses", convertNSArrayToJSIArray(runtime, exception.callStackReturnAddresses));
jsi::Value error = createJSRuntimeError(runtime, "Exception in HostFunction: " + reason);
error.asObject(runtime).setProperty(runtime, "cause", std::move(cause));
return {runtime, std::move(error)};
}
/**
* Creates JS error value with current JS runtime and error details.
*/
static jsi::Value convertJSErrorDetailsToJSRuntimeError(jsi::Runtime &runtime, NSDictionary *jsErrorDetails)
{
NSString *message = jsErrorDetails[@"message"];
auto jsError = createJSRuntimeError(runtime, [message UTF8String]);
for (NSString *key in jsErrorDetails) {
id value = jsErrorDetails[key];
jsError.asObject(runtime).setProperty(runtime, [key UTF8String], convertObjCObjectToJSIValue(runtime, value));
}
return jsError;
}
}
jsi::Value ObjCTurboModule::createPromise(jsi::Runtime &runtime, std::string methodName, PromiseInvocationBlock invoke)
{
if (!invoke) {
return jsi::Value::undefined();
}
jsi::Function Promise = runtime.global().getPropertyAsFunction(runtime, "Promise");
// Note: the passed invoke() block is not retained by default, so let's retain it here to help keep it longer.
// Otherwise, there's a risk of it getting released before the promise function below executes.
PromiseInvocationBlock invokeCopy = [invoke copy];
return Promise.callAsConstructor(
runtime,
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "fn"),
2,
[invokeCopy, jsInvoker = jsInvoker_, moduleName = name_, methodName](
jsi::Runtime &rt, const jsi::Value &thisVal, const jsi::Value *args, size_t count) {
std::string moduleMethod = moduleName + "." + methodName + "()";
if (count != 2) {
throw std::invalid_argument(
moduleMethod + ": Promise must pass constructor function two args. Passed " + std::to_string(count) +
" args.");
}
if (!invokeCopy) {
return jsi::Value::undefined();
}
__block BOOL resolveWasCalled = NO;
__block std::optional<AsyncCallback<>> resolve(
{rt, args[0].getObject(rt).getFunction(rt), std::move(jsInvoker)});
__block std::optional<AsyncCallback<>> reject(
{rt, args[1].getObject(rt).getFunction(rt), std::move(jsInvoker)});
RCTPromiseResolveBlock resolveBlock = ^(id result) {
if (!resolve || !reject) {
if (resolveWasCalled) {
RCTLogError(@"%s: Tried to resolve a promise more than once.", moduleMethod.c_str());
} else {
RCTLogError(
@"%s: Tried to resolve a promise after it's already been rejected.", moduleMethod.c_str());
}
return;
}
resolve->call([result](jsi::Runtime &rt, jsi::Function &jsFunction) {
jsFunction.call(rt, convertObjCObjectToJSIValue(rt, result));
});
resolveWasCalled = YES;
resolve = std::nullopt;
reject = std::nullopt;
};
RCTPromiseRejectBlock rejectBlock = ^(NSString *code, NSString *message, NSError *error) {
if (!resolve || !reject) {
if (resolveWasCalled) {
RCTLogError(@"%s: Tried to reject a promise after it's already been resolved.", moduleMethod.c_str());
} else {
RCTLogError(@"%s: Tried to reject a promise more than once.", moduleMethod.c_str());
}
return;
}
NSDictionary *jsErrorDetails = RCTJSErrorFromCodeMessageAndNSError(code, message, error);
reject->call([jsErrorDetails](jsi::Runtime &rt, jsi::Function &jsFunction) {
jsFunction.call(rt, convertJSErrorDetailsToJSRuntimeError(rt, jsErrorDetails));
});
resolveWasCalled = NO;
resolve = std::nullopt;
reject = std::nullopt;
};
invokeCopy(resolveBlock, rejectBlock);
return jsi::Value::undefined();
}));
}
/**
* Perform method invocation on a specific queue as configured by the module class.
* This serves as a backward-compatible support for RCTBridgeModule's methodQueue API.
*
* In the future:
* - This methodQueue support may be removed for simplicity and consistency with Android.
* - ObjC module methods will be always be called from JS thread.
* They may decide to dispatch to a different queue as needed.
*/
id ObjCTurboModule::performMethodInvocation(
jsi::Runtime &runtime,
bool isSync,
const char *methodName,
NSInvocation *inv,
NSMutableArray *retainedObjectsForInvocation)
{
__block id result;
__weak id<RCTBridgeModule> weakModule = instance_;
const char *moduleName = name_.c_str();
std::string methodNameStr{methodName};
__block int32_t asyncCallCounter = 0;
void (^block)() = ^{
id<RCTBridgeModule> strongModule = weakModule;
if (!strongModule) {
return;
}
if (isSync) {
TurboModulePerfLogger::syncMethodCallExecutionStart(moduleName, methodNameStr.c_str());
} else {
TurboModulePerfLogger::asyncMethodCallExecutionStart(moduleName, methodNameStr.c_str(), asyncCallCounter);
}
@try {
[inv invokeWithTarget:strongModule];
} @catch (NSException *exception) {
throw convertNSExceptionToJSError(runtime, exception);
} @finally {
[retainedObjectsForInvocation removeAllObjects];
}
if (!isSync) {
TurboModulePerfLogger::asyncMethodCallExecutionEnd(moduleName, methodNameStr.c_str(), asyncCallCounter);
return;
}
void *rawResult;
[inv getReturnValue:&rawResult];
result = (__bridge id)rawResult;
TurboModulePerfLogger::syncMethodCallExecutionEnd(moduleName, methodNameStr.c_str());
};
if (isSync) {
nativeMethodCallInvoker_->invokeSync(methodNameStr, [&]() -> void { block(); });
return result;
} else {
asyncCallCounter = getUniqueId();
TurboModulePerfLogger::asyncMethodCallDispatch(moduleName, methodName);
nativeMethodCallInvoker_->invokeAsync(methodNameStr, [block, moduleName, methodNameStr]() -> void {
SystraceSection s(
"RCTTurboModuleAsyncMethodInvocation",
"module",
moduleName,
"method",
methodNameStr,
"returnType",
"promise");
block();
});
return nil;
}
}
void ObjCTurboModule::performVoidMethodInvocation(
jsi::Runtime &runtime,
const char *methodName,
NSInvocation *inv,
NSMutableArray *retainedObjectsForInvocation)
{
__weak id<RCTBridgeModule> weakModule = instance_;
const char *moduleName = name_.c_str();
std::string methodNameStr{methodName};
__block int32_t asyncCallCounter = 0;
void (^block)() = ^{
id<RCTBridgeModule> strongModule = weakModule;
if (!strongModule) {
return;
}
if (shouldVoidMethodsExecuteSync_) {
TurboModulePerfLogger::syncMethodCallExecutionStart(moduleName, methodNameStr.c_str());
} else {
TurboModulePerfLogger::asyncMethodCallExecutionStart(moduleName, methodNameStr.c_str(), asyncCallCounter);
}
@try {
[inv invokeWithTarget:strongModule];
} @catch (NSException *exception) {
throw convertNSExceptionToJSError(runtime, exception);
} @finally {
[retainedObjectsForInvocation removeAllObjects];
}
if (shouldVoidMethodsExecuteSync_) {
TurboModulePerfLogger::syncMethodCallExecutionEnd(moduleName, methodNameStr.c_str());
} else {
TurboModulePerfLogger::asyncMethodCallExecutionEnd(moduleName, methodNameStr.c_str(), asyncCallCounter);
}
return;
};
if (shouldVoidMethodsExecuteSync_) {
nativeMethodCallInvoker_->invokeSync(methodNameStr, [&]() -> void { block(); });
} else {
asyncCallCounter = getUniqueId();
TurboModulePerfLogger::asyncMethodCallDispatch(moduleName, methodName);
nativeMethodCallInvoker_->invokeAsync(methodNameStr, [moduleName, methodNameStr, block]() -> void {
SystraceSection s(
"RCTTurboModuleAsyncMethodInvocation", "module", moduleName, "method", methodNameStr, "returnType", "void");
block();
});
}
}
jsi::Value ObjCTurboModule::convertReturnIdToJSIValue(
jsi::Runtime &runtime,
const char *methodName,
TurboModuleMethodValueKind returnType,
id result)
{
if (returnType == VoidKind) {
return jsi::Value::undefined();
}
if (result == (id)kCFNull || result == nil) {
return jsi::Value::null();
}
jsi::Value returnValue = jsi::Value::undefined();
// TODO: Re-use value conversion logic from existing impl, if possible.
switch (returnType) {
case VoidKind: {
break;
}
case BooleanKind: {
returnValue = convertNSNumberToJSIBoolean(runtime, (NSNumber *)result);
break;
}
case NumberKind: {
returnValue = convertNSNumberToJSINumber(runtime, (NSNumber *)result);
break;
}
case StringKind: {
returnValue = convertNSStringToJSIString(runtime, (NSString *)result);
break;
}
case ObjectKind: {
returnValue = convertNSDictionaryToJSIObject(runtime, (NSDictionary *)result);
break;
}
case ArrayKind: {
returnValue = convertNSArrayToJSIArray(runtime, (NSArray *)result);
break;
}
case FunctionKind:
throw std::runtime_error("convertReturnIdToJSIValue: FunctionKind is not supported yet.");
case PromiseKind:
throw std::runtime_error("convertReturnIdToJSIValue: PromiseKind wasn't handled properly.");
}
return returnValue;
}
/**
* Given a method name, and an argument index, return type of that argument.
* Prerequisite: You must wrap the method declaration inside some variant of the
* RCT_EXPORT_METHOD macro.
*
* This method returns nil if the method for which you're querying the argument type
* is not wrapped in an RCT_EXPORT_METHOD.
*
* Note: This is only being introduced for backward compatibility. It will be removed
* in the future.
*/
NSString *ObjCTurboModule::getArgumentTypeName(jsi::Runtime &runtime, NSString *methodName, int argIndex)
{
if (!methodArgumentTypeNames_) {
NSMutableDictionary<NSString *, NSArray<NSString *> *> *methodArgumentTypeNames = [NSMutableDictionary new];
unsigned int numberOfMethods;
Class cls = [instance_ class];
Method *methods = class_copyMethodList(object_getClass(cls), &numberOfMethods);
if (methods) {
for (unsigned int i = 0; i < numberOfMethods; i++) {
SEL s = method_getName(methods[i]);
NSString *mName = NSStringFromSelector(s);
if (![mName hasPrefix:@"__rct_export__"]) {
continue;
}
// Message dispatch logic from old infra
RCTMethodInfo *(*getMethodInfo)(id, SEL) = (__typeof__(getMethodInfo))objc_msgSend;
RCTMethodInfo *methodInfo = getMethodInfo(cls, s);
NSArray<RCTMethodArgument *> *arguments;
NSString *otherMethodName = RCTParseMethodSignature(methodInfo->objcName, &arguments);
NSMutableArray *argumentTypes = [NSMutableArray arrayWithCapacity:[arguments count]];
for (int j = 0; j < [arguments count]; j += 1) {
[argumentTypes addObject:arguments[j].type];
}
NSString *normalizedOtherMethodName = [otherMethodName componentsSeparatedByString:@":"][0];
methodArgumentTypeNames[normalizedOtherMethodName] = argumentTypes;
}
free(methods);
}
methodArgumentTypeNames_ = methodArgumentTypeNames;
}
if (methodArgumentTypeNames_[methodName]) {
assert([methodArgumentTypeNames_[methodName] count] > argIndex);
return methodArgumentTypeNames_[methodName][argIndex];
}
return nil;
}
void ObjCTurboModule::setInvocationArg(
jsi::Runtime &runtime,
const char *methodName,
const std::string &objCArgType,
const jsi::Value &arg,
size_t i,
NSInvocation *inv,
NSMutableArray *retainedObjectsForInvocation)
{
if (arg.isBool()) {
bool v = arg.getBool();
/**
* JS type checking ensures the Objective C argument here is either a BOOL or NSNumber*.
*/
if (objCArgType == @encode(id)) {
id objCArg = [NSNumber numberWithBool:v];
[inv setArgument:(void *)&objCArg atIndex:i + 2];
[retainedObjectsForInvocation addObject:objCArg];
} else {
[inv setArgument:(void *)&v atIndex:i + 2];
}
return;
}
if (arg.isNumber()) {
double v = arg.getNumber();
/**
* JS type checking ensures the Objective C argument here is either a double or NSNumber*.
*/
if (objCArgType == @encode(id)) {
id objCArg = [NSNumber numberWithDouble:v];
[inv setArgument:(void *)&objCArg atIndex:i + 2];
[retainedObjectsForInvocation addObject:objCArg];
} else {
[inv setArgument:(void *)&v atIndex:i + 2];
}
return;
}
/**
* Convert arg to ObjC objects.
*/
id objCArg = convertJSIValueToObjCObject(runtime, arg, jsInvoker_);
if (objCArg) {
NSString *methodNameNSString = @(methodName);
/**
* Convert objects using RCTConvert.
*/
if (objCArgType == @encode(id)) {
NSString *argumentType = getArgumentTypeName(runtime, methodNameNSString, static_cast<int>(i));
if (argumentType != nil) {
NSString *rctConvertMethodName = [NSString stringWithFormat:@"%@:", argumentType];
SEL rctConvertSelector = NSSelectorFromString(rctConvertMethodName);
if ([RCTConvert respondsToSelector:rctConvertSelector]) {
// Message dispatch logic from old infra
id (*convert)(id, SEL, id) = (__typeof__(convert))objc_msgSend;
id convertedObjCArg = convert([RCTConvert class], rctConvertSelector, objCArg);
[inv setArgument:(void *)&convertedObjCArg atIndex:i + 2];
if (convertedObjCArg) {
[retainedObjectsForInvocation addObject:convertedObjCArg];
}
return;
}
}
}
/**
* Convert objects using RCTCxxConvert to structs.
*/
if ([objCArg isKindOfClass:[NSDictionary class]] && hasMethodArgConversionSelector(methodNameNSString, i)) {
SEL methodArgConversionSelector = getMethodArgConversionSelector(methodNameNSString, i);
// Message dispatch logic from old infra (link:
// https://github.com/facebook/react-native/commit/6783694158057662fd7b11fc123c339b2b21bfe6#diff-263fc157dfce55895cdc16495b55d190R350)
RCTManagedPointer *(*convert)(id, SEL, id) = (__typeof__(convert))objc_msgSend;
RCTManagedPointer *box = convert([RCTCxxConvert class], methodArgConversionSelector, objCArg);
void *pointer = box.voidPointer;
[inv setArgument:&pointer atIndex:i + 2];
[retainedObjectsForInvocation addObject:box];
return;
}
}
/**
* Insert converted args unmodified.
*/
[inv setArgument:(void *)&objCArg atIndex:i + 2];
if (objCArg) {
[retainedObjectsForInvocation addObject:objCArg];
}
}
NSInvocation *ObjCTurboModule::createMethodInvocation(
jsi::Runtime &runtime,
bool isSync,
const char *methodName,
SEL selector,
const jsi::Value *args,
size_t count,
NSMutableArray *retainedObjectsForInvocation)
{
const char *moduleName = name_.c_str();
const id<RCTBridgeModule> module = instance_;
if (isSync) {
TurboModulePerfLogger::syncMethodCallArgConversionStart(moduleName, methodName);
} else {
TurboModulePerfLogger::asyncMethodCallArgConversionStart(moduleName, methodName);
}
NSInvocation *inv =
[NSInvocation invocationWithMethodSignature:[[module class] instanceMethodSignatureForSelector:selector]];
[inv setSelector:selector];
NSMethodSignature *methodSignature = [[module class] instanceMethodSignatureForSelector:selector];
for (size_t i = 0; i < count; i++) {
const jsi::Value &arg = args[i];
const std::string objCArgType = [methodSignature getArgumentTypeAtIndex:i + 2];
setInvocationArg(runtime, methodName, objCArgType, arg, i, inv, retainedObjectsForInvocation);
}
if (isSync) {
TurboModulePerfLogger::syncMethodCallArgConversionEnd(moduleName, methodName);
} else {
TurboModulePerfLogger::asyncMethodCallArgConversionEnd(moduleName, methodName);
}
return inv;
}
bool ObjCTurboModule::isMethodSync(TurboModuleMethodValueKind returnType)
{
if (isSyncModule_) {
return true;
}
if (returnType == VoidKind && shouldVoidMethodsExecuteSync_) {
return true;
}
return !(returnType == VoidKind || returnType == PromiseKind);
}
ObjCTurboModule::ObjCTurboModule(const InitParams &params)
: TurboModule(params.moduleName, params.jsInvoker),
instance_(params.instance),
nativeMethodCallInvoker_(params.nativeMethodCallInvoker),
isSyncModule_(params.isSyncModule),
shouldVoidMethodsExecuteSync_(params.shouldVoidMethodsExecuteSync)
{
}
jsi::Value ObjCTurboModule::invokeObjCMethod(
jsi::Runtime &runtime,
TurboModuleMethodValueKind returnType,
const std::string &methodNameStr,
SEL selector,
const jsi::Value *args,
size_t count)
{
const char *moduleName = name_.c_str();
const char *methodName = methodNameStr.c_str();
bool isSyncInvocation = isMethodSync(returnType);
if (isSyncInvocation) {
TurboModulePerfLogger::syncMethodCallStart(moduleName, methodName);
} else {
TurboModulePerfLogger::asyncMethodCallStart(moduleName, methodName);
}
NSMutableArray *retainedObjectsForInvocation = [NSMutableArray arrayWithCapacity:count + 2];
NSInvocation *inv = createMethodInvocation(
runtime, isSyncInvocation, methodName, selector, args, count, retainedObjectsForInvocation);
jsi::Value returnValue = jsi::Value::undefined();
switch (returnType) {
case PromiseKind: {
returnValue = createPromise(
runtime, methodNameStr, ^(RCTPromiseResolveBlock resolveBlock, RCTPromiseRejectBlock rejectBlock) {
RCTPromiseResolveBlock resolveCopy = [resolveBlock copy];
RCTPromiseRejectBlock rejectCopy = [rejectBlock copy];
[inv setArgument:(void *)&resolveCopy atIndex:count + 2];
[inv setArgument:(void *)&rejectCopy atIndex:count + 3];
[retainedObjectsForInvocation addObject:resolveCopy];
[retainedObjectsForInvocation addObject:rejectCopy];
// The return type becomes void in the ObjC side.
performMethodInvocation(runtime, isSyncInvocation, methodName, inv, retainedObjectsForInvocation);
});
break;
}
case VoidKind: {
performVoidMethodInvocation(runtime, methodName, inv, retainedObjectsForInvocation);
if (isSyncInvocation) {
TurboModulePerfLogger::syncMethodCallReturnConversionStart(moduleName, methodName);
}
returnValue = jsi::Value::undefined();
if (isSyncInvocation) {
TurboModulePerfLogger::syncMethodCallReturnConversionEnd(moduleName, methodName);
}
break;
}
case BooleanKind:
case NumberKind:
case StringKind:
case ObjectKind:
case ArrayKind:
case FunctionKind: {
id result = performMethodInvocation(runtime, true, methodName, inv, retainedObjectsForInvocation);
TurboModulePerfLogger::syncMethodCallReturnConversionStart(moduleName, methodName);
returnValue = convertReturnIdToJSIValue(runtime, methodName, returnType, result);
TurboModulePerfLogger::syncMethodCallReturnConversionEnd(moduleName, methodName);
} break;
}
if (isSyncInvocation) {
TurboModulePerfLogger::syncMethodCallEnd(moduleName, methodName);
} else {
TurboModulePerfLogger::asyncMethodCallEnd(moduleName, methodName);
}
return returnValue;
}
BOOL ObjCTurboModule::hasMethodArgConversionSelector(NSString *methodName, size_t argIndex)
{
return methodArgConversionSelectors_ && methodArgConversionSelectors_[methodName] &&
![methodArgConversionSelectors_[methodName][argIndex] isEqual:[NSNull null]];
}
SEL ObjCTurboModule::getMethodArgConversionSelector(NSString *methodName, size_t argIndex)
{
assert(hasMethodArgConversionSelector(methodName, argIndex));
return (SEL)((NSValue *)methodArgConversionSelectors_[methodName][argIndex]).pointerValue;
}
void ObjCTurboModule::setMethodArgConversionSelector(NSString *methodName, size_t argIndex, NSString *fnName)
{
if (!methodArgConversionSelectors_) {
methodArgConversionSelectors_ = [NSMutableDictionary new];
}
if (!methodArgConversionSelectors_[methodName]) {
auto metaData = methodMap_.at([methodName UTF8String]);
auto argCount = metaData.argCount;
methodArgConversionSelectors_[methodName] = [NSMutableArray arrayWithCapacity:argCount];
for (int i = 0; i < argCount; i += 1) {
[methodArgConversionSelectors_[methodName] addObject:[NSNull null]];
}
}
SEL selector = NSSelectorFromString(fnName);
NSValue *selectorValue = [NSValue valueWithPointer:selector];
methodArgConversionSelectors_[methodName][argIndex] = selectorValue;
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,81 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#import <memory>
#import <React/RCTBridgeModuleDecorator.h>
#import <React/RCTBridgeProxy.h>
#import <React/RCTDefines.h>
#import <React/RCTTurboModuleRegistry.h>
#import <ReactCommon/RuntimeExecutor.h>
#import <ReactCommon/TurboModuleBinding.h>
#import "RCTTurboModule.h"
@class RCTTurboModuleManager;
@protocol RCTTurboModuleManagerDelegate <NSObject>
/**
* Given a module name, return its actual class. If nil is returned, basic ObjC class lookup is performed.
*/
- (Class)getModuleClassFromName:(const char *)name;
/**
* Given a module class, provide an instance for it. If nil is returned, default initializer is used.
*/
- (id<RCTTurboModule>)getModuleInstanceFromClass:(Class)moduleClass;
@optional
/**
* Create an instance of a TurboModule without relying on any ObjC++ module instance.
*/
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const std::string &)name
jsInvoker:
(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker;
/**
* Return a pre-initialized list of leagcy native modules.
* These modules shouldn't be TurboModule-compatible (i.e: they should not conform to RCTTurboModule).
*
* This method is only used by the TurboModule interop layer.
*
* It must match the signature of RCTBridgeDelegate extraModulesForBridge:
* - (NSArray<id<RCTBridgeModule>> *)extraModulesForBridge:(RCTBridge *)bridge;
*/
- (NSArray<id<RCTBridgeModule>> *)extraModulesForBridge:(RCTBridge *)bridge
__attribute((deprecated("Please make all native modules returned from this method TurboModule-compatible.")));
@end
@protocol RCTTurboModuleManagerRuntimeHandler <NSObject>
- (facebook::react::RuntimeExecutor)runtimeExecutorForTurboModuleManager:(RCTTurboModuleManager *)turboModuleManager;
@end
@interface RCTTurboModuleManager : NSObject <RCTTurboModuleRegistry>
- (instancetype)initWithBridge:(RCTBridge *)bridge
delegate:(id<RCTTurboModuleManagerDelegate>)delegate
jsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker;
- (instancetype)initWithBridgeProxy:(RCTBridgeProxy *)bridgeProxy
bridgeModuleDecorator:(RCTBridgeModuleDecorator *)bridgeModuleDecorator
delegate:(id<RCTTurboModuleManagerDelegate>)delegate
jsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker;
- (void)installJSBindings:(facebook::jsi::Runtime &)runtime;
- (void)invalidate;
@property (nonatomic, weak, readwrite) id<RCTTurboModuleManagerRuntimeHandler> runtimeHandler;
@end

View File

@@ -0,0 +1,88 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<2466af777a1d69da2c3810e5a2d53a70>>
*/
/**
* IMPORTANT: Do NOT modify this file directly.
*
* To change the definition of the flags, edit
* packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js.
*
* To regenerate this code, run the following script from the repo root:
* yarn featureflags-update
*/
#include "NativeReactNativeFeatureFlags.h"
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include "Plugins.h"
std::shared_ptr<facebook::react::TurboModule>
NativeReactNativeFeatureFlagsModuleProvider(
std::shared_ptr<facebook::react::CallInvoker> jsInvoker) {
return std::make_shared<facebook::react::NativeReactNativeFeatureFlags>(
std::move(jsInvoker));
}
namespace facebook::react {
NativeReactNativeFeatureFlags::NativeReactNativeFeatureFlags(
std::shared_ptr<CallInvoker> jsInvoker)
: NativeReactNativeFeatureFlagsCxxSpec(std::move(jsInvoker)) {}
bool NativeReactNativeFeatureFlags::commonTestFlag(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::commonTestFlag();
}
bool NativeReactNativeFeatureFlags::enableBackgroundExecutor(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableBackgroundExecutor();
}
bool NativeReactNativeFeatureFlags::useModernRuntimeScheduler(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::useModernRuntimeScheduler();
}
bool NativeReactNativeFeatureFlags::enableMicrotasks(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableMicrotasks();
}
bool NativeReactNativeFeatureFlags::batchRenderingUpdatesInEventLoop(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::batchRenderingUpdatesInEventLoop();
}
bool NativeReactNativeFeatureFlags::enableSpannableBuildingUnification(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableSpannableBuildingUnification();
}
bool NativeReactNativeFeatureFlags::enableCustomDrawOrderFabric(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableCustomDrawOrderFabric();
}
bool NativeReactNativeFeatureFlags::enableFixForClippedSubviewsCrash(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableFixForClippedSubviewsCrash();
}
bool NativeReactNativeFeatureFlags::inspectorEnableCxxInspectorPackagerConnection(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::inspectorEnableCxxInspectorPackagerConnection();
}
bool NativeReactNativeFeatureFlags::inspectorEnableModernCDPRegistry(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::inspectorEnableModernCDPRegistry();
}
} // namespace facebook::react

View File

@@ -0,0 +1,54 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<30481dc57cc7f389d1ed87d097caedda>>
*/
/**
* IMPORTANT: Do NOT modify this file directly.
*
* To change the definition of the flags, edit
* packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js.
*
* To regenerate this code, run the following script from the repo root:
* yarn featureflags-update
*/
#pragma once
#include <FBReactNativeSpec/FBReactNativeSpecJSI.h>
namespace facebook::react {
class NativeReactNativeFeatureFlags
: public NativeReactNativeFeatureFlagsCxxSpec<
NativeReactNativeFeatureFlags>,
std::enable_shared_from_this<NativeReactNativeFeatureFlags> {
public:
NativeReactNativeFeatureFlags(std::shared_ptr<CallInvoker> jsInvoker);
bool commonTestFlag(jsi::Runtime& runtime);
bool enableBackgroundExecutor(jsi::Runtime& runtime);
bool useModernRuntimeScheduler(jsi::Runtime& runtime);
bool enableMicrotasks(jsi::Runtime& runtime);
bool batchRenderingUpdatesInEventLoop(jsi::Runtime& runtime);
bool enableSpannableBuildingUnification(jsi::Runtime& runtime);
bool enableCustomDrawOrderFabric(jsi::Runtime& runtime);
bool enableFixForClippedSubviewsCrash(jsi::Runtime& runtime);
bool inspectorEnableCxxInspectorPackagerConnection(jsi::Runtime& runtime);
bool inspectorEnableModernCDPRegistry(jsi::Runtime& runtime);
};
} // namespace facebook::react

View File

@@ -0,0 +1,76 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
require "json"
package = JSON.parse(File.read(File.join(__dir__, "..", "..", "..", "..", "package.json")))
version = package['version']
source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which were presumably in.
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
else
source[:tag] = "v#{version}"
end
folly_config = get_folly_config()
folly_compiler_flags = folly_config[:compiler_flags]
folly_version = folly_config[:version]
boost_compiler_flags = '-Wno-documentation'
using_hermes = ENV['USE_HERMES'] == nil || ENV['USE_HERMES'] == "1"
header_search_paths = [
"\"$(PODS_ROOT)/boost\"",
"\"$(PODS_ROOT)/RCT-Folly\"",
"\"$(PODS_ROOT)/DoubleConversion\"",
"\"$(PODS_ROOT)/fmt/include\"",
"\"$(PODS_ROOT)/Headers/Private/React-Core\"",
]
create_header_search_path_for_frameworks("ReactCommon-Samples").each { |search_path| header_search_paths << "\"#{search_path}\""}
Pod::Spec.new do |s|
s.name = "ReactCommon-Samples"
s.module_name = "ReactCommon_Samples"
s.header_dir = "ReactCommon"
s.version = version
s.summary = "-" # TODO
s.homepage = "https://reactnative.dev/"
s.license = package["license"]
s.author = "Meta Platforms, Inc. and its affiliates"
s.platforms = min_supported_versions
s.source = source
s.compiler_flags = folly_compiler_flags + ' ' + boost_compiler_flags
s.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => header_search_paths,
"USE_HEADERMAP" => "YES",
"CLANG_CXX_LANGUAGE_STANDARD" => "c++20",
"GCC_WARN_PEDANTIC" => "YES" }
if ENV['USE_FRAMEWORKS']
s.header_mappings_dir = './'
end
s.source_files = "ReactCommon/**/*.{cpp,h}",
"platform/ios/**/*.{mm,cpp,h}"
s.dependency "RCT-Folly"
s.dependency "DoubleConversion"
s.dependency "fmt", "9.1.0"
s.dependency "React-Core"
s.dependency "React-cxxreact"
s.dependency "React-jsi"
add_dependency(s, "React-Codegen", :additional_framework_paths => ["build/generated/ios"])
add_dependency(s, "ReactCommon", :subspec => "turbomodule/core", :additional_framework_paths => ["react/nativemodule/core"])
add_dependency(s, "React-NativeModulesApple", :additional_framework_paths => ["build/generated/ios"])
if using_hermes
s.dependency "hermes-engine"
else
s.dependency "React-jsc"
end
end

View File

@@ -0,0 +1,151 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "NativeSampleTurboCxxModuleSpecJSI.h"
// NOTE: This entire file should be codegen'ed.
namespace facebook::react {
static jsi::Value __hostFunction_NativeSampleTurboCxxModuleSpecJSI_voidFunc(
jsi::Runtime& rt,
TurboModule& turboModule,
const jsi::Value* args,
size_t count) {
static_cast<NativeSampleTurboCxxModuleSpecJSI*>(&turboModule)->voidFunc(rt);
return jsi::Value::undefined();
}
static jsi::Value __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getBool(
jsi::Runtime& rt,
TurboModule& turboModule,
const jsi::Value* args,
size_t count) {
return jsi::Value(
static_cast<NativeSampleTurboCxxModuleSpecJSI*>(&turboModule)
->getBool(rt, args[0].getBool()));
}
static jsi::Value __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getEnum(
jsi::Runtime& rt,
TurboModule& turboModule,
const jsi::Value* args,
size_t count) {
return jsi::Value(
static_cast<NativeSampleTurboCxxModuleSpecJSI*>(&turboModule)
->getEnum(rt, args[0].getNumber()));
}
static jsi::Value __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getNumber(
jsi::Runtime& rt,
TurboModule& turboModule,
const jsi::Value* args,
size_t count) {
return jsi::Value(
static_cast<NativeSampleTurboCxxModuleSpecJSI*>(&turboModule)
->getNumber(rt, args[0].getNumber()));
}
static jsi::Value __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getString(
jsi::Runtime& rt,
TurboModule& turboModule,
const jsi::Value* args,
size_t count) {
return static_cast<NativeSampleTurboCxxModuleSpecJSI*>(&turboModule)
->getString(rt, args[0].getString(rt));
}
static jsi::Value __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getArray(
jsi::Runtime& rt,
TurboModule& turboModule,
const jsi::Value* args,
size_t count) {
return static_cast<NativeSampleTurboCxxModuleSpecJSI*>(&turboModule)
->getArray(rt, args[0].getObject(rt).getArray(rt));
}
static jsi::Value __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getObject(
jsi::Runtime& rt,
TurboModule& turboModule,
const jsi::Value* args,
size_t count) {
return static_cast<NativeSampleTurboCxxModuleSpecJSI*>(&turboModule)
->getObject(rt, args[0].getObject(rt));
}
static jsi::Value __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getValue(
jsi::Runtime& rt,
TurboModule& turboModule,
const jsi::Value* args,
size_t count) {
return static_cast<NativeSampleTurboCxxModuleSpecJSI*>(&turboModule)
->getValue(
rt,
args[0].getNumber(),
args[1].getString(rt),
args[2].getObject(rt));
}
static jsi::Value
__hostFunction_NativeSampleTurboCxxModuleSpecJSI_getValueWithCallback(
jsi::Runtime& rt,
TurboModule& turboModule,
const jsi::Value* args,
size_t count) {
static_cast<NativeSampleTurboCxxModuleSpecJSI*>(&turboModule)
->getValueWithCallback(
rt, std::move(args[0].getObject(rt).getFunction(rt)));
return jsi::Value::undefined();
}
static jsi::Value
__hostFunction_NativeSampleTurboCxxModuleSpecJSI_getValueWithPromise(
jsi::Runtime& rt,
TurboModule& turboModule,
const jsi::Value* args,
size_t count) {
return static_cast<NativeSampleTurboCxxModuleSpecJSI*>(&turboModule)
->getValueWithPromise(rt, args[0].getBool());
}
static jsi::Value __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getConstants(
jsi::Runtime& rt,
TurboModule& turboModule,
const jsi::Value* args,
size_t count) {
return static_cast<NativeSampleTurboCxxModuleSpecJSI*>(&turboModule)
->getConstants(rt);
}
NativeSampleTurboCxxModuleSpecJSI::NativeSampleTurboCxxModuleSpecJSI(
std::shared_ptr<CallInvoker> jsInvoker)
: TurboModule("SampleTurboCxxModule", jsInvoker) {
methodMap_["voidFunc"] = MethodMetadata{
0, __hostFunction_NativeSampleTurboCxxModuleSpecJSI_voidFunc};
methodMap_["getBool"] = MethodMetadata{
1, __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getBool};
methodMap_["getEnum"] = MethodMetadata{
1, __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getEnum};
methodMap_["getNumber"] = MethodMetadata{
1, __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getNumber};
methodMap_["getString"] = MethodMetadata{
1, __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getString};
methodMap_["getArray"] = MethodMetadata{
1, __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getArray};
methodMap_["getObject"] = MethodMetadata{
1, __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getObject};
methodMap_["getValue"] = MethodMetadata{
3, __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getValue};
methodMap_["getValueWithCallback"] = MethodMetadata{
1, __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getValueWithCallback};
methodMap_["getValueWithPromise"] = MethodMetadata{
1, __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getValueWithPromise};
methodMap_["getConstants"] = MethodMetadata{
0, __hostFunction_NativeSampleTurboCxxModuleSpecJSI_getConstants};
}
} // namespace facebook::react

View File

@@ -0,0 +1,42 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <memory>
#include <string>
#include <ReactCommon/TurboModule.h>
namespace facebook::react {
// TODO: This definition should be codegen'ed for type-safety purpose.
class JSI_EXPORT NativeSampleTurboCxxModuleSpecJSI : public TurboModule {
protected:
NativeSampleTurboCxxModuleSpecJSI(std::shared_ptr<CallInvoker> jsInvoker);
public:
virtual void voidFunc(jsi::Runtime& rt) = 0;
virtual bool getBool(jsi::Runtime& rt, bool arg) = 0;
virtual double getEnum(jsi::Runtime& rt, double arg) = 0;
virtual double getNumber(jsi::Runtime& rt, double arg) = 0;
virtual jsi::String getString(jsi::Runtime& rt, const jsi::String& arg) = 0;
virtual jsi::Array getArray(jsi::Runtime& rt, const jsi::Array& arg) = 0;
virtual jsi::Object getObject(jsi::Runtime& rt, const jsi::Object& arg) = 0;
virtual jsi::Object getValue(
jsi::Runtime& rt,
double x,
const jsi::String& y,
const jsi::Object& z) = 0;
virtual void getValueWithCallback(
jsi::Runtime& rt,
const jsi::Function& callback) = 0;
virtual jsi::Value getValueWithPromise(jsi::Runtime& rt, bool error) = 0;
virtual jsi::Object getConstants(jsi::Runtime& rt) = 0;
};
} // namespace facebook::react

View File

@@ -0,0 +1,96 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "SampleTurboCxxModule.h"
#include <ReactCommon/TurboModuleUtils.h>
using namespace facebook;
namespace facebook::react {
SampleTurboCxxModule::SampleTurboCxxModule(
std::shared_ptr<CallInvoker> jsInvoker)
: NativeSampleTurboCxxModuleSpecJSI(jsInvoker) {}
void SampleTurboCxxModule::voidFunc(jsi::Runtime& rt) {
// Nothing to do
}
bool SampleTurboCxxModule::getBool(jsi::Runtime& rt, bool arg) {
return arg;
}
double SampleTurboCxxModule::getEnum(jsi::Runtime& rt, double arg) {
return arg;
}
double SampleTurboCxxModule::getNumber(jsi::Runtime& rt, double arg) {
return arg;
}
jsi::String SampleTurboCxxModule::getString(
jsi::Runtime& rt,
const jsi::String& arg) {
return jsi::String::createFromUtf8(rt, arg.utf8(rt));
}
jsi::Array SampleTurboCxxModule::getArray(
jsi::Runtime& rt,
const jsi::Array& arg) {
return deepCopyJSIArray(rt, arg);
}
jsi::Object SampleTurboCxxModule::getObject(
jsi::Runtime& rt,
const jsi::Object& arg) {
return deepCopyJSIObject(rt, arg);
}
jsi::Object SampleTurboCxxModule::getValue(
jsi::Runtime& rt,
double x,
const jsi::String& y,
const jsi::Object& z) {
// Note: return type isn't type-safe.
jsi::Object result(rt);
result.setProperty(rt, "x", jsi::Value(x));
result.setProperty(rt, "y", jsi::String::createFromUtf8(rt, y.utf8(rt)));
result.setProperty(rt, "z", deepCopyJSIObject(rt, z));
return result;
}
void SampleTurboCxxModule::getValueWithCallback(
jsi::Runtime& rt,
const jsi::Function& callback) {
callback.call(rt, jsi::String::createFromUtf8(rt, "value from callback!"));
}
jsi::Value SampleTurboCxxModule::getValueWithPromise(
jsi::Runtime& rt,
bool error) {
return createPromiseAsJSIValue(
rt, [error](jsi::Runtime& rt2, std::shared_ptr<Promise> promise) {
if (error) {
promise->reject("intentional promise rejection");
} else {
promise->resolve(jsi::String::createFromUtf8(rt2, "result!"));
}
});
}
jsi::Object SampleTurboCxxModule::getConstants(jsi::Runtime& rt) {
// Note: return type isn't type-safe.
jsi::Object result(rt);
result.setProperty(rt, "const1", jsi::Value(true));
result.setProperty(rt, "const2", jsi::Value(375));
result.setProperty(
rt, "const3", jsi::String::createFromUtf8(rt, "something"));
return result;
}
} // namespace facebook::react

View File

@@ -0,0 +1,43 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <memory>
#include "NativeSampleTurboCxxModuleSpecJSI.h"
namespace facebook::react {
/**
* A sample implementation of the C++ spec. In practice, this class can just
* extend jsi::HostObject directly, but using the spec provides build-time
* type-safety.
*/
class SampleTurboCxxModule : public NativeSampleTurboCxxModuleSpecJSI {
public:
SampleTurboCxxModule(std::shared_ptr<CallInvoker> jsInvoker);
void voidFunc(jsi::Runtime& rt) override;
bool getBool(jsi::Runtime& rt, bool arg) override;
double getEnum(jsi::Runtime& rt, double arg) override;
double getNumber(jsi::Runtime& rt, double arg) override;
jsi::String getString(jsi::Runtime& rt, const jsi::String& arg) override;
jsi::Array getArray(jsi::Runtime& rt, const jsi::Array& arg) override;
jsi::Object getObject(jsi::Runtime& rt, const jsi::Object& arg) override;
jsi::Object getValue(
jsi::Runtime& rt,
double x,
const jsi::String& y,
const jsi::Object& z) override;
void getValueWithCallback(jsi::Runtime& rt, const jsi::Function& callback)
override;
jsi::Value getValueWithPromise(jsi::Runtime& rt, bool error) override;
jsi::Object getConstants(jsi::Runtime& rt) override;
};
} // namespace facebook::react

View File

@@ -0,0 +1,26 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
add_compile_options(
-fexceptions
-frtti
-std=c++20
-Wall
-Wpedantic
-DFOLLY_NO_CONFIG=1
-DLOG_TAG=\"ReactNative\")
file(GLOB sampleturbomodule_SRC CONFIGURE_DEPENDS ReactCommon/*.cpp)
add_library(sampleturbomodule STATIC ${sampleturbomodule_SRC})
target_include_directories(sampleturbomodule PUBLIC .)
target_link_libraries(sampleturbomodule
fbjni
jsi
react_nativemodule_core)

View File

@@ -0,0 +1,114 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
// NOTE: This entire file should be codegen'ed.
package com.facebook.fbreact.specs;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.build.ReactBuildConfig;
import com.facebook.react.turbomodule.core.interfaces.TurboModule;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
public abstract class NativeSampleTurboModuleSpec extends ReactContextBaseJavaModule
implements TurboModule {
public NativeSampleTurboModuleSpec(ReactApplicationContext reactContext) {
super(reactContext);
}
@ReactMethod(isBlockingSynchronousMethod = true)
public abstract double getNumber(double arg);
@ReactMethod(isBlockingSynchronousMethod = true)
public abstract WritableMap getValue(double x, String y, ReadableMap z);
@ReactMethod(isBlockingSynchronousMethod = true)
public abstract WritableMap getObject(ReadableMap arg);
@ReactMethod(isBlockingSynchronousMethod = true)
public abstract WritableMap getUnsafeObject(ReadableMap arg);
@ReactMethod
public abstract void voidFunc();
@ReactMethod(isBlockingSynchronousMethod = true)
public abstract WritableArray getArray(ReadableArray arg);
@ReactMethod
public abstract void getValueWithPromise(boolean error, Promise promise);
@ReactMethod
public abstract void getValueWithCallback(Callback callback);
@ReactMethod(isBlockingSynchronousMethod = true)
public abstract String getString(String arg);
@ReactMethod(isBlockingSynchronousMethod = true)
public abstract double getRootTag(double arg);
@ReactMethod(isBlockingSynchronousMethod = true)
public abstract boolean getBool(boolean arg);
@ReactMethod(isBlockingSynchronousMethod = true)
public abstract double getEnum(double arg);
@ReactMethod()
public abstract void voidFuncThrows();
@ReactMethod(isBlockingSynchronousMethod = true)
public abstract WritableMap getObjectThrows(ReadableMap arg);
@ReactMethod()
public abstract void promiseThrows(Promise promise);
@ReactMethod()
public abstract void voidFuncAssert();
@ReactMethod(isBlockingSynchronousMethod = true)
public abstract WritableMap getObjectAssert(ReadableMap arg);
@ReactMethod()
public abstract void promiseAssert(Promise promise);
protected abstract Map<String, Object> getTypedExportedConstants();
@Override
public final @Nullable Map<String, Object> getConstants() {
Map<String, Object> constants = getTypedExportedConstants();
if (ReactBuildConfig.DEBUG || ReactBuildConfig.IS_INTERNAL_BUILD) {
Set<String> obligatoryFlowConstants =
new HashSet<>(Arrays.asList("const2", "const1", "const3"));
Set<String> optionalFlowConstants = new HashSet<>();
Set<String> undeclaredConstants = new HashSet<>(constants.keySet());
undeclaredConstants.removeAll(obligatoryFlowConstants);
undeclaredConstants.removeAll(optionalFlowConstants);
if (!undeclaredConstants.isEmpty()) {
throw new IllegalStateException(
String.format("Native Module Flow doesn't declare constants: %s", undeclaredConstants));
}
undeclaredConstants = obligatoryFlowConstants;
undeclaredConstants.removeAll(constants.keySet());
if (!undeclaredConstants.isEmpty()) {
throw new IllegalStateException(
String.format("Native Module doesn't fill in constants: %s", undeclaredConstants));
}
}
return constants;
}
}

View File

@@ -0,0 +1,363 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
// NOTE: This entire file should be codegen'ed.
#include <ReactCommon/SampleTurboModuleSpec.h>
namespace facebook::react {
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_voidFunc(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt, VoidKind, "voidFunc", "()V", args, count, cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_getBool(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt, BooleanKind, "getBool", "(Z)Z", args, count, cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_getEnum(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt, NumberKind, "getEnum", "(D)D", args, count, cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_getNumber(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt, NumberKind, "getNumber", "(D)D", args, count, cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_getString(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt,
StringKind,
"getString",
"(Ljava/lang/String;)Ljava/lang/String;",
args,
count,
cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_getArray(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt,
ArrayKind,
"getArray",
"(Lcom/facebook/react/bridge/ReadableArray;)Lcom/facebook/react/bridge/WritableArray;",
args,
count,
cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_getObject(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt,
ObjectKind,
"getObject",
"(Lcom/facebook/react/bridge/ReadableMap;)Lcom/facebook/react/bridge/WritableMap;",
args,
count,
cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_getRootTag(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt, NumberKind, "getRootTag", "(D)D", args, count, cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_getValue(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt,
ObjectKind,
"getValue",
"(DLjava/lang/String;Lcom/facebook/react/bridge/ReadableMap;)Lcom/facebook/react/bridge/WritableMap;",
args,
count,
cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_getValueWithCallback(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt,
VoidKind,
"getValueWithCallback",
"(Lcom/facebook/react/bridge/Callback;)V",
args,
count,
cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_getValueWithPromise(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt,
PromiseKind,
"getValueWithPromise",
"(ZLcom/facebook/react/bridge/Promise;)V",
args,
count,
cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_voidFuncThrows(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt, VoidKind, "voidFuncThrows", "()V", args, count, cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_getObjectThrows(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt,
ObjectKind,
"getObjectThrows",
"(Lcom/facebook/react/bridge/ReadableMap;)Lcom/facebook/react/bridge/WritableMap;",
args,
count,
cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_promiseThrows(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt,
PromiseKind,
"promiseThrows",
"(Lcom/facebook/react/bridge/Promise;)V",
args,
count,
cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_voidFuncAssert(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt, VoidKind, "voidFuncAssert", "()V", args, count, cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_getObjectAssert(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt,
ObjectKind,
"getObjectAssert",
"(Lcom/facebook/react/bridge/ReadableMap;)Lcom/facebook/react/bridge/WritableMap;",
args,
count,
cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_promiseAssert(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt,
PromiseKind,
"promiseAssert",
"(Lcom/facebook/react/bridge/Promise;)V",
args,
count,
cachedMethodId);
}
static facebook::jsi::Value
__hostFunction_NativeSampleTurboModuleSpecJSI_getConstants(
facebook::jsi::Runtime& rt,
TurboModule& turboModule,
const facebook::jsi::Value* args,
size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule&>(turboModule)
.invokeJavaMethod(
rt,
ObjectKind,
"getConstants",
"()Ljava/util/Map;",
args,
count,
cachedMethodId);
}
NativeSampleTurboModuleSpecJSI::NativeSampleTurboModuleSpecJSI(
const JavaTurboModule::InitParams& params)
: JavaTurboModule(params) {
methodMap_["voidFunc"] =
MethodMetadata{0, __hostFunction_NativeSampleTurboModuleSpecJSI_voidFunc};
methodMap_["getBool"] =
MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_getBool};
methodMap_["getEnum"] =
MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_getEnum};
methodMap_["getNumber"] = MethodMetadata{
1, __hostFunction_NativeSampleTurboModuleSpecJSI_getNumber};
methodMap_["getString"] = MethodMetadata{
1, __hostFunction_NativeSampleTurboModuleSpecJSI_getString};
methodMap_["getArray"] =
MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_getArray};
methodMap_["getObject"] = MethodMetadata{
1, __hostFunction_NativeSampleTurboModuleSpecJSI_getObject};
methodMap_["getRootTag"] = MethodMetadata{
1, __hostFunction_NativeSampleTurboModuleSpecJSI_getRootTag};
methodMap_["getValue"] =
MethodMetadata{3, __hostFunction_NativeSampleTurboModuleSpecJSI_getValue};
methodMap_["getValueWithCallback"] = MethodMetadata{
1, __hostFunction_NativeSampleTurboModuleSpecJSI_getValueWithCallback};
methodMap_["getValueWithPromise"] = MethodMetadata{
1, __hostFunction_NativeSampleTurboModuleSpecJSI_getValueWithPromise};
methodMap_["voidFuncThrows"] = MethodMetadata{
0, __hostFunction_NativeSampleTurboModuleSpecJSI_voidFuncThrows};
methodMap_["getObjectThrows"] = MethodMetadata{
1, __hostFunction_NativeSampleTurboModuleSpecJSI_getObjectThrows};
methodMap_["promiseThrows"] = MethodMetadata{
1, __hostFunction_NativeSampleTurboModuleSpecJSI_promiseThrows};
methodMap_["voidFuncAssert"] = MethodMetadata{
0, __hostFunction_NativeSampleTurboModuleSpecJSI_voidFuncAssert};
methodMap_["getObjectAssert"] = MethodMetadata{
1, __hostFunction_NativeSampleTurboModuleSpecJSI_getObjectAssert};
methodMap_["promiseAssert"] = MethodMetadata{
1, __hostFunction_NativeSampleTurboModuleSpecJSI_promiseAssert};
methodMap_["getConstants"] = MethodMetadata{
0, __hostFunction_NativeSampleTurboModuleSpecJSI_getConstants};
}
std::shared_ptr<TurboModule> SampleTurboModuleSpec_ModuleProvider(
const std::string& moduleName,
const JavaTurboModule::InitParams& params) {
if (moduleName == "SampleTurboModule") {
return std::make_shared<NativeSampleTurboModuleSpecJSI>(params);
}
return nullptr;
}
} // namespace facebook::react

View File

@@ -0,0 +1,30 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
// NOTE: This entire file should be codegen'ed.
#pragma once
#include <ReactCommon/JavaTurboModule.h>
#include <ReactCommon/TurboModule.h>
#include <fbjni/fbjni.h>
namespace facebook::react {
/**
* C++ class for module 'SampleTurboModule'
*/
class JSI_EXPORT NativeSampleTurboModuleSpecJSI : public JavaTurboModule {
public:
NativeSampleTurboModuleSpecJSI(const JavaTurboModule::InitParams& params);
};
std::shared_ptr<TurboModule> SampleTurboModuleSpec_ModuleProvider(
const std::string& moduleName,
const JavaTurboModule::InitParams& params);
} // namespace facebook::react

View File

@@ -0,0 +1,246 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.fbreact.specs;
import android.app.Activity;
import android.util.DisplayMetrics;
import android.widget.Toast;
import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeArray;
import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.module.annotations.ReactModule;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nullable;
@ReactModule(name = SampleLegacyModule.NAME)
public class SampleLegacyModule extends ReactContextBaseJavaModule {
public static final String NAME = "SampleLegacyModule";
private static final String TAG = SampleLegacyModule.class.getName();
private final ReactApplicationContext mContext;
private Toast mToast;
public SampleLegacyModule(ReactApplicationContext context) {
super(context);
mContext = context;
}
@DoNotStrip
@SuppressWarnings("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
public boolean getBool(boolean arg) {
log("getBool", arg, arg);
return arg;
}
@DoNotStrip
@SuppressWarnings("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
public double getEnum(double arg) {
log("getEnum", arg, arg);
return arg;
}
@DoNotStrip
@SuppressWarnings("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
public double getDouble(double arg) {
log("getDouble", arg, arg);
return arg;
}
@DoNotStrip
@SuppressWarnings("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
public int getInt(int arg) {
log("getInt", arg, arg);
return arg;
}
@DoNotStrip
@SuppressWarnings("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
public float getFloat(float arg) {
log("getFloat", arg, arg);
return arg;
}
@DoNotStrip
@SuppressWarnings("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
public double getObjectDouble(Double arg) {
log("getObjectDouble", arg, arg);
return arg;
}
@DoNotStrip
@SuppressWarnings("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
public Integer getObjectInteger(Integer arg) {
log("getObjectInteger", arg, arg);
return arg;
}
@DoNotStrip
@SuppressWarnings("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
public Float getObjectFloat(Float arg) {
log("getObjectFloat", arg, arg);
return arg;
}
@DoNotStrip
@SuppressWarnings("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
public String getString(String arg) {
log("getString", arg, arg);
return arg;
}
@DoNotStrip
@SuppressWarnings("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
public double getRootTag(double arg) {
log("getRootTag", arg, arg);
return arg;
}
@DoNotStrip
@SuppressWarnings("unused")
@ReactMethod
public void voidFunc() {
log("voidFunc", "<void>", "<void>");
return;
}
// This function returns {@link WritableMap} instead of {@link Map} for backward compat with
// existing native modules that use this Writable* as return types or in events. {@link
// WritableMap} is modified in the Java side, and read (or consumed) on the C++ side.
// In the future, all native modules should ideally return an immutable Map
@DoNotStrip
@SuppressWarnings("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
public WritableMap getObject(ReadableMap arg) {
WritableNativeMap map = new WritableNativeMap();
map.merge(arg);
log("getObject", arg, map);
return map;
}
@DoNotStrip
@SuppressWarnings("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
public WritableMap getUnsafeObject(ReadableMap arg) {
WritableNativeMap map = new WritableNativeMap();
map.merge(arg);
log("getUnsafeObject", arg, map);
return map;
}
@DoNotStrip
@SuppressWarnings("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
public WritableMap getValue(double numberArg, String stringArg, ReadableMap mapArg) {
WritableMap map = new WritableNativeMap();
map.putDouble("x", numberArg);
map.putString("y", stringArg);
WritableMap zMap = new WritableNativeMap();
zMap.merge(mapArg);
map.putMap("z", zMap);
log(
"getValue",
MapBuilder.of("1-numberArg", numberArg, "2-stringArg", stringArg, "3-mapArg", mapArg),
map);
return map;
}
@DoNotStrip
@SuppressWarnings("unused")
@ReactMethod
public void getValueWithCallback(final Callback callback) {
String result = "Value From Callback";
log("Callback", "Return Time", result);
callback.invoke(result);
}
@DoNotStrip
@SuppressWarnings("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
public WritableArray getArray(ReadableArray arg) {
if (arg == null || Arguments.toList(arg) == null) {
// Returning an empty array, since the super class always returns non-null
return new WritableNativeArray();
}
WritableArray result = Arguments.makeNativeArray(Arguments.toList(arg));
log("getArray", arg, result);
return result;
}
@DoNotStrip
@SuppressWarnings("unused")
@ReactMethod
public void getValueWithPromise(boolean error, Promise promise) {
if (error) {
promise.reject(
"code 1",
"intentional promise rejection",
new Throwable("promise intentionally rejected"));
} else {
promise.resolve("result");
}
}
@Override
public final @Nullable Map<String, Object> getConstants() {
Map<String, Object> result = new HashMap<>();
DisplayMetrics displayMetrics = new DisplayMetrics();
Activity activity = mContext.getCurrentActivity();
if (activity != null) {
result.put("const2", 390);
}
result.put("const1", true);
result.put("const3", "something");
log("constantsToExport", "", result);
return result;
}
private void log(String method, Object input, Object output) {
if (mToast != null) {
mToast.cancel();
}
StringBuilder message = new StringBuilder("Method :");
message
.append(method)
.append("\nInputs: ")
.append(input.toString())
.append("\nOutputs: ")
.append(output.toString());
mToast = Toast.makeText(mContext, message.toString(), Toast.LENGTH_LONG);
mToast.show();
}
public void invalidate() {}
@Override
public String getName() {
return NAME;
}
}

View File

@@ -0,0 +1,248 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.fbreact.specs;
import android.app.Activity;
import android.util.DisplayMetrics;
import android.widget.Toast;
import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeArray;
import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.module.annotations.ReactModule;
import java.util.HashMap;
import java.util.Map;
@ReactModule(name = SampleTurboModule.NAME)
public class SampleTurboModule extends NativeSampleTurboModuleSpec {
public static final String NAME = "SampleTurboModule";
private static final String TAG = SampleTurboModule.class.getName();
private final ReactApplicationContext mContext;
private Toast mToast;
public SampleTurboModule(ReactApplicationContext context) {
super(context);
mContext = context;
}
@DoNotStrip
@SuppressWarnings("unused")
@Override
public boolean getBool(boolean arg) {
log("getBool", arg, arg);
return arg;
}
@DoNotStrip
@SuppressWarnings("unused")
@Override
public double getEnum(double arg) {
log("getEnum", arg, arg);
return arg;
}
@Override
protected Map<String, Object> getTypedExportedConstants() {
Map<String, Object> result = new HashMap<>();
DisplayMetrics displayMetrics = new DisplayMetrics();
Activity activity = mContext.getCurrentActivity();
if (activity != null) {
activity.getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
result.put("const2", displayMetrics.widthPixels);
}
result.put("const1", true);
result.put("const3", "something");
log("constantsToExport", "", result);
return result;
}
@DoNotStrip
@SuppressWarnings("unused")
@Override
public double getNumber(double arg) {
log("getNumber", arg, arg);
return arg;
}
@DoNotStrip
@SuppressWarnings("unused")
@Override
public String getString(String arg) {
log("getString", arg, arg);
return arg;
}
@DoNotStrip
@SuppressWarnings("unused")
@Override
public double getRootTag(double arg) {
log("getRootTag", arg, arg);
return arg;
}
@DoNotStrip
@SuppressWarnings("unused")
@Override
public void voidFunc() {
log("voidFunc", "<void>", "<void>");
return;
}
// This function returns {@link WritableMap} instead of {@link Map} for backward compat with
// existing native modules that use this Writable* as return types or in events. {@link
// WritableMap} is modified in the Java side, and read (or consumed) on the C++ side.
// In the future, all native modules should ideally return an immutable Map
@DoNotStrip
@Override
@SuppressWarnings("unused")
public WritableMap getObject(ReadableMap arg) {
WritableNativeMap map = new WritableNativeMap();
map.merge(arg);
log("getObject", arg, map);
return map;
}
@DoNotStrip
@Override
@SuppressWarnings("unused")
public WritableMap getUnsafeObject(ReadableMap arg) {
WritableNativeMap map = new WritableNativeMap();
map.merge(arg);
log("getUnsafeObject", arg, map);
return map;
}
@DoNotStrip
@SuppressWarnings("unused")
@Override
public WritableMap getValue(double numberArg, String stringArg, ReadableMap mapArg) {
WritableMap map = new WritableNativeMap();
map.putDouble("x", numberArg);
map.putString("y", stringArg);
WritableMap zMap = new WritableNativeMap();
zMap.merge(mapArg);
map.putMap("z", zMap);
log(
"getValue",
MapBuilder.of("1-numberArg", numberArg, "2-stringArg", stringArg, "3-mapArg", mapArg),
map);
return map;
}
@DoNotStrip
@SuppressWarnings("unused")
@Override
public void getValueWithCallback(final Callback callback) {
String result = "Value From Callback";
log("Callback", "Return Time", result);
callback.invoke(result);
}
@DoNotStrip
@SuppressWarnings("unused")
@Override
public WritableArray getArray(ReadableArray arg) {
if (arg == null || Arguments.toList(arg) == null) {
// Returning an empty array, since the super class always returns non-null
return new WritableNativeArray();
}
WritableArray result = Arguments.makeNativeArray(Arguments.toList(arg));
log("getArray", arg, result);
return result;
}
@Override
@DoNotStrip
@SuppressWarnings("unused")
public void getValueWithPromise(boolean error, Promise promise) {
if (error) {
promise.reject(
"code 1",
"intentional promise rejection",
new Throwable("promise intentionally rejected"));
} else {
promise.resolve("result");
}
}
@Override
@DoNotStrip
@SuppressWarnings("unused")
public void voidFuncThrows() {
throw new RuntimeException("Intentional exception from JVM voidFuncThrows");
};
@Override
@DoNotStrip
@SuppressWarnings("unused")
public WritableMap getObjectThrows(ReadableMap arg) {
throw new RuntimeException(
"Intentional exception from JVM getObjectThrows with " + arg.toString());
};
@Override
@DoNotStrip
@SuppressWarnings("unused")
public void promiseThrows(Promise promise) {
throw new RuntimeException("Intentional exception from JVM promiseThrows");
};
@Override
@DoNotStrip
@SuppressWarnings("unused")
public void voidFuncAssert() {
assert false : "Intentional assert from JVM voidFuncAssert";
};
@Override
@DoNotStrip
@SuppressWarnings("unused")
public WritableMap getObjectAssert(ReadableMap arg) {
assert false : "Intentional assert from JVM getObjectAssert with " + arg.toString();
return null;
};
@Override
@DoNotStrip
@SuppressWarnings("unused")
public void promiseAssert(Promise promise) {
assert false : "Intentional assert from JVM promiseAssert";
};
private void log(String method, Object input, Object output) {
if (mToast != null) {
mToast.cancel();
}
StringBuilder message = new StringBuilder("Method :");
message
.append(method)
.append("\nInputs: ")
.append(input.toString())
.append("\nOutputs: ")
.append(output.toString());
mToast = Toast.makeText(mContext, message.toString(), Toast.LENGTH_LONG);
mToast.show();
}
public void invalidate() {}
@Override
public String getName() {
return NAME;
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
// NOTE: This entire file should be codegen'ed.
#import <vector>
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
#import <ReactCommon/RCTTurboModule.h>
/**
* The ObjC protocol based on the JS Flow type for SampleTurboModule.
*/
@protocol NativeSampleTurboModuleSpec <RCTBridgeModule, RCTTurboModule>
- (void)voidFunc;
- (NSNumber *)getBool:(BOOL)arg;
- (NSNumber *)getEnum:(double)arg;
- (NSNumber *)getNumber:(double)arg;
- (NSString *)getString:(NSString *)arg;
- (NSArray<id<NSObject>> *)getArray:(NSArray *)arg;
- (NSDictionary *)getObject:(NSDictionary *)arg;
- (NSDictionary *)getUnsafeObject:(NSDictionary *)arg;
- (NSNumber *)getRootTag:(double)arg;
- (NSDictionary *)getValue:(double)x y:(NSString *)y z:(NSDictionary *)z;
- (void)getValueWithCallback:(RCTResponseSenderBlock)callback;
- (void)getValueWithPromise:(BOOL)error resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject;
- (void)voidFuncThrows;
- (NSDictionary *)getObjectThrows:(NSDictionary *)arg;
- (void)promiseThrows:(BOOL)error resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject;
- (void)voidFuncAssert;
- (NSDictionary *)getObjectAssert:(NSDictionary *)arg;
- (void)promiseAssert:(BOOL)error resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject;
- (NSDictionary *)constantsToExport;
- (NSDictionary *)getConstants;
@end
namespace facebook::react {
/**
* The iOS TurboModule impl specific to SampleTurboModule.
*/
class JSI_EXPORT NativeSampleTurboModuleSpecJSI : public ObjCTurboModule {
public:
NativeSampleTurboModuleSpecJSI(const ObjCTurboModule::InitParams &params);
};
} // namespace facebook::react

View File

@@ -0,0 +1,229 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTNativeSampleTurboModuleSpec.h"
namespace facebook::react {
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_voidFunc(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, VoidKind, "voidFunc", @selector(voidFunc), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getBool(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, BooleanKind, "getBool", @selector(getBool:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getEnum(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, NumberKind, "getEnum", @selector(getEnum:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getNumber(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, NumberKind, "getNumber", @selector(getNumber:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getString(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, StringKind, "getString", @selector(getString:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getArray(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, ArrayKind, "getArray", @selector(getArray:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getObject(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, ObjectKind, "getObject", @selector(getObject:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getUnsafeObject(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, ObjectKind, "getUnsafeObject", @selector(getUnsafeObject:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getRootTag(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, NumberKind, "getRootTag", @selector(getRootTag:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getValue(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, ObjectKind, "getValue", @selector(getValue:y:z:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getValueWithCallback(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, VoidKind, "getValueWithCallback", @selector(getValueWithCallback:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getValueWithPromise(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(
rt, PromiseKind, "getValueWithPromise", @selector(getValueWithPromise:resolve:reject:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getConstants(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, ObjectKind, "getConstants", @selector(getConstants), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_voidFuncThrows(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, VoidKind, "voidFuncThrows", @selector(voidFuncThrows), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getObjectThrows(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, ObjectKind, "getObjectThrows", @selector(getObjectThrows:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_promiseThrows(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, PromiseKind, "promiseThrows", @selector(promiseThrows:resolve:reject:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_voidFuncAssert(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, VoidKind, "voidFuncAssert", @selector(voidFuncAssert), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getObjectAssert(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, ObjectKind, "getObjectAssert", @selector(getObjectAssert:), args, count);
}
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_promiseAssert(
facebook::jsi::Runtime &rt,
TurboModule &turboModule,
const facebook::jsi::Value *args,
size_t count)
{
return static_cast<ObjCTurboModule &>(turboModule)
.invokeObjCMethod(rt, PromiseKind, "promiseAssert", @selector(promiseAssert:resolve:reject:), args, count);
}
NativeSampleTurboModuleSpecJSI::NativeSampleTurboModuleSpecJSI(const ObjCTurboModule::InitParams &params)
: ObjCTurboModule(params)
{
methodMap_["voidFunc"] = MethodMetadata{0, __hostFunction_NativeSampleTurboModuleSpecJSI_voidFunc};
methodMap_["getBool"] = MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_getBool};
methodMap_["getEnum"] = MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_getEnum};
methodMap_["getNumber"] = MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_getNumber};
methodMap_["getString"] = MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_getString};
methodMap_["getArray"] = MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_getArray};
methodMap_["getObject"] = MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_getObject};
methodMap_["getUnsafeObject"] = MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_getUnsafeObject};
methodMap_["getRootTag"] = MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_getRootTag};
methodMap_["getValue"] = MethodMetadata{3, __hostFunction_NativeSampleTurboModuleSpecJSI_getValue};
methodMap_["getValueWithCallback"] =
MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_getValueWithCallback};
methodMap_["getValueWithPromise"] =
MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_getValueWithPromise};
methodMap_["voidFuncThrows"] = MethodMetadata{0, __hostFunction_NativeSampleTurboModuleSpecJSI_voidFuncThrows};
methodMap_["getObjectThrows"] = MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_getObjectThrows};
methodMap_["promiseThrows"] = MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_promiseThrows};
methodMap_["voidFuncAssert"] = MethodMetadata{0, __hostFunction_NativeSampleTurboModuleSpecJSI_voidFuncAssert};
methodMap_["getObjectAssert"] = MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_getObjectAssert};
methodMap_["promiseAssert"] = MethodMetadata{1, __hostFunction_NativeSampleTurboModuleSpecJSI_promiseAssert};
methodMap_["getConstants"] = MethodMetadata{0, __hostFunction_NativeSampleTurboModuleSpecJSI_getConstants};
}
} // namespace facebook::react

View File

@@ -0,0 +1,16 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <UIKit/UIKit.h>
#import <React/RCTAssert.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTInvalidating.h>
#import <React/RCTUtils.h>
@interface RCTSampleLegacyModule : NSObject <RCTBridgeModule, RCTInvalidating>
@end

View File

@@ -0,0 +1,222 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTSampleLegacyModule.h"
@implementation RCTSampleLegacyModule {
RCTBridge *_bridge;
}
// Backward-compatible export
RCT_EXPORT_MODULE()
// Backward-compatible queue configuration
+ (BOOL)requiresMainQueueSetup
{
return YES;
}
- (void)setBridge:(RCTBridge *)bridge
{
_bridge = bridge;
}
- (dispatch_queue_t)methodQueue
{
return dispatch_get_main_queue();
}
// Backward compatible invalidation
- (void)invalidate
{
// Actually do nothing here.
NSLog(@"Invalidating RCTSampleTurboModule...");
}
- (NSDictionary *)getConstants
{
__block NSDictionary *constants;
RCTUnsafeExecuteOnMainQueueSync(^{
constants = @{
@"const1" : @YES,
@"const2" : @(390),
@"const3" : @"something",
};
});
return constants;
}
// TODO: Remove once fully migrated to TurboModule.
- (NSDictionary *)constantsToExport
{
return [self getConstants];
}
RCT_EXPORT_METHOD(voidFunc)
{
// Nothing to do
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, getBool : (BOOL)arg)
{
return @(arg);
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, getEnum : (double)arg)
{
return @(arg);
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, getNumber : (double)arg)
{
return @(arg);
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, getFloat : (float)arg)
{
return @(arg);
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, getInt : (int)arg)
{
return @(arg);
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, getLongLong : (int64_t)arg)
{
return @(arg);
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, getUnsignedLongLong : (uint64_t)arg)
{
return @(arg);
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, getNSInteger : (NSInteger)arg)
{
return @(arg);
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, getNSUInteger : (NSUInteger)arg)
{
return @(arg);
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSArray<id<NSObject>> *, getArray : (NSArray *)arg)
{
return arg;
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSDictionary *, getObject : (NSDictionary *)arg)
{
return arg;
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSString *, getString : (NSString *)arg)
{
return arg;
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, getNSNumber : (nonnull NSNumber *)arg)
{
return arg;
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSDictionary *, getUnsafeObject : (NSDictionary *)arg)
{
return arg;
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, getRootTag : (double)arg)
{
return @(arg);
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSDictionary *, getValue : (double)x y : (NSString *)y z : (NSDictionary *)z)
{
return @{
@"x" : @(x),
@"y" : y ?: [NSNull null],
@"z" : z ?: [NSNull null],
};
}
RCT_EXPORT_METHOD(getValueWithCallback : (RCTResponseSenderBlock)callback)
{
if (!callback) {
return;
}
callback(@[ @"value from callback!" ]);
}
RCT_EXPORT_METHOD(getValueWithPromise
: (BOOL)error resolve
: (RCTPromiseResolveBlock)resolve reject
: (RCTPromiseRejectBlock)reject)
{
if (!resolve || !reject) {
return;
}
if (error) {
reject(
@"code_1",
@"intentional promise rejection",
[NSError errorWithDomain:@"RCTSampleTurboModule" code:1 userInfo:nil]);
} else {
resolve(@"result!");
}
}
RCT_EXPORT_METHOD(voidFuncThrows)
{
NSException *myException = [NSException exceptionWithName:@"Excepption"
reason:@"Intentional exception from ObjC voidFuncThrows"
userInfo:nil];
@throw myException;
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSDictionary *, getObjectThrows : (NSDictionary *)arg)
{
NSException *myException = [NSException exceptionWithName:@"Excepption"
reason:@"Intentional exception from ObjC getObjectThrows"
userInfo:nil];
@throw myException;
}
RCT_EXPORT_METHOD(promiseThrows
: (BOOL)error resolve
: (RCTPromiseResolveBlock)resolve reject
: (RCTPromiseRejectBlock)reject)
{
NSException *myException = [NSException exceptionWithName:@"Excepption"
reason:@"Intentional exception from ObjC promiseThrows"
userInfo:nil];
@throw myException;
}
RCT_EXPORT_METHOD(voidFuncAssert)
{
RCTAssert(false, @"Intentional assert from ObjC voidFuncAssert");
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSDictionary *, getObjectAssert : (NSDictionary *)arg)
{
RCTAssert(false, @"Intentional assert from ObjC getObjectAssert");
return arg;
}
RCT_EXPORT_METHOD(promiseAssert
: (BOOL)error resolve
: (RCTPromiseResolveBlock)resolve reject
: (RCTPromiseRejectBlock)reject)
{
RCTAssert(false, @"Intentional assert from ObjC promiseAssert");
}
@end

View File

@@ -0,0 +1,27 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#import <React/RCTCxxModule.h>
#import <ReactCommon/RCTTurboModule.h>
/**
* Sample backward-compatible RCTCxxModule-based module.
* With jsi::HostObject, this class is no longer necessary, but the system supports it for
* backward compatibility.
*/
@interface RCTSampleTurboCxxModule_v1 : RCTCxxModule <RCTTurboModule>
@end
/**
* Second variant of a sample backward-compatible RCTCxxModule-based module.
*/
@interface RCTSampleTurboCxxModule_v2 : RCTCxxModule <RCTTurboModule>
@end

View File

@@ -0,0 +1,54 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTSampleTurboCxxModule.h"
#import <ReactCommon/SampleTurboCxxModule.h>
#import <cxxreact/CxxModule.h>
#import "SampleTurboCxxModuleLegacyImpl.h"
using namespace facebook;
// ObjC++ wrapper.
@implementation RCTSampleTurboCxxModule_v1
RCT_EXPORT_MODULE();
- (std::shared_ptr<react::TurboModule>)getTurboModuleWithJsInvoker:(std::shared_ptr<react::CallInvoker>)jsInvoker
{
return std::make_shared<react::SampleTurboCxxModule>(jsInvoker);
}
- (std::unique_ptr<xplat::module::CxxModule>)createModule
{
return nullptr;
}
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
return nullptr;
}
@end
@implementation RCTSampleTurboCxxModule_v2
RCT_EXPORT_MODULE();
- (std::unique_ptr<xplat::module::CxxModule>)createModule
{
return std::make_unique<react::SampleTurboCxxModuleLegacyImpl>();
}
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
return nullptr;
}
@end

View File

@@ -0,0 +1,18 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
#import "RCTNativeSampleTurboModuleSpec.h"
/**
* Sample iOS-specific impl of a TurboModule, conforming to the spec protocol.
* This class is also 100% compatible with the NativeModule system.
*/
@interface RCTSampleTurboModule : NSObject <NativeSampleTurboModuleSpec>
@end

View File

@@ -0,0 +1,195 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTSampleTurboModule.h"
#import <React/RCTAssert.h>
#import <React/RCTUtils.h>
#import <UIKit/UIKit.h>
using namespace facebook::react;
@implementation RCTSampleTurboModule
// Backward-compatible export
RCT_EXPORT_MODULE()
// Backward-compatible queue configuration
+ (BOOL)requiresMainQueueSetup
{
return YES;
}
- (dispatch_queue_t)methodQueue
{
return dispatch_get_main_queue();
}
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
return std::make_shared<NativeSampleTurboModuleSpecJSI>(params);
}
// Backward compatible invalidation
- (void)invalidate
{
// Actually do nothing here.
NSLog(@"Invalidating RCTSampleTurboModule...");
}
- (NSDictionary *)getConstants
{
__block NSDictionary *constants;
RCTUnsafeExecuteOnMainQueueSync(^{
UIScreen *mainScreen = UIScreen.mainScreen;
CGSize screenSize = mainScreen.bounds.size;
constants = @{
@"const1" : @YES,
@"const2" : @(screenSize.width),
@"const3" : @"something",
};
});
return constants;
}
// TODO: Remove once fully migrated to TurboModule.
- (NSDictionary *)constantsToExport
{
return [self getConstants];
}
RCT_EXPORT_METHOD(voidFunc)
{
// Nothing to do
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, getBool : (BOOL)arg)
{
return @(arg);
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, getEnum : (double)arg)
{
return @(arg);
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, getNumber : (double)arg)
{
return @(arg);
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSString *, getString : (NSString *)arg)
{
return arg;
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSArray<id<NSObject>> *, getArray : (NSArray *)arg)
{
return arg;
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSDictionary *, getObject : (NSDictionary *)arg)
{
return arg;
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSDictionary *, getUnsafeObject : (NSDictionary *)arg)
{
return arg;
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, getRootTag : (double)arg)
{
return @(arg);
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSDictionary *, getValue : (double)x y : (NSString *)y z : (NSDictionary *)z)
{
return @{
@"x" : @(x),
@"y" : y ?: [NSNull null],
@"z" : z ?: [NSNull null],
};
}
RCT_EXPORT_METHOD(getValueWithCallback : (RCTResponseSenderBlock)callback)
{
if (!callback) {
return;
}
callback(@[ @"value from callback!" ]);
}
RCT_EXPORT_METHOD(getValueWithPromise
: (BOOL)error resolve
: (RCTPromiseResolveBlock)resolve reject
: (RCTPromiseRejectBlock)reject)
{
if (!resolve || !reject) {
return;
}
if (error) {
reject(
@"code_1",
@"intentional promise rejection",
[NSError errorWithDomain:@"RCTSampleTurboModule" code:1 userInfo:nil]);
} else {
resolve(@"result!");
}
}
RCT_EXPORT_METHOD(voidFuncThrows)
{
NSException *myException = [NSException exceptionWithName:@"Excepption"
reason:@"Intentional exception from ObjC voidFuncThrows"
userInfo:nil];
@throw myException;
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSDictionary *, getObjectThrows : (NSDictionary *)arg)
{
NSException *myException = [NSException exceptionWithName:@"Excepption"
reason:@"Intentional exception from ObjC getObjectThrows"
userInfo:nil];
@throw myException;
}
RCT_EXPORT_METHOD(promiseThrows
: (BOOL)error resolve
: (RCTPromiseResolveBlock)resolve reject
: (RCTPromiseRejectBlock)reject)
{
NSException *myException = [NSException exceptionWithName:@"Excepption"
reason:@"Intentional exception from ObjC promiseThrows"
userInfo:nil];
@throw myException;
}
RCT_EXPORT_METHOD(voidFuncAssert)
{
RCTAssert(false, @"Intentional assert from ObjC voidFuncAssert");
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSDictionary *, getObjectAssert : (NSDictionary *)arg)
{
RCTAssert(false, @"Intentional assert from ObjC getObjectAssert");
return arg;
}
RCT_EXPORT_METHOD(promiseAssert
: (BOOL)error resolve
: (RCTPromiseResolveBlock)resolve reject
: (RCTPromiseRejectBlock)reject)
{
RCTAssert(false, @"Intentional assert from ObjC promiseAssert");
}
@end

View File

@@ -0,0 +1,177 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "SampleTurboCxxModuleLegacyImpl.h"
#include <cxxreact/JsArgumentHelpers.h>
using namespace facebook::xplat::module;
namespace facebook::react {
SampleTurboCxxModuleLegacyImpl::SampleTurboCxxModuleLegacyImpl() {}
std::string SampleTurboCxxModuleLegacyImpl::getName() {
return "SampleTurboCxxModule_v2";
}
std::map<std::string, folly::dynamic>
SampleTurboCxxModuleLegacyImpl::getConstants() {
return {
{"const1", true},
{"const2", 375},
{"const3", "something"},
};
};
std::vector<CxxModule::Method> SampleTurboCxxModuleLegacyImpl::getMethods() {
return {
CxxModule::Method(
"voidFunc", [this](folly::dynamic args) { voidFunc(); }),
CxxModule::Method(
"getBool",
[this](folly::dynamic args) {
return getBool(xplat::jsArgAsBool(args, 0));
},
CxxModule::SyncTag),
CxxModule::Method(
"getEnum",
[this](folly::dynamic args) {
return getEnum(xplat::jsArgAsDouble(args, 0));
},
CxxModule::SyncTag),
CxxModule::Method(
"getNumber",
[this](folly::dynamic args) {
return getNumber(xplat::jsArgAsDouble(args, 0));
},
CxxModule::SyncTag),
CxxModule::Method(
"getString",
[this](folly::dynamic args) {
return getString(xplat::jsArgAsString(args, 0));
},
CxxModule::SyncTag),
CxxModule::Method(
"getString",
[this](folly::dynamic args) {
return getString(xplat::jsArgAsString(args, 0));
},
CxxModule::SyncTag),
CxxModule::Method(
"getArray",
[this](folly::dynamic args) {
return getArray(xplat::jsArgAsArray(args, 0));
},
CxxModule::SyncTag),
CxxModule::Method(
"getObject",
[this](folly::dynamic args) {
return getObject(xplat::jsArgAsObject(args, 0));
},
CxxModule::SyncTag),
CxxModule::Method(
"getUnsafeObject",
[this](folly::dynamic args) {
return getUnsafeObject(xplat::jsArgAsObject(args, 0));
},
CxxModule::SyncTag),
CxxModule::Method(
"getRootTag",
[this](folly::dynamic args) {
return getNumber(xplat::jsArgAsDouble(args, 0));
},
CxxModule::SyncTag),
CxxModule::Method(
"getValue",
[this](folly::dynamic args) {
return getValue(
xplat::jsArgAsDouble(args, 0),
xplat::jsArgAsString(args, 1),
xplat::jsArgAsObject(args, 2));
},
CxxModule::SyncTag),
CxxModule::Method(
"getValueWithCallback",
[this](folly::dynamic args, CxxModule::Callback callback) {
getValueWithCallback(callback);
}),
CxxModule::Method(
"getValueWithPromise",
[this](
folly::dynamic args,
CxxModule::Callback resolve,
CxxModule::Callback reject) {
getValueWithPromise(xplat::jsArgAsBool(args, 0), resolve, reject);
}),
};
};
void SampleTurboCxxModuleLegacyImpl::voidFunc() {
// Do nothing.
}
bool SampleTurboCxxModuleLegacyImpl::getBool(bool arg) {
return arg;
}
double SampleTurboCxxModuleLegacyImpl::getEnum(double arg) {
return arg;
}
double SampleTurboCxxModuleLegacyImpl::getNumber(double arg) {
return arg;
}
std::string SampleTurboCxxModuleLegacyImpl::getString(const std::string& arg) {
return arg;
}
folly::dynamic SampleTurboCxxModuleLegacyImpl::getArray(
const folly::dynamic& arg) {
return arg;
}
folly::dynamic SampleTurboCxxModuleLegacyImpl::getObject(
const folly::dynamic& arg) {
return arg;
}
folly::dynamic SampleTurboCxxModuleLegacyImpl::getUnsafeObject(
const folly::dynamic& arg) {
return arg;
}
double SampleTurboCxxModuleLegacyImpl::getRootTag(double arg) {
return arg;
}
folly::dynamic SampleTurboCxxModuleLegacyImpl::getValue(
double x,
const std::string& y,
const folly::dynamic& z) {
return folly::dynamic::object("x", x)("y", y)("z", z);
}
void SampleTurboCxxModuleLegacyImpl::getValueWithCallback(
const CxxModule::Callback& callback) {
callback({"value from callback!"});
}
void SampleTurboCxxModuleLegacyImpl::getValueWithPromise(
bool error,
const CxxModule::Callback& resolve,
const CxxModule::Callback& reject) {
if (!error) {
resolve({"result!"});
} else {
reject(
{folly::dynamic::object("message", "intentional promise rejection")});
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,46 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <cxxreact/CxxModule.h>
namespace facebook::react {
/**
* A sample CxxModule (legacy system) implementation.
*/
class SampleTurboCxxModuleLegacyImpl
: public facebook::xplat::module::CxxModule {
public:
SampleTurboCxxModuleLegacyImpl();
std::string getName() override;
std::map<std::string, folly::dynamic> getConstants() override;
std::vector<facebook::xplat::module::CxxModule::Method> getMethods() override;
// API
void voidFunc();
bool getBool(bool arg);
double getEnum(double arg);
double getNumber(double arg);
std::string getString(const std::string& arg);
folly::dynamic getArray(const folly::dynamic& arg);
folly::dynamic getObject(const folly::dynamic& arg);
folly::dynamic getUnsafeObject(const folly::dynamic& arg);
double getRootTag(double arg);
folly::dynamic
getValue(double x, const std::string& y, const folly::dynamic& z);
void getValueWithCallback(
const facebook::xplat::module::CxxModule::Callback& callback);
void getValueWithPromise(
bool error,
const facebook::xplat::module::CxxModule::Callback& resolve,
const facebook::xplat::module::CxxModule::Callback& reject);
};
} // namespace facebook::react