-
Notifications
You must be signed in to change notification settings - Fork 29
/
Copy pathAppDelegate.mm
456 lines (398 loc) · 16.1 KB
/
AppDelegate.mm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
#import "AppDelegate.h"
#import <React/RCTBridge.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import <React/RCTAppSetupUtils.h>
#if RCT_NEW_ARCH_ENABLED
#import <React/CoreModulesPlugins.h>
#import <React/RCTCxxBridgeDelegate.h>
#import <React/RCTFabricSurfaceHostingProxyRootView.h>
#import <React/RCTSurfacePresenter.h>
#import <React/RCTSurfacePresenterBridgeAdapter.h>
#import <ReactCommon/RCTTurboModuleManager.h>
#import <react/config/ReactNativeConfig.h>
static NSString *const kRNConcurrentRoot = @"concurrentRoot";
@interface AppDelegate () <
RCTCxxBridgeDelegate,
RCTTurboModuleManagerDelegate> {
RCTTurboModuleManager *_turboModuleManager;
RCTSurfacePresenterBridgeAdapter *_bridgeAdapter;
std::shared_ptr<const facebook::react::ReactNativeConfig> _reactNativeConfig;
facebook::react::ContextContainer::Shared _contextContainer;
}
@end
#endif
#import "CommIOSNotifications.h"
#import "Orientation.h"
#import <React/RCTConvert.h>
#import <React/RCTBridge+Private.h>
#import <React/RCTCxxBridgeDelegate.h>
#import <React/RCTJSIExecutorRuntimeInstaller.h>
#import <React/RCTLinkingManager.h>
#import <cxxreact/JSExecutor.h>
#import <jsireact/JSIExecutor.h>
#import <reacthermes/HermesExecutorFactory.h>
#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 <cstdio>
#import <stdexcept>
#import <string>
#import <ReactCommon/RCTTurboModuleManager.h>
#import <RNReanimated/REAInitializer.h>
#import <UserNotifications/UserNotifications.h>
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<facebook::react::ContextContainer const>();
_reactNativeConfig =
std::make_shared<facebook::react::EmptyReactNativeConfig const>();
_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<UIApplicationOpenURLOptionsKey, id> *)options {
return [RCTLinkingManager application:application
openURL:url
options:options];
}
- (BOOL)application:(UIApplication *)application
continueUserActivity:(nonnull NSUserActivity *)userActivity
restorationHandler:
(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> *_Nullable))
restorationHandler {
return [RCTLinkingManager application:application
continueUserActivity:userActivity
restorationHandler:restorationHandler];
}
- (NSArray<id<RCTBridgeModule>> *)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<facebook::react::JSExecutorFactory>)
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<facebook::react::TurboModule>)
getTurboModule:(const std::string &)name
jsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker {
return nullptr;
}
- (std::shared_ptr<facebook::react::TurboModule>)
getTurboModule:(const std::string &)name
initParams:
(const facebook::react::ObjCTurboModule::InitParams &)params {
return nullptr;
}
- (id<RCTTurboModule>)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<JSExecutorFactory>)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<comm::CommCoreModule> coreNativeModule =
std::make_shared<comm::CommCoreModule>(bridge.jsCallInvoker);
std::shared_ptr<comm::CommUtilsModule> utilsNativeModule =
std::make_shared<comm::CommUtilsModule>(bridge.jsCallInvoker);
std::shared_ptr<comm::CommRustModule> rustNativeModule =
std::make_shared<comm::CommRustModule>(bridge.jsCallInvoker);
std::shared_ptr<comm::CommConstants> nativeConstants =
std::make_shared<comm::CommConstants>();
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<HermesExecutorFactory>(
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<NSString *> *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<NSString *> *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