diff --git a/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.h b/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.h --- a/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.h +++ b/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.h @@ -16,12 +16,13 @@ static std::once_flag initialized; static int sqlcipherEncryptionKeySize; - static std::string secureStoreEncryptionKeyID; public: static std::string sqliteFilePath; static std::string encryptionKey; + static std::string secureStoreEncryptionKeyID; + SQLiteQueryExecutor(); static void initialize(std::string &databasePath); std::unique_ptr getThread(std::string threadID) const override; diff --git a/native/ios/Comm/AppDelegate.mm b/native/ios/Comm/AppDelegate.mm --- a/native/ios/Comm/AppDelegate.mm +++ b/native/ios/Comm/AppDelegate.mm @@ -15,6 +15,7 @@ #import #import "CommCoreModule.h" +#import "CommSecureStoreIOSWrapper.h" #import "GlobalDBSingleton.h" #import "GlobalNetworkSingleton.h" #import "Logger.h" @@ -69,6 +70,12 @@ - (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + NSString *secureStoreEncryptionKeyID = [NSString + stringWithUTF8String: + (comm::SQLiteQueryExecutor::secureStoreEncryptionKeyID.c_str())]; + [[CommSecureStoreIOSWrapper sharedInstance] + migrateOptionsForKey:secureStoreEncryptionKeyID + withVersion:@"0"]; [self attemptDatabaseInitialization]; return YES; } @@ -164,8 +171,8 @@ } else if ([notification[backgroundNotificationTypeKey] isEqualToString:@"CLEAR"]) { if (notification[setUnreadStatusKey] && notification[@"threadID"]) { - std::string threadID = - std::string([notification[@"threadID"] UTF8String]); + NSString *objcThreadID = notification[@"threadID"]; + std::string threadID = std::string([objcThreadID UTF8String]); // this callback may be called from inactive state so we need // to initialize the database [self attemptDatabaseInitialization]; diff --git a/native/ios/Comm/CommSecureStoreIOSWrapper.h b/native/ios/Comm/CommSecureStoreIOSWrapper.h --- a/native/ios/Comm/CommSecureStoreIOSWrapper.h +++ b/native/ios/Comm/CommSecureStoreIOSWrapper.h @@ -9,4 +9,5 @@ - (void)set:(NSString *)key value:(NSString *)value; - (NSString *)get:(NSString *)key; +- (void)migrateOptionsForKey:(NSString *)key withVersion:(NSString *)version; @end diff --git a/native/ios/Comm/CommSecureStoreIOSWrapper.mm b/native/ios/Comm/CommSecureStoreIOSWrapper.mm --- a/native/ios/Comm/CommSecureStoreIOSWrapper.mm +++ b/native/ios/Comm/CommSecureStoreIOSWrapper.mm @@ -16,6 +16,7 @@ - (NSString *)_getValueWithKey:(NSString *)key withOptions:(NSDictionary *)options error:(NSError **)error; +- (void)_deleteValueWithKey:(NSString *)key withOptions:(NSDictionary *)options; @end @implementation CommSecureStoreIOSWrapper @@ -33,6 +34,8 @@ (EXSecureStore *)[[moduleRegistryProvider moduleRegistry] getExportedModuleOfClass:EXSecureStore.class]; shared.secureStore = secureStore; + shared.options = + @{@"keychainAccessible" : @(EXSecureStoreAccessibleAfterFirstUnlock)}; }); return shared; } @@ -64,4 +67,33 @@ error:&error]; } +- (void)migrateOptionsForKey:(NSString *)key withVersion:(NSString *)version { + NSString *secureStoreKeyVersionID = [key stringByAppendingString:@".version"]; + NSString *failureProtectionCopyKey = [key stringByAppendingString:@".copy"]; + + NSString *secureStoreKeyVersion = [self get:secureStoreKeyVersionID]; + if (secureStoreKeyVersion && + [secureStoreKeyVersion isEqualToString:version]) { + return; + } + + NSString *value = [self get:key]; + NSString *valueCopy = [self get:failureProtectionCopyKey]; + + if (value) { + [self set:failureProtectionCopyKey value:value]; + [[self secureStore] _deleteValueWithKey:key withOptions:[self options]]; + } else if (valueCopy) { + value = valueCopy; + } else { + [self set:secureStoreKeyVersionID value:version]; + return; + } + + [self set:key value:value]; + [self set:secureStoreKeyVersionID value:version]; + [[self secureStore] _deleteValueWithKey:failureProtectionCopyKey + withOptions:[self options]]; +} + @end diff --git a/native/redux/redux-setup.js b/native/redux/redux-setup.js --- a/native/redux/redux-setup.js +++ b/native/redux/redux-setup.js @@ -1,5 +1,6 @@ // @flow +import { isEqual } from 'lodash/lang'; import { AppState as NativeAppState, Platform, Alert } from 'react-native'; import ExitApp from 'react-native-exit-app'; import Orientation from 'react-native-orientation-locker'; @@ -19,6 +20,7 @@ invalidSessionDowngrade, invalidSessionRecovery, } from 'lib/shared/account-utils'; +import { isStaff } from 'lib/shared/user-utils'; import { loginActionSources } from 'lib/types/account-types'; import { defaultEnabledApps } from 'lib/types/enabled-apps'; import { defaultCalendarFilters } from 'lib/types/filter-types'; @@ -32,7 +34,10 @@ import { updateTypes } from 'lib/types/update-types'; import { reduxLoggerMiddleware } from 'lib/utils/action-logger'; import { setNewSessionActionType } from 'lib/utils/action-utils'; -import { convertMessageStoreOperationsToClientDBOperations } from 'lib/utils/message-ops-utils'; +import { + convertMessageStoreOperationsToClientDBOperations, + translateClientDBMessageInfosToRawMessageInfos, +} from 'lib/utils/message-ops-utils'; import { convertThreadStoreOperationsToClientDBOperations } from 'lib/utils/thread-ops-utils'; import { commCoreModule } from '../native-modules'; @@ -314,6 +319,42 @@ const convertedMessageStoreOperations = convertMessageStoreOperationsToClientDBOperations( messageStoreOperations, ); + + if (convertedMessageStoreOperations.length > 0) { + global.CommCoreModule.processMessageStoreOperationsSync( + convertedMessageStoreOperations, + ); + } + + const crashReportsEnabled = state.reportStore.enabledReports.crashReports; + const viewerID = state.currentUserInfo?.id; + + try { + const messages = global.CommCoreModule.getAllMessagesSync(); + const rawMsgsFromSQLite = translateClientDBMessageInfosToRawMessageInfos( + messages, + ); + + const ignoreList = [ + '@@INIT', + 'persist/REHYDRATE', + 'persist/PERSIST', + 'SET_THREAD_STORE', + ]; + if ( + !isEqual(rawMsgsFromSQLite, state.messageStore.messages) && + !ignoreList.includes(action.type) && + !action.type.includes('@@redux/INIT') + ) { + Alert.alert(`${action.type}: NOT EQUAL`); + } + } catch (e) { + if ((__DEV__ || (viewerID && isStaff(viewerID))) && crashReportsEnabled) { + throw e; + } + console.log(e.message); + } + (async () => { try { const promises = []; @@ -324,13 +365,6 @@ ), ); } - if (convertedMessageStoreOperations.length > 0) { - promises.push( - commCoreModule.processMessageStoreOperations( - convertedMessageStoreOperations, - ), - ); - } await Promise.all(promises); } catch { dispatch({