diff --git a/native/ios/Comm-Bridging-Header.h b/native/ios/Comm-Bridging-Header.h --- a/native/ios/Comm-Bridging-Header.h +++ b/native/ios/Comm-Bridging-Header.h @@ -2,3 +2,7 @@ // Use this file to import your target's public headers that you would like to // expose to Swift. // +#import "CommIOSNotifications.h" +#import "Comm/DBInit.h" +#import "Comm/Tools.h" +#import "CommIOSServicesClient.h" \ No newline at end of file diff --git a/native/ios/Comm.xcodeproj/project.pbxproj b/native/ios/Comm.xcodeproj/project.pbxproj --- a/native/ios/Comm.xcodeproj/project.pbxproj +++ b/native/ios/Comm.xcodeproj/project.pbxproj @@ -8,9 +8,7 @@ /* Begin PBXBuildFile section */ 0E02677D2D81ED6600788249 /* DMOperationStore.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0E02677C2D81ED6600788249 /* DMOperationStore.cpp */; }; - 13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; - 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; 1F537ACC7B60DC049C0ECFA7 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 769A87FB41BCE3FEF97FD59A /* ExpoModulesProvider.swift */; }; 34055C152BAD31AC0008E713 /* SyncedMetadataStore.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34055C142BAD31AC0008E713 /* SyncedMetadataStore.cpp */; }; 34329B442B9EC7EC00233438 /* IntegrityStore.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34329B3F2B9EBFCE00233438 /* IntegrityStore.cpp */; }; @@ -19,6 +17,8 @@ 443650412E21F3F200026241 /* StringUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 443650402E21F3F200026241 /* StringUtils.cpp */; }; 443650422E21F3F200026241 /* StringUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 443650402E21F3F200026241 /* StringUtils.cpp */; }; 443650452E21F47600026241 /* CommInitializerModule.mm in Sources */ = {isa = PBXBuildFile; fileRef = 443650442E21F47600026241 /* CommInitializerModule.mm */; }; + 44F116742DE474D30027DDA6 /* DBInit.mm in Sources */ = {isa = PBXBuildFile; fileRef = 44F116732DE474D30027DDA6 /* DBInit.mm */; }; + 44F116752DE474D30027DDA6 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44F116712DE474D30027DDA6 /* AppDelegate.swift */; }; 71142A7726C2650B0039DCBD /* CommSecureStoreIOSWrapper.mm in Sources */ = {isa = PBXBuildFile; fileRef = 71142A7626C2650A0039DCBD /* CommSecureStoreIOSWrapper.mm */; }; 711B408425DA97F9005F8F06 /* dummy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F26E81B24440D87004049C6 /* dummy.swift */; }; 71762A75270D8AAE00F565ED /* PlatformSpecificTools.mm in Sources */ = {isa = PBXBuildFile; fileRef = 71762A74270D8AAE00F565ED /* PlatformSpecificTools.mm */; }; @@ -164,11 +164,8 @@ 0E02677E2D81ED7B00788249 /* DMOperationStoreOperations.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DMOperationStoreOperations.h; sourceTree = ""; }; 0E0267862D970E9500788249 /* DeleteMessageSpec.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = DeleteMessageSpec.h; path = PersistentStorageUtilities/MessageOperationsUtilities/MessageSpecs/DeleteMessageSpec.h; sourceTree = ""; }; 13B07F961A680F5B00A75B9A /* Comm.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Comm.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = Comm/AppDelegate.h; sourceTree = ""; }; - 13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = Comm/AppDelegate.mm; sourceTree = ""; }; 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = Comm/Images.xcassets; sourceTree = ""; }; 13B07FB61A68108700A75B9A /* Info.release.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.release.plist; path = Comm/Info.release.plist; sourceTree = ""; }; - 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = Comm/main.m; sourceTree = ""; }; 2DDA0A22FECC9DAA5C19C35D /* Metadata.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Metadata.h; sourceTree = ""; }; 34055C132BAD31AB0008E713 /* SyncedMetadataStore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SyncedMetadataStore.h; path = PersistentStorageUtilities/DataStores/SyncedMetadataStore.h; sourceTree = ""; }; 34055C142BAD31AC0008E713 /* SyncedMetadataStore.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SyncedMetadataStore.cpp; path = PersistentStorageUtilities/DataStores/SyncedMetadataStore.cpp; sourceTree = ""; }; @@ -189,6 +186,9 @@ 443650402E21F3F200026241 /* StringUtils.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = StringUtils.cpp; sourceTree = ""; }; 443650432E21F47600026241 /* CommInitializerModule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CommInitializerModule.h; sourceTree = ""; }; 443650442E21F47600026241 /* CommInitializerModule.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = CommInitializerModule.mm; sourceTree = ""; }; + 44F116712DE474D30027DDA6 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = Comm/AppDelegate.swift; sourceTree = ""; }; + 44F116722DE474D30027DDA6 /* DBInit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = DBInit.h; path = Comm/DBInit.h; sourceTree = ""; }; + 44F116732DE474D30027DDA6 /* DBInit.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = DBInit.mm; path = Comm/DBInit.mm; sourceTree = ""; }; 71142A7526C2650A0039DCBD /* CommSecureStoreIOSWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CommSecureStoreIOSWrapper.h; path = Comm/CommSecureStoreIOSWrapper.h; sourceTree = ""; }; 71142A7626C2650A0039DCBD /* CommSecureStoreIOSWrapper.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = CommSecureStoreIOSWrapper.mm; path = Comm/CommSecureStoreIOSWrapper.mm; sourceTree = ""; }; 711CF80E25DC096000A00FBD /* libFolly.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libFolly.a; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -432,12 +432,12 @@ 71B8CCB626BD30EC0040C0A2 /* CommCoreImplementations */, 7F788C2B248AA2130098F071 /* SplashScreen.storyboard */, 7FCFD8BD1E81B8DF00629B0E /* Comm.entitlements */, - 13B07FAF1A68108700A75B9A /* AppDelegate.h */, - 13B07FB01A68108700A75B9A /* AppDelegate.mm */, 13B07FB51A68108700A75B9A /* Images.xcassets */, 7F554F822332D58B007CB9F7 /* Info.debug.plist */, 13B07FB61A68108700A75B9A /* Info.release.plist */, - 13B07FB71A68108700A75B9A /* main.m */, + 44F116712DE474D30027DDA6 /* AppDelegate.swift */, + 44F116722DE474D30027DDA6 /* DBInit.h */, + 44F116732DE474D30027DDA6 /* DBInit.mm */, 7FCEA2DC2444010B004017B1 /* Comm-Bridging-Header.h */, 7F26E81B24440D87004049C6 /* dummy.swift */, ); @@ -1398,8 +1398,9 @@ 8E2CC2592B5C99B0000C94D6 /* KeyserverStore.cpp in Sources */, DFD5E77C2B05181400C32B6A /* RustSecureStore.cpp in Sources */, 71BF5B7526B401D300EDE27D /* Tools.cpp in Sources */, - 13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */, 7FE4D9F5291DFE9300667BF6 /* commJSI-generated.cpp in Sources */, + 44F116742DE474D30027DDA6 /* DBInit.mm in Sources */, + 44F116752DE474D30027DDA6 /* AppDelegate.swift in Sources */, 8B652FA6295EAA5B009F8163 /* RustCallback.cpp in Sources */, 71142A7726C2650B0039DCBD /* CommSecureStoreIOSWrapper.mm in Sources */, 7FBB2A7B29EEA2A4002C6493 /* Base64.cpp in Sources */, @@ -1425,7 +1426,6 @@ 443650452E21F47600026241 /* CommInitializerModule.mm in Sources */, DFD5E77E2B05264000C32B6A /* AESCrypto.mm in Sources */, 8EA59BD62A6E8E0400EB4F53 /* DraftStore.cpp in Sources */, - 13B07FC11A68108700A75B9A /* main.m in Sources */, 71BE844B2636A944002849D2 /* SQLiteQueryExecutor.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/native/ios/Comm/AppDelegate.h b/native/ios/Comm/AppDelegate.h deleted file mode 100644 --- a/native/ios/Comm/AppDelegate.h +++ /dev/null @@ -1,10 +0,0 @@ -#import -#import -#import - -@interface AppDelegate - : EXAppDelegateWrapper - -@property(nonatomic, strong) UIWindow *window; - -@end diff --git a/native/ios/Comm/AppDelegate.mm b/native/ios/Comm/AppDelegate.mm deleted file mode 100644 --- a/native/ios/Comm/AppDelegate.mm +++ /dev/null @@ -1,456 +0,0 @@ -#import "AppDelegate.h" - -#import -#import -#import - -#import - -#if RCT_NEW_ARCH_ENABLED -#import -#import -#import -#import -#import -#import -#import -static NSString *const kRNConcurrentRoot = @"concurrentRoot"; -@interface AppDelegate () < - RCTCxxBridgeDelegate, - RCTTurboModuleManagerDelegate> { - RCTTurboModuleManager *_turboModuleManager; - RCTSurfacePresenterBridgeAdapter *_bridgeAdapter; - std::shared_ptr _reactNativeConfig; - facebook::react::ContextContainer::Shared _contextContainer; -} -@end -#endif - -#import "CommIOSNotifications.h" -#import "Orientation.h" -#import - -#import -#import -#import -#import -#import -#import -#import - -#import "CommConstants.h" -#import "CommCoreModule.h" -#import "CommIOSServicesClient.h" -#import "CommMMKV.h" -#import "CommRustModule.h" -#import "CommUtilsModule.h" -#import "GlobalDBSingleton.h" -#import "Logger.h" -#import "MessageOperationsUtilities.h" -#import "TemporaryMessageStorage.h" -#import "ThreadOperations.h" -#import "Tools.h" -#import -#import -#import - -#import - -#import - -#import - -NSString *const setUnreadStatusKey = @"setUnreadStatus"; -NSString *const threadIDKey = @"threadID"; -NSString *const newMessageInfosNSNotification = - @"app.comm.ns_new_message_infos"; -CFStringRef newMessageInfosDarwinNotification = - CFSTR("app.comm.darwin_new_message_infos"); - -void didReceiveNewMessageInfosDarwinNotification( - CFNotificationCenterRef center, - void *observer, - CFStringRef name, - const void *object, - CFDictionaryRef userInfo) { - [[NSNotificationCenter defaultCenter] - postNotificationName:newMessageInfosNSNotification - object:nil]; -} - -@interface AppDelegate () < - RCTCxxBridgeDelegate, - RCTTurboModuleManagerDelegate> { -} -@end - -@implementation AppDelegate - -- (BOOL)application:(UIApplication *)application - willFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - [self attemptDatabaseInitialization]; - [self registerForNewMessageInfosNotifications]; - comm::CommMMKV::initialize(); - return YES; -} - -- (BOOL)application:(UIApplication *)application - didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - RCTAppSetupPrepareApp(application); - - [self moveMessagesToDatabase:NO]; - [self scheduleNSEBlobsDeletion]; - [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient - error:nil]; - - RCTBridge *bridge = - [self.reactDelegate createBridgeWithDelegate:self - launchOptions:launchOptions]; - -#if RCT_NEW_ARCH_ENABLED - _contextContainer = - std::make_shared(); - _reactNativeConfig = - std::make_shared(); - _contextContainer->insert("ReactNativeConfig", _reactNativeConfig); - _bridgeAdapter = [[RCTSurfacePresenterBridgeAdapter alloc] - initWithBridge:bridge - contextContainer:_contextContainer]; - bridge.surfacePresenter = _bridgeAdapter.surfacePresenter; -#endif - - NSDictionary *initProps = [self prepareInitialProps]; - UIView *rootView = [self.reactDelegate createRootViewWithBridge:bridge - moduleName:@"Comm" - initialProperties:initProps]; - - if (@available(iOS 13.0, *)) { - rootView.backgroundColor = [UIColor systemBackgroundColor]; - } else { - rootView.backgroundColor = [UIColor whiteColor]; - } - - self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; - UIViewController *rootViewController = - [self.reactDelegate createRootViewController]; - rootViewController.view = rootView; - self.window.rootViewController = rootViewController; - [self.window makeKeyAndVisible]; - [super application:application didFinishLaunchingWithOptions:launchOptions]; - - // This prevents a very small flicker from occurring before expo-splash-screen - // is able to display - UIView *launchScreenView = - [[UIStoryboard storyboardWithName:@"SplashScreen" - bundle:nil] instantiateInitialViewController] - .view; - launchScreenView.frame = self.window.bounds; - - ((RCTRootView *)rootView).loadingView = launchScreenView; - ((RCTRootView *)rootView).loadingViewFadeDelay = 0; - ((RCTRootView *)rootView).loadingViewFadeDuration = 0.001; - - return YES; -} - -- (BOOL)application:(UIApplication *)application - openURL:(NSURL *)url - options: - (NSDictionary *)options { - return [RCTLinkingManager application:application - openURL:url - options:options]; -} - -- (BOOL)application:(UIApplication *)application - continueUserActivity:(nonnull NSUserActivity *)userActivity - restorationHandler: - (nonnull void (^)(NSArray> *_Nullable)) - restorationHandler { - return [RCTLinkingManager application:application - continueUserActivity:userActivity - restorationHandler:restorationHandler]; -} - -- (NSArray> *)extraModulesForBridge:(RCTBridge *)bridge { - // If you'd like to export some custom RCTBridgeModules that are not Expo - // modules, add them here! - return @[]; -} - -/// This method controls whether the `concurrentRoot`feature of React18 is -/// turned on or off. -/// -/// @see: https://reactjs.org/blog/2022/03/29/react-v18.html -/// @note: This requires to be rendering on Fabric (i.e. on the New -/// Architecture). -/// @return: `true` if the `concurrentRoot` feture is enabled. Otherwise, it -/// returns `false`. -- (BOOL)concurrentRootEnabled { - // Switch this bool to turn on and off the concurrent root - return true; -} -- (NSDictionary *)prepareInitialProps { - NSMutableDictionary *initProps = [NSMutableDictionary new]; -#ifdef RCT_NEW_ARCH_ENABLED - initProps[kRNConcurrentRoot] = @([self concurrentRootEnabled]); -#endif - return initProps; -} - -- (void)application:(UIApplication *)application - didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { - [CommIOSNotifications - didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; -} - -- (void)application:(UIApplication *)application - didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { - [CommIOSNotifications didFailToRegisterForRemoteNotificationsWithError:error]; -} - -// Required for the notification event. You must call the completion handler -// after handling the remote notification. -- (void)application:(UIApplication *)application - didReceiveRemoteNotification:(NSDictionary *)notification - fetchCompletionHandler: - (void (^)(UIBackgroundFetchResult))completionHandler { - - [CommIOSNotifications didReceiveRemoteNotification:notification - fetchCompletionHandler:completionHandler]; -} - -- (UIInterfaceOrientationMask)application:(UIApplication *)application - supportedInterfaceOrientationsForWindow:(UIWindow *)window { - return [Orientation getOrientation]; -} - -- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge { -#if DEBUG - return - [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"]; -#else - return [[NSBundle mainBundle] URLForResource:@"main" - withExtension:@"jsbundle"]; -#endif -} - -#if RCT_NEW_ARCH_ENABLED -#pragma mark - RCTCxxBridgeDelegate -- (std::unique_ptr) - jsExecutorFactoryForBridge:(RCTBridge *)bridge { - _turboModuleManager = - [[RCTTurboModuleManager alloc] initWithBridge:bridge - delegate:self - jsInvoker:bridge.jsCallInvoker]; - return RCTAppSetupDefaultJsExecutorFactory(bridge, _turboModuleManager); -} -#pragma mark RCTTurboModuleManagerDelegate -- (Class)getModuleClassFromName:(const char *)name { - return RCTCoreModulesClassProvider(name); -} -- (std::shared_ptr) - getTurboModule:(const std::string &)name - jsInvoker:(std::shared_ptr)jsInvoker { - return nullptr; -} -- (std::shared_ptr) - getTurboModule:(const std::string &)name - initParams: - (const facebook::react::ObjCTurboModule::InitParams &)params { - return nullptr; -} -- (id)getModuleInstanceFromClass:(Class)moduleClass { - return RCTAppSetupDefaultModuleFromClass(moduleClass); -} -#endif - -using JSExecutorFactory = facebook::react::JSExecutorFactory; -using HermesExecutorFactory = facebook::react::HermesExecutorFactory; -using Runtime = facebook::jsi::Runtime; - -- (std::unique_ptr)jsExecutorFactoryForBridge: - (RCTBridge *)bridge { - __weak __typeof(self) weakSelf = self; - - const auto commRuntimeInstaller = [weakSelf, - bridge](facebook::jsi::Runtime &rt) { - if (!bridge) { - return; - } - __typeof(self) strongSelf = weakSelf; - if (strongSelf) { - std::shared_ptr coreNativeModule = - std::make_shared(bridge.jsCallInvoker); - std::shared_ptr utilsNativeModule = - std::make_shared(bridge.jsCallInvoker); - std::shared_ptr rustNativeModule = - std::make_shared(bridge.jsCallInvoker); - std::shared_ptr nativeConstants = - std::make_shared(); - - rt.global().setProperty( - rt, - facebook::jsi::PropNameID::forAscii(rt, "CommCoreModule"), - facebook::jsi::Object::createFromHostObject(rt, coreNativeModule)); - rt.global().setProperty( - rt, - facebook::jsi::PropNameID::forAscii(rt, "CommUtilsModule"), - facebook::jsi::Object::createFromHostObject(rt, utilsNativeModule)); - rt.global().setProperty( - rt, - facebook::jsi::PropNameID::forAscii(rt, "CommRustModule"), - facebook::jsi::Object::createFromHostObject(rt, rustNativeModule)); - rt.global().setProperty( - rt, - facebook::jsi::PropNameID::forAscii(rt, "CommConstants"), - facebook::jsi::Object::createFromHostObject(rt, nativeConstants)); - } - }; - const auto installer = - reanimated::REAJSIExecutorRuntimeInstaller(bridge, commRuntimeInstaller); - - return std::make_unique( - facebook::react::RCTJSIExecutorRuntimeInstaller(installer), - JSIExecutor::defaultTimeoutInvoker, - makeRuntimeConfig(3072)); -} - -- (void)attemptDatabaseInitialization { - std::string sqliteFilePath = - std::string([[Tools getSQLiteFilePath] UTF8String]); - // Previous Comm versions used app group location for SQLite - // database, so that NotificationService was able to acces it directly. - // Unfortunately it caused errores related to system locks. The code - // below re-migrates SQLite from app group to app specific location - // on devices where previous Comm version was installed. - NSString *appGroupSQLiteFilePath = [Tools getAppGroupSQLiteFilePath]; - if ([NSFileManager.defaultManager fileExistsAtPath:appGroupSQLiteFilePath] && - std::rename( - std::string([appGroupSQLiteFilePath UTF8String]).c_str(), - sqliteFilePath.c_str())) { - throw std::runtime_error( - "Failed to move SQLite database from app group to default location"); - } - comm::GlobalDBSingleton::instance.scheduleOrRun([&sqliteFilePath]() { - comm::DatabaseManager::initializeQueryExecutor(sqliteFilePath); - }); -} - -- (void)moveMessagesToDatabase:(BOOL)sendBackgroundMessagesInfosToJS { - TemporaryMessageStorage *temporaryStorage = - [[TemporaryMessageStorage alloc] init]; - NSArray *messages = [temporaryStorage readAndClearMessages]; - - if (sendBackgroundMessagesInfosToJS && messages && messages.count) { - [CommIOSNotifications - didReceiveBackgroundMessageInfos:@{@"messageInfosArray" : messages}]; - } - - for (NSString *message in messages) { - std::string messageInfos = std::string([message UTF8String]); - comm::GlobalDBSingleton::instance.scheduleOrRun([messageInfos]() mutable { - comm::MessageOperationsUtilities::storeMessageInfos(messageInfos); - }); - } - - TemporaryMessageStorage *temporaryRescindsStorage = - [[TemporaryMessageStorage alloc] initForRescinds]; - NSArray *rescindMessages = - [temporaryRescindsStorage readAndClearMessages]; - for (NSString *rescindMessage in rescindMessages) { - NSData *binaryRescindMessage = - [rescindMessage dataUsingEncoding:NSUTF8StringEncoding]; - - NSError *jsonError = nil; - NSDictionary *rescindPayload = - [NSJSONSerialization JSONObjectWithData:binaryRescindMessage - options:0 - error:&jsonError]; - if (jsonError) { - comm::Logger::log( - "Failed to deserialize persisted rescind payload. Details: " + - std::string([jsonError.localizedDescription UTF8String])); - continue; - } - - if (!(rescindPayload[setUnreadStatusKey] && rescindPayload[threadIDKey])) { - continue; - } - - std::string threadID = - std::string([rescindPayload[threadIDKey] UTF8String]); - comm::GlobalDBSingleton::instance.scheduleOrRun([threadID]() mutable { - comm::ThreadOperations::updateSQLiteUnreadStatus(threadID, false); - }); - } -} - -- (void)didReceiveNewMessageInfosNSNotification:(NSNotification *)notification { - [self moveMessagesToDatabase:YES]; - [self scheduleNSEBlobsDeletion]; -} - -- (void)registerForNewMessageInfosNotifications { - [[NSNotificationCenter defaultCenter] - addObserver:self - selector:@selector(didReceiveNewMessageInfosNSNotification:) - name:newMessageInfosNSNotification - object:nil]; - - CFNotificationCenterAddObserver( - CFNotificationCenterGetDarwinNotifyCenter(), - (__bridge const void *)(self), - didReceiveNewMessageInfosDarwinNotification, - newMessageInfosDarwinNotification, - NULL, - CFNotificationSuspensionBehaviorDeliverImmediately); -} - -// NSE has limited time to process notifications. Therefore -// deferable and low priority networking such as fetched -// blob deletion from blob service should be handled by the -// main app on a low priority background thread. - -- (void)scheduleNSEBlobsDeletion { - dispatch_async( - dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ - [CommIOSServicesClient.sharedInstance deleteStoredBlobs]; - }); -} - -- (void)applicationWillResignActive:(UIApplication *)application { - [[CommIOSServicesClient sharedInstance] cancelOngoingRequests]; -} - -// Copied from -// ReactAndroid/src/main/java/com/facebook/hermes/reactexecutor/OnLoad.cpp -static ::hermes::vm::RuntimeConfig -makeRuntimeConfig(::hermes::vm::gcheapsize_t heapSizeMB) { - namespace vm = ::hermes::vm; - auto gcConfigBuilder = - vm::GCConfig::Builder() - .withName("RN") - // For the next two arguments: avoid GC before TTI by initializing the - // runtime to allocate directly in the old generation, but revert to - // normal operation when we reach the (first) TTI point. - .withAllocInYoung(false) - .withRevertToYGAtTTI(true); - - if (heapSizeMB > 0) { - gcConfigBuilder.withMaxHeapSize(heapSizeMB << 20); - } - -#if DEBUG - return vm::RuntimeConfig::Builder() - .withGCConfig(gcConfigBuilder.build()) - .withEnableSampleProfiling(true) - .build(); -#else - return vm::RuntimeConfig::Builder() - .withGCConfig(gcConfigBuilder.build()) - .build(); -#endif -} - -@end diff --git a/native/ios/Comm/AppDelegate.swift b/native/ios/Comm/AppDelegate.swift new file mode 100644 --- /dev/null +++ b/native/ios/Comm/AppDelegate.swift @@ -0,0 +1,175 @@ +import Expo +import React +import ReactAppDependencyProvider +import Foundation + +let newMessageInfosNSNotification = Notification.Name("app.comm.ns_new_message_infos") +let newMessageInfosDarwinNotification: CFString = ("app.comm.darwin_new_message_infos") as CFString + +func didReceiveNewMessageInfosDarwinNotification( + center: CFNotificationCenter?, + observer: UnsafeMutableRawPointer?, + name: CFNotificationName?, + object: UnsafeRawPointer?, + userInfo: CFDictionary? +) { + NotificationCenter.default.post(name: newMessageInfosNSNotification, object: nil) +} + +@UIApplicationMain +public class AppDelegate: ExpoAppDelegate { + var window: UIWindow? + + var reactNativeDelegate: ExpoReactNativeFactoryDelegate? + var reactNativeFactory: RCTReactNativeFactory? + + public override func application( + _ application: UIApplication, + willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil + ) -> Bool { + DBInit.attemptDatabaseInitialization() + registerForNewMessageInfosNotifications() + DBInit.initMMKV() + return true + } + + public override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil + ) -> Bool { + DBInit.moveMessages(toDatabase: false) + scheduleNSEBlobsDeletion(); + do { + try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.ambient) + } catch { + + } + + let delegate = ReactNativeDelegate() + let factory = ExpoReactNativeFactory(delegate: delegate) + delegate.dependencyProvider = RCTAppDependencyProvider() + + reactNativeDelegate = delegate + reactNativeFactory = factory + bindReactNativeFactory(factory) + +#if os(iOS) || os(tvOS) + window = UIWindow(frame: UIScreen.main.bounds) + factory.startReactNative( + withModuleName: "Comm", + in: window, + launchOptions: launchOptions) +#endif + + if let launchScreenView = UIStoryboard.init(name: "SplashScreen", bundle: nil).instantiateInitialViewController()?.view { + if let bounds = window?.bounds { + launchScreenView.frame = bounds; + } + if let rootView = (window?.rootViewController?.view as? RCTRootView) { + rootView.backgroundColor = UIColor.systemBackground + rootView.loadingView = launchScreenView + rootView.loadingViewFadeDelay = 0 + rootView.loadingViewFadeDuration = 0.001 + } + } + + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } + + // Linking API + public override func application( + _ app: UIApplication, + open url: URL, + options: [UIApplication.OpenURLOptionsKey: Any] = [:] + ) -> Bool { + return super.application(app, open: url, options: options) || RCTLinkingManager.application(app, open: url, options: options) + } + + // Universal Links + public override func application( + _ application: UIApplication, + continue userActivity: NSUserActivity, + restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void + ) -> Bool { + let result = RCTLinkingManager.application(application, continue: userActivity, restorationHandler: restorationHandler) + return super.application(application, continue: userActivity, restorationHandler: restorationHandler) || result + } + + public override func application( + _ application: UIApplication, + didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data + ) { + CommIOSNotifications.didRegisterForRemoteNotifications(withDeviceToken: deviceToken) + } + + public override func application( + _ application: UIApplication, + didFailToRegisterForRemoteNotificationsWithError error: Error + ) { + CommIOSNotifications.didFailToRegisterForRemoteNotificationsWithError(error) + } + + // Required for the notification event. You must call the completion handler + // after handling the remote notification. + public override func application( + _ application: UIApplication, + didReceiveRemoteNotification notification: [AnyHashable : Any], + fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void + ) { + CommIOSNotifications.didReceiveRemoteNotification(notification, fetchCompletionHandler: completionHandler) + } + + @objc private func didReceiveNewMessageInfosNSNotification(notification: Notification) { + DBInit.moveMessages(toDatabase: true) + scheduleNSEBlobsDeletion() + } + + private func registerForNewMessageInfosNotifications() { + NotificationCenter.default.addObserver( + self, + selector: #selector(self.didReceiveNewMessageInfosNSNotification(notification:)), + name: newMessageInfosNSNotification, + object: nil + ) + + CFNotificationCenterAddObserver( + CFNotificationCenterGetDarwinNotifyCenter(), + Unmanaged.passUnretained(self).toOpaque(), + didReceiveNewMessageInfosDarwinNotification, + newMessageInfosDarwinNotification, + nil, + CFNotificationSuspensionBehavior.deliverImmediately + ) + } + + // NSE has limited time to process notifications. Therefore + // deferable and low priority networking such as fetched + // blob deletion from blob service should be handled by the + // main app on a low priority background thread. + private func scheduleNSEBlobsDeletion() { + DispatchQueue.global(qos: .background).async { + (CommIOSServicesClient.sharedInstance() as? CommIOSServicesClient)?.deleteStoredBlobs() + } + } + + public override func applicationWillResignActive(_ application: UIApplication) { + (CommIOSServicesClient.sharedInstance() as? CommIOSServicesClient)?.cancelOngoingRequests() + } +} + +class ReactNativeDelegate: ExpoReactNativeFactoryDelegate { + // Extension point for config-plugins + + override func sourceURL(for bridge: RCTBridge) -> URL? { + // needed to return the correct URL for expo-dev-client. + bridge.bundleURL ?? bundleURL() + } + + override func bundleURL() -> URL? { +#if DEBUG + return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: ".expo/.virtual-metro-entry") +#else + return Bundle.main.url(forResource: "main", withExtension: "jsbundle") +#endif + } +} diff --git a/native/ios/Comm/DBInit.h b/native/ios/Comm/DBInit.h new file mode 100644 --- /dev/null +++ b/native/ios/Comm/DBInit.h @@ -0,0 +1,9 @@ +#import + +@interface DBInit : NSObject + ++ (void)attemptDatabaseInitialization; ++ (void)moveMessagesToDatabase:(BOOL)sendBackgroundMessagesInfosToJS; ++ (void)initMMKV; + +@end diff --git a/native/ios/Comm/DBInit.mm b/native/ios/Comm/DBInit.mm new file mode 100644 --- /dev/null +++ b/native/ios/Comm/DBInit.mm @@ -0,0 +1,98 @@ +#import "DBInit.h" +#import "CommConstants.h" +#import "CommCoreModule.h" +#import "CommIOSNotifications.h" +#import "CommIOSServicesClient.h" +#import "CommMMKV.h" +#import "CommRustModule.h" +#import "CommUtilsModule.h" +#import "GlobalDBSingleton.h" +#import "Logger.h" +#import "MessageOperationsUtilities.h" +#import "TemporaryMessageStorage.h" +#import "ThreadOperations.h" +#import "Tools.h" +#import +#import +#import + +NSString *const setUnreadStatusKey = @"setUnreadStatus"; +NSString *const threadIDKey = @"threadID"; + +@implementation DBInit + ++ (void)attemptDatabaseInitialization { + std::string sqliteFilePath = + std::string([[Tools getSQLiteFilePath] UTF8String]); + // Previous Comm versions used app group location for SQLite + // database, so that NotificationService was able to acces it directly. + // Unfortunately it caused errores related to system locks. The code + // below re-migrates SQLite from app group to app specific location + // on devices where previous Comm version was installed. + NSString *appGroupSQLiteFilePath = [Tools getAppGroupSQLiteFilePath]; + if ([NSFileManager.defaultManager fileExistsAtPath:appGroupSQLiteFilePath] && + std::rename( + std::string([appGroupSQLiteFilePath UTF8String]).c_str(), + sqliteFilePath.c_str())) { + throw std::runtime_error( + "Failed to move SQLite database from app group to default location"); + } + comm::GlobalDBSingleton::instance.scheduleOrRun([&sqliteFilePath]() { + comm::DatabaseManager::initializeQueryExecutor(sqliteFilePath); + }); +} + ++ (void)moveMessagesToDatabase:(BOOL)sendBackgroundMessagesInfosToJS { + TemporaryMessageStorage *temporaryStorage = + [[TemporaryMessageStorage alloc] init]; + NSArray *messages = [temporaryStorage readAndClearMessages]; + + if (sendBackgroundMessagesInfosToJS && messages && messages.count) { + [CommIOSNotifications + didReceiveBackgroundMessageInfos:@{@"messageInfosArray" : messages}]; + } + + for (NSString *message in messages) { + std::string messageInfos = std::string([message UTF8String]); + comm::GlobalDBSingleton::instance.scheduleOrRun([messageInfos]() mutable { + comm::MessageOperationsUtilities::storeMessageInfos(messageInfos); + }); + } + + TemporaryMessageStorage *temporaryRescindsStorage = + [[TemporaryMessageStorage alloc] initForRescinds]; + NSArray *rescindMessages = + [temporaryRescindsStorage readAndClearMessages]; + for (NSString *rescindMessage in rescindMessages) { + NSData *binaryRescindMessage = + [rescindMessage dataUsingEncoding:NSUTF8StringEncoding]; + + NSError *jsonError = nil; + NSDictionary *rescindPayload = + [NSJSONSerialization JSONObjectWithData:binaryRescindMessage + options:0 + error:&jsonError]; + if (jsonError) { + comm::Logger::log( + "Failed to deserialize persisted rescind payload. Details: " + + std::string([jsonError.localizedDescription UTF8String])); + continue; + } + + if (!(rescindPayload[setUnreadStatusKey] && rescindPayload[threadIDKey])) { + continue; + } + + std::string threadID = + std::string([rescindPayload[threadIDKey] UTF8String]); + comm::GlobalDBSingleton::instance.scheduleOrRun([threadID]() mutable { + comm::ThreadOperations::updateSQLiteUnreadStatus(threadID, false); + }); + } +} + ++ (void)initMMKV { + comm::CommMMKV::initialize(); +} + +@end diff --git a/native/ios/Comm/main.m b/native/ios/Comm/main.m deleted file mode 100644 --- a/native/ios/Comm/main.m +++ /dev/null @@ -1,10 +0,0 @@ -#import - -#import "AppDelegate.h" - -int main(int argc, char *argv[]) { - @autoreleasepool { - return UIApplicationMain( - argc, argv, nil, NSStringFromClass([AppDelegate class])); - } -}