diff --git a/native/android/app/CMakeLists.txt b/native/android/app/CMakeLists.txt index b14aafdac..ced3321f2 100644 --- a/native/android/app/CMakeLists.txt +++ b/native/android/app/CMakeLists.txt @@ -1,245 +1,246 @@ # For more information about using CMake with Android Studio, read the # documentation: https://d.android.com/studio/projects/add-native-code.html project(comm CXX C) set(CMAKE_CXX_STANDARD 17) # C0103 is a naming convention, but the variable names which need to be set # are determined by the upstream project # cmake-lint: disable=C0103 # Disable line length as some paths are hard to reduce without becoming cryptic # cmake-lint: disable=C0301 # Sets the minimum version of CMake required to build the native library. cmake_minimum_required(VERSION 3.18) # Creates and names a library, sets it as either STATIC # or SHARED, and provides the relative paths to its source code. # You can define multiple libraries, and CMake builds them for you. # Gradle automatically packages shared libraries with your APK. set(PACKAGE_NAME "comm_jni_module") find_library(log-lib log) find_package(fbjni REQUIRED CONFIG) set(BUILD_TESTING OFF) set(HAVE_SYMBOLIZE OFF) set(WITH_GTEST OFF CACHE BOOL "Use googletest" FORCE) set(WITH_GFLAGS OFF CACHE BOOL "Use gflags" FORCE) # General set(_third_party_dir ${CMAKE_CURRENT_SOURCE_DIR}/build/third-party-ndk) set(_android_build_dir build/${CMAKE_ANDROID_ARCH_ABI}) include(FetchContent) if(CMAKE_ANDROID_ARCH_ABI STREQUAL arm64-v8a) set(Rust_CARGO_TARGET aarch64-linux-android) elseif(CMAKE_ANDROID_ARCH_ABI STREQUAL x86_64) set(Rust_CARGO_TARGET x86_64-linux-android) elseif(CMAKE_ANDROID_ARCH_ABI STREQUAL armeabi-v7a) set(Rust_CARGO_TARGET armv7-linux-androideabi) endif() string(TOLOWER ${CMAKE_HOST_SYSTEM_NAME} CMAKE_HOST_SYSTEM_NAME_LOWER) set(_toolchain_path "$ENV{ANDROID_HOME}/ndk/${NDK_VERSION}/toolchains/llvm/prebuilt/${CMAKE_HOST_SYSTEM_NAME_LOWER}-x86_64/bin" ) if(EXISTS "${_toolchain_path}/${Rust_CARGO_TARGET}-ar") set(AR "${_toolchain_path}/${Rust_CARGO_TARGET}-ar") else() set(AR "${_toolchain_path}/llvm-ar") endif() FetchContent_Declare( Corrosion GIT_REPOSITORY https://github.com/corrosion-rs/corrosion.git GIT_TAG v0.2.1 ) FetchContent_MakeAvailable(Corrosion) include(../../../shared/cmake/corrosion-cxx.cmake) add_library_rust( PATH ../../native_rust_library FEATURES android NAMESPACE comm ) # We're updating parameters below for Cmake's find_OpenSSL() function set(OPENSSL_ROOT_DIR "${_third_party_dir}/openssl/openssl-${OPENSSL_VERSION}/${_android_build_dir}" ) list(APPEND CMAKE_FIND_ROOT_PATH "${OPENSSL_ROOT_DIR}") # Override HAVE_EXECINFO_H in glog's CMakeLists.txt if( CMAKE_ANDROID_ARCH_ABI STREQUAL arm64-v8a OR CMAKE_ANDROID_ARCH_ABI STREQUAL armeabi-v7a ) set(HAVE_EXECINFO_H OFF CACHE BOOL "Whether platform has execinfo.h") endif() add_subdirectory(${_third_party_dir}/glog/glog-${GLOG_VERSION}/) file(GLOB LIBRN_DIR "${REACT_NATIVE_SO_DIR}/${ANDROID_ABI}") if (NOT LIBRN_DIR) # If /${ANDROID_ABI} dir not found, then ${REACT_NATIVE_SO_DIR} is probably: # ReactAndroid/build/react-ndk/exported file(GLOB LIBRN_DIR "${REACT_NATIVE_SO_DIR}") endif () include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/folly-target.cmake) include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/openssl-target.cmake) add_subdirectory(../../node_modules/olm ./build) set(_node_modules_dir ${CMAKE_CURRENT_SOURCE_DIR}/../../node_modules) set(_react_native_dir ${_node_modules_dir}/react-native) add_subdirectory(../../cpp/CommonCpp/ ${CMAKE_CURRENT_BINARY_DIR}/build/CommonCpp EXCLUDE_FROM_ALL ) file(GLOB SQLCIPHER "${_node_modules_dir}/@commapp/sqlcipher-amalgamation/src/*.c" ) # Add files which aren't a part of comm-tools list(APPEND ANDROID_NATIVE_CODE "./src/cpp/CommSecureStore.cpp" "./src/cpp/DatabaseInitializerJNIHelper.cpp" "./src/cpp/GlobalDBSingleton.cpp" "./src/cpp/Logger.cpp" "./src/cpp/MessageOperationsUtilitiesJNIHelper.cpp" "./src/cpp/PlatformSpecificTools.cpp" "./src/cpp/TerminateApp.cpp" "./src/cpp/ThreadOperationsJNIHelper.cpp" "./src/cpp/jsiInstaller.cpp" "./src/cpp/NotificationsCryptoModuleJNIHelper.cpp" "./src/cpp/StaffUtilsJNIHelper.cpp" "./src/cpp/AESCrypto.cpp" + "./src/cpp/CommServicesAuthMetadataEmitter.cpp" ) list(APPEND GENERATED_NATIVE_CODE "../../cpp/CommonCpp/_generated/commJSI-generated.cpp" "../../cpp/CommonCpp/_generated/utilsJSI-generated.cpp" "../../cpp/CommonCpp/_generated/rustJSI-generated.cpp" ) list(APPEND RUST_NATIVE_CODE "../../native_rust_library/RustCallback.cpp" "../../native_rust_library/RustAESCrypto.cpp" ) file(GLOB CRYPTO_NATIVE_CODE "../../cpp/CommonCpp/CryptoTools/*.cpp") file(GLOB DB_NATIVE_CODE "../../cpp/CommonCpp/DatabaseManagers/*.cpp") file(GLOB DB_ENTITIES_NATIVE_CODE "../../cpp/CommonCpp/DatabaseManagers/entities/*.cpp") file(GLOB_RECURSE MODULE_NATIVE_CODE "../../cpp/CommonCpp/NativeModules/**/*.cpp") file(GLOB MODULE_ROOT_NATIVE_CODE "../../cpp/CommonCpp/NativeModules/*.cpp") file(GLOB NOTIFICATIONS_NATIVE_CODE "../../cpp/CommonCpp/Notifications/**/*.cpp") add_library( # Sets the name of the library ${PACKAGE_NAME} # Sets the library as a shared library SHARED # React dependencies ${_react_native_dir}/ReactCommon/jsi/jsi/jsi.cpp ${_react_native_dir}/ReactCommon/jsi/jsi/JSIDynamic.cpp ${_react_native_dir}/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/ReactCommon/CallInvokerHolder.cpp ${_react_native_dir}/ReactCommon/react/nativemodule/core/ReactCommon/TurboModule.cpp ${_react_native_dir}/ReactCommon/react/bridging/LongLivedObject.cpp ${_react_native_dir}/ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleUtils.cpp # Third party dependencies ${SQLCIPHER} # comm code ${ANDROID_NATIVE_CODE} ${GENERATED_NATIVE_CODE} ${CRYPTO_NATIVE_CODE} ${DB_NATIVE_CODE} ${DB_ENTITIES_NATIVE_CODE} ${MODULE_NATIVE_CODE} ${MODULE_ROOT_NATIVE_CODE} ${TOOLS_NATIVE_CODE} ${NOTIFICATIONS_NATIVE_CODE} ${RUST_NATIVE_CODE} ) set(BUILD_DIR ${CMAKE_SOURCE_DIR}/build) target_include_directories( ${PACKAGE_NAME} PRIVATE # React Native ${_react_native_dir}/React ${_react_native_dir}/React/Base ${_react_native_dir}/ReactCommon ${_react_native_dir}/ReactCommon/jsi ${_react_native_dir}/ReactCommon/callinvoker ${_react_native_dir}/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/ReactCommon # SQLCipher amalgamation ${_node_modules_dir}/@commapp/sqlcipher-amalgamation/src # SQLite ORM ../../cpp/third-party/sqlite_orm # symlinked React Native headers ../headers # comm android specific code ./src/cpp # comm native mutual code ../../cpp/CommonCpp/ ../../cpp/CommonCpp/NativeModules ../../cpp/CommonCpp/NativeModules/InternalModules ../../cpp/CommonCpp/NativeModules/PersistentStorageUtilities ../../cpp/CommonCpp/NativeModules/PersistentStorageUtilities/DataStores ../../cpp/CommonCpp/NativeModules/PersistentStorageUtilities/ThreadOperationsUtilities ../../cpp/CommonCpp/NativeModules/PersistentStorageUtilities/MessageOperationsUtilities ../../cpp/CommonCpp/NativeModules/PersistentStorageUtilities/MessageOperationsUtilities/MessageSpecs ../../cpp/CommonCpp/DatabaseManagers ../../cpp/CommonCpp/Notifications ../../cpp/CommonCpp/Notifications/BackgroundDataStorage # native rust library ${native_rust_library_include_dir} ) add_definitions( # SQLCipher -DSQLITE_THREADSAFE=0 -DSQLITE_HAS_CODEC -DSQLITE_TEMP_STORE=2 -DSQLCIPHER_CRYPTO_OPENSSL ) target_link_libraries( ${PACKAGE_NAME} fbjni::fbjni android ${log-lib} Folly::folly glog::glog olm openssl-crypto openssl-ssl comm::native_rust_library comm-tools ) # add a dummy library which is required by CallInvokerHolderImpl.java add_library( turbomodulejsijni # Sets the library as a shared library. SHARED # Provides a relative path to your source file(s). ./src/cpp/dummy.cpp ) diff --git a/native/android/app/src/cpp/CommServicesAuthMetadataEmitter.cpp b/native/android/app/src/cpp/CommServicesAuthMetadataEmitter.cpp new file mode 100644 index 000000000..0f568fe09 --- /dev/null +++ b/native/android/app/src/cpp/CommServicesAuthMetadataEmitter.cpp @@ -0,0 +1,32 @@ +#include "jniHelpers.h" +#include +#include + +using namespace facebook::jni; + +class CommServicesAuthMetadataEmitterJavaClass + : public JavaClass { +public: + static auto constexpr kJavaDescriptor = + "Lapp/comm/android/commservices/CommServicesAuthMetadataEmitter;"; + + static void + sendAuthMetadataToJS(rust::String accessToken, rust::String userID) { + + static const auto cls = javaClassStatic(); + static auto method = cls->getStaticMethod( + "sendAuthMetadataToJS"); + method(cls, std::string(accessToken), std::string(userID)); + } +}; + +namespace comm { +void CommServicesAuthMetadataEmitter::sendAuthMetadataToJS( + rust::String accessToken, + rust::String userID) { + NativeAndroidAccessProvider::runTask([&]() { + CommServicesAuthMetadataEmitterJavaClass::sendAuthMetadataToJS( + accessToken, userID); + }); +} +} // namespace comm diff --git a/native/android/app/src/main/java/app/comm/android/MainApplication.java b/native/android/app/src/main/java/app/comm/android/MainApplication.java index 84c693636..428d42b56 100644 --- a/native/android/app/src/main/java/app/comm/android/MainApplication.java +++ b/native/android/app/src/main/java/app/comm/android/MainApplication.java @@ -1,124 +1,126 @@ package app.comm.android; import android.content.Context; import android.content.res.Configuration; import android.database.CursorWindow; import androidx.annotation.NonNull; import androidx.multidex.MultiDexApplication; +import app.comm.android.commservices.CommServicesPackage; import app.comm.android.fbjni.CommSecureStore; import app.comm.android.fbjni.DatabaseInitializer; import app.comm.android.fbjni.GlobalDBSingleton; import app.comm.android.newarchitecture.MainApplicationReactNativeHost; import app.comm.android.notifications.CommAndroidNotificationsPackage; import com.facebook.react.PackageList; import com.facebook.react.ReactApplication; import com.facebook.react.ReactInstanceManager; import com.facebook.react.ReactNativeHost; import com.facebook.react.ReactPackage; import com.facebook.react.bridge.JSIModulePackage; import com.facebook.react.config.ReactFeatureFlags; import com.facebook.soloader.SoLoader; import com.wix.reactnativekeyboardinput.KeyboardInputPackage; import expo.modules.ApplicationLifecycleDispatcher; import expo.modules.ReactNativeHostWrapper; import java.io.File; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.security.Security; import java.util.List; public class MainApplication extends MultiDexApplication implements ReactApplication { static { System.loadLibrary("fbjni"); System.loadLibrary("comm_jni_module"); } private static Context context; private final ReactNativeHost mReactNativeHost = new ReactNativeHostWrapper(this, new ReactNativeHost(this) { @Override public boolean getUseDeveloperSupport() { return BuildConfig.DEBUG; } @Override protected List getPackages() { @SuppressWarnings("UnnecessaryLocalVariable") List packages = new PackageList(this).getPackages(); packages.add(new KeyboardInputPackage(this.getApplication())); packages.add(new CommAndroidNotificationsPackage()); + packages.add(new CommServicesPackage()); return packages; } @Override protected String getJSMainModuleName() { return "index"; } @Override protected JSIModulePackage getJSIModulePackage() { return new CommCoreJSIModulePackage(); } }); private final ReactNativeHost mNewArchitectureNativeHost = new ReactNativeHostWrapper( this, new MainApplicationReactNativeHost(this)); @Override public ReactNativeHost getReactNativeHost() { if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { return mNewArchitectureNativeHost; } else { return mReactNativeHost; } } @Override public void onCreate() { super.onCreate(); // If you opted-in for the New Architecture, we enable the TurboModule // system MainApplication.context = this.getApplicationContext(); ReactFeatureFlags.useTurboModules = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED; Security.insertProviderAt(new org.conscrypt.OpenSSLProvider(), 1); SoLoader.init(this, /* native exopackage */ false); this.initializeDatabase(); ApplicationLifecycleDispatcher.onApplicationCreate(this); try { Field field = CursorWindow.class.getDeclaredField("sCursorWindowSize"); field.setAccessible(true); field.set(null, 100 * 1024 * 1024); // 100 MiB } catch (Exception e) { if (BuildConfig.DEBUG) { e.printStackTrace(); } } } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig); } public static Context getMainApplicationContext() { return MainApplication.context; } private void initializeDatabase() { File sqliteFile = this.getApplicationContext().getDatabasePath("comm.sqlite"); CommSecureStore.getInstance().initialize( ExpoUtils.createExpoSecureStoreSupplier(this.getApplicationContext())); GlobalDBSingleton.scheduleOrRun(() -> { DatabaseInitializer.initializeDatabaseManager(sqliteFile.getPath()); }); } } diff --git a/native/android/app/src/main/java/app/comm/android/commservices/CommServicesAuthMetadataEmitter.java b/native/android/app/src/main/java/app/comm/android/commservices/CommServicesAuthMetadataEmitter.java new file mode 100644 index 000000000..9bc5151ba --- /dev/null +++ b/native/android/app/src/main/java/app/comm/android/commservices/CommServicesAuthMetadataEmitter.java @@ -0,0 +1,76 @@ +package app.comm.android.commservices; + +import android.util.Log; +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.modules.core.DeviceEventManagerModule; +import java.util.HashMap; +import java.util.Map; + +public class CommServicesAuthMetadataEmitter + extends ReactContextBaseJavaModule { + + private static int listenersCount = 0; + private static CommServicesAuthMetadataEmitter sharedInstance = null; + + public static final String COMM_SERVICES_AUTH_METADATA = + "commServicesAuthMetadata"; + + CommServicesAuthMetadataEmitter(ReactApplicationContext reactContext) { + super(reactContext); + } + + @Override + public String getName() { + return "CommServicesAuthMetadataEmitter"; + } + + @ReactMethod + public void addListener(String eventName) { + synchronized (CommServicesAuthMetadataEmitter.class) { + listenersCount++; + if (sharedInstance == null) { + sharedInstance = this; + } + } + } + + @ReactMethod + public void removeListeners(Integer count) { + synchronized (CommServicesAuthMetadataEmitter.class) { + listenersCount -= count; + boolean isLastListener = listenersCount == 0; + if (isLastListener) { + sharedInstance = null; + } + } + } + + public static void sendAuthMetadataToJS(String accessToken, String userID) { + synchronized (CommServicesAuthMetadataEmitter.class) { + if (sharedInstance == null) { + return; + } + + // Event body must match UserLoginResponse + // type from 'lib/types/identity-service-types.js' + WritableMap eventBody = Arguments.createMap(); + eventBody.putString("accessToken", accessToken); + eventBody.putString("userID", userID); + + sharedInstance.getReactApplicationContext() + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + .emit(COMM_SERVICES_AUTH_METADATA, eventBody); + } + } + + @Override + public Map getConstants() { + final Map constants = new HashMap<>(); + constants.put("COMM_SERVICES_AUTH_METADATA", COMM_SERVICES_AUTH_METADATA); + return constants; + } +} diff --git a/native/android/app/src/main/java/app/comm/android/commservices/CommServicesPackage.java b/native/android/app/src/main/java/app/comm/android/commservices/CommServicesPackage.java new file mode 100644 index 000000000..23c90be61 --- /dev/null +++ b/native/android/app/src/main/java/app/comm/android/commservices/CommServicesPackage.java @@ -0,0 +1,27 @@ +package app.comm.android.commservices; + +import com.facebook.react.ReactPackage; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.uimanager.ViewManager; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class CommServicesPackage implements ReactPackage { + @Override + public List + createViewManagers(ReactApplicationContext reactContext) { + return Collections.emptyList(); + } + + @Override + public List + createNativeModules(ReactApplicationContext reactContext) { + List modules = new ArrayList<>(); + + modules.add(new CommServicesAuthMetadataEmitter(reactContext)); + + return modules; + } +}