diff --git a/__tests__/Storyshots.test.js b/__tests__/Storyshots.test.js index 1315f9bb..638fbb0e 100644 --- a/__tests__/Storyshots.test.js +++ b/__tests__/Storyshots.test.js @@ -20,6 +20,26 @@ jest.mock('react-native-file-viewer', () => ({ jest.mock('../app/lib/database', () => jest.fn(() => null)); global.Date.now = jest.fn(() => new Date('2019-10-10').getTime()); +jest.mock('react-native-mmkv-storage', () => { + return { + Loader: jest.fn().mockImplementation(() => { + return { + setProcessingMode: jest.fn().mockImplementation(() => { + return { + withEncryption: jest.fn().mockImplementation(() => { + return { + initialize: jest.fn() + }; + }) + }; + }) + }; + }), + create: jest.fn(), + MODES: { MULTI_PROCESS: '' } + }; +}); + const converter = new Stories2SnapsConverter(); initStoryshots({ diff --git a/android/app/src/main/java/chat/rocket/reactnative/MainApplication.java b/android/app/src/main/java/chat/rocket/reactnative/MainApplication.java index bf7dc5ef..c0361e78 100644 --- a/android/app/src/main/java/chat/rocket/reactnative/MainApplication.java +++ b/android/app/src/main/java/chat/rocket/reactnative/MainApplication.java @@ -12,7 +12,6 @@ import com.facebook.soloader.SoLoader; import com.reactnativecommunity.viewpager.RNCViewPagerPackage; import com.facebook.react.bridge.JSIModulePackage; import com.swmansion.reanimated.ReanimatedJSIModulePackage; - import org.unimodules.adapters.react.ModuleRegistryAdapter; import org.unimodules.adapters.react.ReactModuleRegistryProvider; @@ -54,7 +53,7 @@ public class MainApplication extends Application implements ReactApplication { @Override protected JSIModulePackage getJSIModulePackage() { - return new ReanimatedJSIModulePackage(); // <- add + return new ReanimatedJSIModulePackage(); } @Override diff --git a/android/app/src/play/java/chat/rocket/reactnative/Ejson.java b/android/app/src/play/java/chat/rocket/reactnative/Ejson.java index e44c6b72..fb083df1 100644 --- a/android/app/src/play/java/chat/rocket/reactnative/Ejson.java +++ b/android/app/src/play/java/chat/rocket/reactnative/Ejson.java @@ -53,16 +53,8 @@ public class Ejson { String alias = Utils.toHex("com.MMKV.default"); // Retrieve container password - secureKeystore.getSecureKey(alias, new RNCallback() { - @Override - public void invoke(Object... args) { - String error = (String) args[0]; - if (error == null) { - String password = (String) args[1]; - mmkv = MMKV.mmkvWithID("default", MMKV.SINGLE_PROCESS_MODE, password); - } - } - }); + String password = secureKeystore.getSecureKey(alias); + mmkv = MMKV.mmkvWithID("default", MMKV.SINGLE_PROCESS_MODE, password); } public String getAvatarUri() { diff --git a/app/containers/Passcode/PasscodeEnter.tsx b/app/containers/Passcode/PasscodeEnter.tsx index 0a9b6b1f..f61bf91a 100644 --- a/app/containers/Passcode/PasscodeEnter.tsx +++ b/app/containers/Passcode/PasscodeEnter.tsx @@ -10,7 +10,7 @@ import { TYPE } from './constants'; import { ATTEMPTS_KEY, LOCKED_OUT_TIMER_KEY, MAX_ATTEMPTS, PASSCODE_KEY } from '../../constants/localAuthentication'; import { biometryAuth, resetAttempts } from '../../utils/localAuthentication'; import { getDiff, getLockedUntil } from './utils'; -import UserPreferences from '../../lib/userPreferences'; +import { useUserPreferences } from '../../lib/userPreferences'; import I18n from '../../i18n'; interface IPasscodePasscodeEnter { @@ -23,16 +23,11 @@ const PasscodeEnter = ({ theme, hasBiometry, finishProcess }: IPasscodePasscodeE const ref = useRef(null); let attempts: any = 0; let lockedUntil: any = false; - const [passcode, setPasscode] = useState(null); + const [passcode] = useUserPreferences(PASSCODE_KEY); const [status, setStatus] = useState(null); const { getItem: getAttempts, setItem: setAttempts } = useAsyncStorage(ATTEMPTS_KEY); const { setItem: setLockedUntil } = useAsyncStorage(LOCKED_OUT_TIMER_KEY); - const fetchPasscode = async () => { - const p: any = await UserPreferences.getStringAsync(PASSCODE_KEY); - setPasscode(p); - }; - const biometry = async () => { if (hasBiometry && status === TYPE.ENTER) { const result = await biometryAuth(); @@ -56,7 +51,6 @@ const PasscodeEnter = ({ theme, hasBiometry, finishProcess }: IPasscodePasscodeE } else { setStatus(TYPE.ENTER); } - await fetchPasscode(); biometry(); }; diff --git a/app/index.tsx b/app/index.tsx index 89e6c527..508bec33 100644 --- a/app/index.tsx +++ b/app/index.tsx @@ -6,8 +6,7 @@ import { KeyCommandsEmitter } from 'react-native-keycommands'; import RNScreens from 'react-native-screens'; import { SafeAreaProvider, initialWindowMetrics } from 'react-native-safe-area-context'; -import { defaultTheme, newThemeState, subscribeTheme, unsubscribeTheme } from './utils/theme'; -import UserPreferences from './lib/userPreferences'; +import { getTheme, initialTheme, newThemeState, subscribeTheme, unsubscribeTheme } from './utils/theme'; import EventEmitter from './utils/events'; import { appInit, appInitLocalSettings, setMasterDetail as setMasterDetailAction } from './actions/app'; import { deepLinkingOpen } from './actions/deepLinking'; @@ -17,9 +16,9 @@ import store from './lib/createStore'; import { toggleAnalyticsEventsReport, toggleCrashErrorsReport } from './utils/log'; import { ThemeContext } from './theme'; import { DimensionsContext } from './dimensions'; -import RocketChat, { THEME_PREFERENCES_KEY } from './lib/rocketchat'; +import RocketChat from './lib/rocketchat'; import { MIN_WIDTH_MASTER_DETAIL_LAYOUT } from './constants/tablet'; -import { isTablet, supportSystemTheme } from './utils/deviceInfo'; +import { isTablet } from './utils/deviceInfo'; import { KEY_COMMAND } from './commands'; import AppContainer from './AppContainer'; import TwoFactor from './containers/TwoFactor'; @@ -33,6 +32,7 @@ import { isFDroidBuild } from './constants/environment'; import { IThemePreference } from './definitions/ITheme'; import { ICommand } from './definitions/ICommand'; import { initStore } from './lib/auxStore'; +import { themes } from './constants/colors'; RNScreens.enableScreens(); initStore(store); @@ -88,12 +88,10 @@ export default class Root extends React.Component<{}, IState> { this.initCrashReport(); } const { width, height, scale, fontScale } = Dimensions.get('window'); + const theme = initialTheme(); this.state = { - theme: defaultTheme(), - themePreferences: { - currentTheme: supportSystemTheme() ? 'automatic' : 'light', - darkLevel: 'black' - }, + theme: getTheme(theme), + themePreferences: theme, width, height, scale, @@ -128,7 +126,6 @@ export default class Root extends React.Component<{}, IState> { } init = async () => { - UserPreferences.getMapAsync(THEME_PREFERENCES_KEY).then((theme: any) => this.setTheme(theme)); store.dispatch(appInitLocalSettings()); // Open app from push notification @@ -209,7 +206,9 @@ export default class Root extends React.Component<{}, IState> { render() { const { themePreferences, theme, width, height, scale, fontScale } = this.state; return ( - + { this.privateKey = await SimpleCrypto.RSA.importKey(EJSON.parse(privateKey)); - await UserPreferences.setStringAsync(`${server}-${E2E_PUBLIC_KEY}`, EJSON.stringify(publicKey)); - await UserPreferences.setStringAsync(`${server}-${E2E_PRIVATE_KEY}`, privateKey); + UserPreferences.setString(`${server}-${E2E_PUBLIC_KEY}`, EJSON.stringify(publicKey)); + UserPreferences.setString(`${server}-${E2E_PRIVATE_KEY}`, privateKey); }; // Could not obtain public-private keypair from server. @@ -182,9 +182,9 @@ class Encryption { }; // Create a random password to local created keys - createRandomPassword = async (server: string) => { + createRandomPassword = (server: string) => { const password = randomPassword(); - await UserPreferences.setStringAsync(`${server}-${E2E_RANDOM_PASSWORD_KEY}`, password); + UserPreferences.setString(`${server}-${E2E_RANDOM_PASSWORD_KEY}`, password); return password; }; @@ -194,7 +194,7 @@ class Encryption { // Encode the private key const encodedPrivateKey = await this.encodePrivateKey(EJSON.stringify(privateKey), password, this.userId as string); - const publicKey = await UserPreferences.getStringAsync(`${server}-${E2E_PUBLIC_KEY}`); + const publicKey = UserPreferences.getString(`${server}-${E2E_PUBLIC_KEY}`); // Send the new keys to the server await RocketChat.e2eSetUserPublicAndPrivateKeys(EJSON.stringify(publicKey), encodedPrivateKey); diff --git a/app/lib/methods/logout.ts b/app/lib/methods/logout.ts index e9e3daeb..0086fe10 100644 --- a/app/lib/methods/logout.ts +++ b/app/lib/methods/logout.ts @@ -14,23 +14,23 @@ import UserPreferences from '../userPreferences'; import { ICertificate, IRocketChat } from '../../definitions'; import sdk from '../rocketchat/services/sdk'; -async function removeServerKeys({ server, userId }: { server: string; userId?: string | null }) { - await UserPreferences.removeItem(`${RocketChat.TOKEN_KEY}-${server}`); +function removeServerKeys({ server, userId }: { server: string; userId?: string | null }) { + UserPreferences.removeItem(`${RocketChat.TOKEN_KEY}-${server}`); if (userId) { - await UserPreferences.removeItem(`${RocketChat.TOKEN_KEY}-${userId}`); + UserPreferences.removeItem(`${RocketChat.TOKEN_KEY}-${userId}`); } - await UserPreferences.removeItem(`${BASIC_AUTH_KEY}-${server}`); - await UserPreferences.removeItem(`${server}-${E2E_PUBLIC_KEY}`); - await UserPreferences.removeItem(`${server}-${E2E_PRIVATE_KEY}`); - await UserPreferences.removeItem(`${server}-${E2E_RANDOM_PASSWORD_KEY}`); + UserPreferences.removeItem(`${BASIC_AUTH_KEY}-${server}`); + UserPreferences.removeItem(`${server}-${E2E_PUBLIC_KEY}`); + UserPreferences.removeItem(`${server}-${E2E_PRIVATE_KEY}`); + UserPreferences.removeItem(`${server}-${E2E_RANDOM_PASSWORD_KEY}`); } async function removeSharedCredentials({ server }: { server: string }) { // clear certificate for server - SSL Pinning try { - const certificate = (await UserPreferences.getMapAsync(extractHostname(server))) as ICertificate | null; + const certificate = UserPreferences.getMap(extractHostname(server)) as ICertificate | null; if (certificate?.path) { - await UserPreferences.removeItem(extractHostname(server)); + UserPreferences.removeItem(extractHostname(server)); await FileSystem.deleteAsync(certificate.path); } } catch (e) { @@ -42,7 +42,7 @@ async function removeServerData({ server }: { server: string }) { try { const batch: Model[] = []; const serversDB = database.servers; - const userId = await UserPreferences.getStringAsync(`${RocketChat.TOKEN_KEY}-${server}`); + const userId = UserPreferences.getString(`${RocketChat.TOKEN_KEY}-${server}`); const usersCollection = serversDB.get('users'); if (userId) { @@ -55,14 +55,14 @@ async function removeServerData({ server }: { server: string }) { await serversDB.write(() => serversDB.batch(...batch)); await removeSharedCredentials({ server }); - await removeServerKeys({ server, userId }); + removeServerKeys({ server, userId }); } catch (e) { log(e); } } -async function removeCurrentServer() { - await UserPreferences.removeItem(RocketChat.CURRENT_SERVER); +function removeCurrentServer() { + UserPreferences.removeItem(RocketChat.CURRENT_SERVER); } async function removeServerDatabase({ server }: { server: string }) { @@ -76,9 +76,9 @@ async function removeServerDatabase({ server }: { server: string }) { export async function removeServer({ server }: { server: string }): Promise { try { - const userId = await UserPreferences.getStringAsync(`${RocketChat.TOKEN_KEY}-${server}`); + const userId = UserPreferences.getString(`${RocketChat.TOKEN_KEY}-${server}`); if (userId) { - const resume = await UserPreferences.getStringAsync(`${RocketChat.TOKEN_KEY}-${userId}`); + const resume = UserPreferences.getString(`${RocketChat.TOKEN_KEY}-${userId}`); const sdk = new RocketchatClient({ host: server, protocol: 'ddp', useSsl: useSsl(server) }); await sdk.login({ resume }); diff --git a/app/lib/rocketchat/rocketchat.js b/app/lib/rocketchat/rocketchat.js index cbcacdf0..6396d5e8 100644 --- a/app/lib/rocketchat/rocketchat.js +++ b/app/lib/rocketchat/rocketchat.js @@ -116,8 +116,8 @@ const RocketChat = { database.setShareDB(server); try { - const certificate = await UserPreferences.getStringAsync(`${RocketChat.CERTIFICATE_KEY}-${server}`); - await SSLPinning.setCertificate(certificate, server); + const certificate = UserPreferences.getString(`${RocketChat.CERTIFICATE_KEY}-${server}`); + SSLPinning.setCertificate(certificate, server); } catch { // Do nothing } @@ -156,7 +156,7 @@ const RocketChat = { reduxStore.dispatch(shareSetSettings(this.parseSettings(parsed))); // set User info - const userId = await UserPreferences.getStringAsync(`${RocketChat.TOKEN_KEY}-${server}`); + const userId = UserPreferences.getString(`${RocketChat.TOKEN_KEY}-${server}`); const userCollections = serversDB.get('users'); let user = null; if (userId) { @@ -457,14 +457,13 @@ const RocketChat = { } return JSON.parse(allowAnalyticsEvents); }, - async getSortPreferences() { - const prefs = await UserPreferences.getMapAsync(SORT_PREFS_KEY); - return prefs; + getSortPreferences() { + return UserPreferences.getMap(SORT_PREFS_KEY); }, - async saveSortPreference(param) { - let prefs = await RocketChat.getSortPreferences(); + saveSortPreference(param) { + let prefs = RocketChat.getSortPreferences(); prefs = { ...prefs, ...param }; - return UserPreferences.setMapAsync(SORT_PREFS_KEY, prefs); + return UserPreferences.setMap(SORT_PREFS_KEY, prefs); }, getLoginServices, determineAuthType, diff --git a/app/lib/userPreferences.ts b/app/lib/userPreferences.ts index 6f4818da..a9720202 100644 --- a/app/lib/userPreferences.ts +++ b/app/lib/userPreferences.ts @@ -1,4 +1,4 @@ -import MMKVStorage from 'react-native-mmkv-storage'; +import MMKVStorage, { create } from 'react-native-mmkv-storage'; const MMKV = new MMKVStorage.Loader() // MODES.MULTI_PROCESS = ACCESSIBLE BY APP GROUP (iOS) @@ -6,52 +6,51 @@ const MMKV = new MMKVStorage.Loader() .withEncryption() .initialize(); +export const useUserPreferences = create(MMKV); + class UserPreferences { private mmkv: MMKVStorage.API; constructor() { this.mmkv = MMKV; } - async getStringAsync(key: string) { + getString(key: string): string | null { try { - const value = await this.mmkv.getStringAsync(key); - return value; + return this.mmkv.getString(key) || null; } catch { return null; } } - setStringAsync(key: string, value: string) { - return this.mmkv.setStringAsync(key, value); + setString(key: string, value: string): boolean | undefined { + return this.mmkv.setString(key, value); } - async getBoolAsync(key: string) { + getBool(key: string): boolean | null { try { - const value = await this.mmkv.getBoolAsync(key); - return value; + return this.mmkv.getBool(key) || null; } catch { return null; } } - setBoolAsync(key: string, value: boolean) { - return this.mmkv.setBoolAsync(key, value); + setBool(key: string, value: boolean): boolean | undefined { + return this.mmkv.setBool(key, value); } - async getMapAsync(key: string) { + getMap(key: string): object | null { try { - const value = await this.mmkv.getMapAsync(key); - return value; + return this.mmkv.getMap(key) || null; } catch { return null; } } - setMapAsync(key: string, value: object) { - return this.mmkv.setMapAsync(key, value); + setMap(key: string, value: object): boolean | undefined { + return this.mmkv.setMap(key, value); } - removeItem(key: string) { + removeItem(key: string): boolean | undefined { return this.mmkv.removeItem(key); } } diff --git a/app/sagas/deepLinking.js b/app/sagas/deepLinking.js index 900e6a14..c1002539 100644 --- a/app/sagas/deepLinking.js +++ b/app/sagas/deepLinking.js @@ -158,8 +158,8 @@ const handleOpen = function* handleOpen({ params }) { } const [server, user] = yield all([ - UserPreferences.getStringAsync(RocketChat.CURRENT_SERVER), - UserPreferences.getStringAsync(`${RocketChat.TOKEN_KEY}-${host}`) + UserPreferences.getString(RocketChat.CURRENT_SERVER), + UserPreferences.getString(`${RocketChat.TOKEN_KEY}-${host}`) ]); // TODO: needs better test diff --git a/app/sagas/encryption.js b/app/sagas/encryption.js index cdf938bc..e99f8b72 100644 --- a/app/sagas/encryption.js +++ b/app/sagas/encryption.js @@ -39,7 +39,7 @@ const handleEncryptionInit = function* handleEncryptionInit() { } // Fetch stored private e2e key for this server - const storedPrivateKey = yield UserPreferences.getStringAsync(`${server}-${E2E_PRIVATE_KEY}`); + const storedPrivateKey = UserPreferences.getString(`${server}-${E2E_PRIVATE_KEY}`); // Fetch server stored e2e keys const keys = yield RocketChat.e2eFetchMyKeys(); @@ -52,13 +52,15 @@ const handleEncryptionInit = function* handleEncryptionInit() { } // If the user has a private key stored, but never entered the password - const storedRandomPassword = yield UserPreferences.getStringAsync(`${server}-${E2E_RANDOM_PASSWORD_KEY}`); + const storedRandomPassword = UserPreferences.getString(`${server}-${E2E_RANDOM_PASSWORD_KEY}`); + if (storedRandomPassword) { yield put(encryptionSet(true, E2E_BANNER_TYPE.SAVE_PASSWORD)); } // Fetch stored public e2e key for this server - let storedPublicKey = yield UserPreferences.getStringAsync(`${server}-${E2E_PUBLIC_KEY}`); + let storedPublicKey = UserPreferences.getString(`${server}-${E2E_PUBLIC_KEY}`); + // Prevent parse undefined if (storedPublicKey) { storedPublicKey = EJSON.parse(storedPublicKey); diff --git a/app/sagas/init.js b/app/sagas/init.js index 3671e83f..a7efced6 100644 --- a/app/sagas/init.js +++ b/app/sagas/init.js @@ -14,7 +14,7 @@ import { appReady, appStart } from '../actions/app'; import { RootEnum } from '../definitions'; export const initLocalSettings = function* initLocalSettings() { - const sortPreferences = yield RocketChat.getSortPreferences(); + const sortPreferences = RocketChat.getSortPreferences(); yield put(setAllPreferences(sortPreferences)); }; @@ -22,19 +22,19 @@ const BIOMETRY_MIGRATION_KEY = 'kBiometryMigration'; const restore = function* restore() { try { - const server = yield UserPreferences.getStringAsync(RocketChat.CURRENT_SERVER); - let userId = yield UserPreferences.getStringAsync(`${RocketChat.TOKEN_KEY}-${server}`); + const server = UserPreferences.getString(RocketChat.CURRENT_SERVER); + let userId = UserPreferences.getString(`${RocketChat.TOKEN_KEY}-${server}`); // Migration biometry setting from WatermelonDB to MMKV // TODO: remove it after a few versions - const hasMigratedBiometry = yield UserPreferences.getBoolAsync(BIOMETRY_MIGRATION_KEY); + const hasMigratedBiometry = UserPreferences.getBool(BIOMETRY_MIGRATION_KEY); if (!hasMigratedBiometry) { const serversDB = database.servers; const serversCollection = serversDB.get('servers'); const servers = yield serversCollection.query().fetch(); const isBiometryEnabled = servers.some(server => !!server.biometry); - yield UserPreferences.setBoolAsync(BIOMETRY_ENABLED_KEY, isBiometryEnabled); - yield UserPreferences.setBoolAsync(BIOMETRY_MIGRATION_KEY, true); + UserPreferences.setBool(BIOMETRY_ENABLED_KEY, isBiometryEnabled); + UserPreferences.setBool(BIOMETRY_MIGRATION_KEY, true); } if (!server) { @@ -48,7 +48,7 @@ const restore = function* restore() { if (servers.length > 0) { for (let i = 0; i < servers.length; i += 1) { const newServer = servers[i].id; - userId = yield UserPreferences.getStringAsync(`${RocketChat.TOKEN_KEY}-${newServer}`); + userId = UserPreferences.getString(`${RocketChat.TOKEN_KEY}-${newServer}`); if (userId) { return yield put(selectServerRequest(newServer)); } diff --git a/app/sagas/login.js b/app/sagas/login.js index 346e3be1..adde0dad 100644 --- a/app/sagas/login.js +++ b/app/sagas/login.js @@ -161,8 +161,8 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) { } }); - yield UserPreferences.setStringAsync(`${RocketChat.TOKEN_KEY}-${server}`, user.id); - yield UserPreferences.setStringAsync(`${RocketChat.TOKEN_KEY}-${user.id}`, user.token); + UserPreferences.setString(`${RocketChat.TOKEN_KEY}-${server}`, user.id); + UserPreferences.setString(`${RocketChat.TOKEN_KEY}-${user.id}`, user.token); yield put(setUser(user)); EventEmitter.emit('connected'); @@ -200,7 +200,7 @@ const handleLogout = function* handleLogout({ forcedByServer }) { if (servers.length > 0) { for (let i = 0; i < servers.length; i += 1) { const newServer = servers[i].id; - const token = yield UserPreferences.getStringAsync(`${RocketChat.TOKEN_KEY}-${newServer}`); + const token = UserPreferences.getString(`${RocketChat.TOKEN_KEY}-${newServer}`); if (token) { yield put(selectServerRequest(newServer)); return; diff --git a/app/sagas/selectServer.js b/app/sagas/selectServer.js index c67af42d..492191ee 100644 --- a/app/sagas/selectServer.js +++ b/app/sagas/selectServer.js @@ -69,15 +69,14 @@ const getServerInfo = function* getServerInfo({ server, raiseError = true }) { const handleSelectServer = function* handleSelectServer({ server, version, fetchVersion }) { try { // SSL Pinning - Read certificate alias and set it to be used by network requests - const certificate = yield UserPreferences.getStringAsync(`${RocketChat.CERTIFICATE_KEY}-${server}`); - yield SSLPinning.setCertificate(certificate, server); - + const certificate = UserPreferences.getString(`${RocketChat.CERTIFICATE_KEY}-${server}`); + SSLPinning.setCertificate(certificate, server); yield put(inquiryReset()); yield put(encryptionStop()); yield put(clearActiveUsers()); const serversDB = database.servers; - yield UserPreferences.setStringAsync(RocketChat.CURRENT_SERVER, server); - const userId = yield UserPreferences.getStringAsync(`${RocketChat.TOKEN_KEY}-${server}`); + UserPreferences.setString(RocketChat.CURRENT_SERVER, server); + const userId = UserPreferences.getString(`${RocketChat.TOKEN_KEY}-${server}`); const userCollections = serversDB.get('users'); let user = null; if (userId) { @@ -97,14 +96,14 @@ const handleSelectServer = function* handleSelectServer({ server, version, fetch }; } catch { // search credentials on shared credentials (Experimental/Official) - const token = yield UserPreferences.getStringAsync(`${RocketChat.TOKEN_KEY}-${userId}`); + const token = UserPreferences.getString(`${RocketChat.TOKEN_KEY}-${userId}`); if (token) { user = { token }; } } } - const basicAuth = yield UserPreferences.getStringAsync(`${BASIC_AUTH_KEY}-${server}`); + const basicAuth = UserPreferences.getString(`${BASIC_AUTH_KEY}-${server}`); setBasicAuth(basicAuth); // Check for running requests and abort them before connecting to the server @@ -148,8 +147,8 @@ const handleSelectServer = function* handleSelectServer({ server, version, fetch const handleServerRequest = function* handleServerRequest({ server, username, fromServerHistory }) { try { // SSL Pinning - Read certificate alias and set it to be used by network requests - const certificate = yield UserPreferences.getStringAsync(`${RocketChat.CERTIFICATE_KEY}-${server}`); - yield SSLPinning.setCertificate(certificate, server); + const certificate = UserPreferences.getString(`${RocketChat.CERTIFICATE_KEY}-${server}`); + SSLPinning.setCertificate(certificate, server); const serverInfo = yield getServerInfo({ server }); const serversDB = database.servers; diff --git a/app/share.tsx b/app/share.tsx index 8a756d91..1dddbe87 100644 --- a/app/share.tsx +++ b/app/share.tsx @@ -5,14 +5,13 @@ import { AppearanceProvider } from 'react-native-appearance'; import { createStackNavigator } from '@react-navigation/stack'; import { Provider } from 'react-redux'; -import { defaultTheme, newThemeState, subscribeTheme, unsubscribeTheme } from './utils/theme'; +import { getTheme, initialTheme, newThemeState, subscribeTheme, unsubscribeTheme } from './utils/theme'; import UserPreferences from './lib/userPreferences'; import Navigation from './lib/ShareNavigation'; import store from './lib/createStore'; import { initStore } from './lib/auxStore'; -import { supportSystemTheme } from './utils/deviceInfo'; import { defaultHeader, getActiveRouteName, navigationTheme, themedHeader } from './utils/navigation'; -import RocketChat, { THEME_PREFERENCES_KEY } from './lib/rocketchat'; +import RocketChat from './lib/rocketchat'; import { ThemeContext } from './theme'; import { localAuthenticate } from './utils/localAuthentication'; import { IThemePreference } from './definitions/ITheme'; @@ -94,12 +93,10 @@ class Root extends React.Component<{}, IState> { constructor(props: any) { super(props); const { width, height, scale, fontScale } = Dimensions.get('screen'); + const theme = initialTheme(); this.state = { - theme: defaultTheme(), - themePreferences: { - currentTheme: supportSystemTheme() ? 'automatic' : 'light', - darkLevel: 'black' - }, + theme: getTheme(theme), + themePreferences: theme, root: '', width, height, @@ -115,9 +112,7 @@ class Root extends React.Component<{}, IState> { } init = async () => { - UserPreferences.getMapAsync(THEME_PREFERENCES_KEY).then((theme: any) => this.setTheme(theme)); - - const currentServer = await UserPreferences.getStringAsync(RocketChat.CURRENT_SERVER); + const currentServer = UserPreferences.getString(RocketChat.CURRENT_SERVER); if (currentServer) { await localAuthenticate(currentServer); diff --git a/app/utils/localAuthentication.ts b/app/utils/localAuthentication.ts index 141268b4..9a2255f7 100644 --- a/app/utils/localAuthentication.ts +++ b/app/utils/localAuthentication.ts @@ -69,7 +69,7 @@ const openChangePasscodeModal = ({ force }: { force: boolean }) => export const changePasscode = async ({ force = false }: { force: boolean }): Promise => { const passcode = await openChangePasscodeModal({ force }); - await UserPreferences.setStringAsync(PASSCODE_KEY, sha256(passcode)); + UserPreferences.setString(PASSCODE_KEY, sha256(passcode)); }; export const biometryAuth = (force?: boolean): Promise => @@ -86,12 +86,12 @@ export const biometryAuth = (force?: boolean): Promise { const result = await biometryAuth(true); const isBiometryEnabled = !!result?.success; - await UserPreferences.setBoolAsync(BIOMETRY_ENABLED_KEY, isBiometryEnabled); + UserPreferences.setBool(BIOMETRY_ENABLED_KEY, isBiometryEnabled); return isBiometryEnabled; }; export const checkHasPasscode = async ({ force = true }: { force?: boolean }): Promise<{ newPasscode?: boolean } | void> => { - const storedPasscode = await UserPreferences.getStringAsync(PASSCODE_KEY); + const storedPasscode = UserPreferences.getString(PASSCODE_KEY); if (!storedPasscode) { await changePasscode({ force }); await checkBiometry(); @@ -137,7 +137,7 @@ export const localAuthenticate = async (server: string): Promise => { store.dispatch(setLocalAuthenticated(false)); // let hasBiometry = false; - let hasBiometry = (await UserPreferences.getBoolAsync(BIOMETRY_ENABLED_KEY)) ?? false; + let hasBiometry = UserPreferences.getBool(BIOMETRY_ENABLED_KEY) ?? false; // if biometry is enabled on the app if (hasBiometry) { diff --git a/app/utils/openLink.ts b/app/utils/openLink.ts index 4048b3ad..70abcec7 100644 --- a/app/utils/openLink.ts +++ b/app/utils/openLink.ts @@ -37,7 +37,7 @@ const appSchemeURL = (url: string, browser: string): string => { const openLink = async (url: string, theme = 'light'): Promise => { try { - const browser = await UserPreferences.getStringAsync(DEFAULT_BROWSER_KEY); + const browser = UserPreferences.getString(DEFAULT_BROWSER_KEY); if (browser === 'inApp') { await WebBrowser.openBrowserAsync(url, { diff --git a/app/utils/sslPinning.ts b/app/utils/sslPinning.ts index 27b10022..76ccc9a5 100644 --- a/app/utils/sslPinning.ts +++ b/app/utils/sslPinning.ts @@ -14,13 +14,13 @@ const extractFileScheme = (path: string) => path.replace('file://', ''); // file const getPath = (name: string) => `${documentDirectory}/${name}`; -const persistCertificate = async (name: string, password: string) => { +const persistCertificate = (name: string, password: string) => { const certificatePath = getPath(name); const certificate: ICertificate = { path: extractFileScheme(certificatePath), password }; - await UserPreferences.setMapAsync(name, certificate); + UserPreferences.setMap(name, certificate); return certificate; }; @@ -44,7 +44,7 @@ const RCSSLPinning = Platform.select({ try { const certificatePath = getPath(name); await FileSystem.copyAsync({ from: uri, to: certificatePath }); - await persistCertificate(name, password!); + persistCertificate(name, password!); resolve(name); } catch (e) { reject(e); @@ -58,13 +58,13 @@ const RCSSLPinning = Platform.select({ reject(e); } }), - setCertificate: async (name: string, server: string) => { + setCertificate: (name: string, server: string) => { if (name) { - let certificate = (await UserPreferences.getMapAsync(name)) as ICertificate; + let certificate = UserPreferences.getMap(name) as ICertificate; if (!certificate.path.match(extractFileScheme(documentDirectory!))) { - certificate = await persistCertificate(name, certificate.password); + certificate = persistCertificate(name, certificate.password); } - await UserPreferences.setMapAsync(extractHostname(server), certificate); + UserPreferences.setMap(extractHostname(server), certificate); } } }, diff --git a/app/utils/theme.ts b/app/utils/theme.ts index 0e9d8e05..1f2e848c 100644 --- a/app/utils/theme.ts +++ b/app/utils/theme.ts @@ -5,9 +5,20 @@ import setRootViewColor from 'rn-root-view'; import { IThemePreference, TThemeMode } from '../definitions/ITheme'; import { themes } from '../constants/colors'; import { isAndroid } from './deviceInfo'; +import UserPreferences from '../lib/userPreferences'; +import { THEME_PREFERENCES_KEY } from '../lib/rocketchat'; let themeListener: { remove: () => void } | null; +export const initialTheme = (): IThemePreference => { + const theme = UserPreferences.getMap(THEME_PREFERENCES_KEY) as IThemePreference; + const initialTheme: IThemePreference = { + currentTheme: defaultTheme(), + darkLevel: 'black' + }; + return theme || initialTheme; +}; + export const defaultTheme = (): TThemeMode => { const systemTheme = Appearance.getColorScheme(); if (systemTheme && systemTheme !== 'no-preference') { diff --git a/app/views/DefaultBrowserView.tsx b/app/views/DefaultBrowserView.tsx index 895f7086..6e726c75 100644 --- a/app/views/DefaultBrowserView.tsx +++ b/app/views/DefaultBrowserView.tsx @@ -73,9 +73,9 @@ class DefaultBrowserView extends React.Component { + changeDefaultBrowser = (newBrowser: TValue) => { logEvent(events.DB_CHANGE_DEFAULT_BROWSER, { browser: newBrowser }); try { const browser = newBrowser || 'systemDefault:'; - await UserPreferences.setStringAsync(DEFAULT_BROWSER_KEY, browser); + UserPreferences.setString(DEFAULT_BROWSER_KEY, browser); this.setState({ browser }); } catch { logEvent(events.DB_CHANGE_DEFAULT_BROWSER_F); diff --git a/app/views/DisplayPrefsView.tsx b/app/views/DisplayPrefsView.tsx index f793eb92..4405cd0a 100644 --- a/app/views/DisplayPrefsView.tsx +++ b/app/views/DisplayPrefsView.tsx @@ -17,7 +17,7 @@ import I18n from '../i18n'; import RocketChat from '../lib/rocketchat'; import { SettingsStackParamList } from '../stacks/types'; import { useTheme } from '../theme'; -import log, { events, logEvent } from '../utils/log'; +import { events, logEvent } from '../utils/log'; interface IDisplayPrefsView { navigation: StackNavigationProp; @@ -45,53 +45,49 @@ const DisplayPrefsView = (props: IDisplayPrefsView): JSX.Element => { } }, []); - const setSortPreference = async (param: Partial) => { - try { - dispatch(setPreference(param)); - await RocketChat.saveSortPreference(param); - } catch (e) { - log(e); - } + const setSortPreference = (param: Partial) => { + dispatch(setPreference(param)); + RocketChat.saveSortPreference(param); }; - const sortByName = async () => { + const sortByName = () => { logEvent(events.DP_SORT_CHANNELS_BY_NAME); - await setSortPreference({ sortBy: SortBy.Alphabetical }); + setSortPreference({ sortBy: SortBy.Alphabetical }); }; - const sortByActivity = async () => { + const sortByActivity = () => { logEvent(events.DP_SORT_CHANNELS_BY_ACTIVITY); - await setSortPreference({ sortBy: SortBy.Activity }); + setSortPreference({ sortBy: SortBy.Activity }); }; - const toggleGroupByType = async () => { + const toggleGroupByType = () => { logEvent(events.DP_GROUP_CHANNELS_BY_TYPE); - await setSortPreference({ groupByType: !groupByType }); + setSortPreference({ groupByType: !groupByType }); }; - const toggleGroupByFavorites = async () => { + const toggleGroupByFavorites = () => { logEvent(events.DP_GROUP_CHANNELS_BY_FAVORITE); - await setSortPreference({ showFavorites: !showFavorites }); + setSortPreference({ showFavorites: !showFavorites }); }; - const toggleUnread = async () => { + const toggleUnread = () => { logEvent(events.DP_GROUP_CHANNELS_BY_UNREAD); - await setSortPreference({ showUnread: !showUnread }); + setSortPreference({ showUnread: !showUnread }); }; - const toggleAvatar = async () => { + const toggleAvatar = () => { logEvent(events.DP_TOGGLE_AVATAR); - await setSortPreference({ showAvatar: !showAvatar }); + setSortPreference({ showAvatar: !showAvatar }); }; - const displayExpanded = async () => { + const displayExpanded = () => { logEvent(events.DP_DISPLAY_EXPANDED); - await setSortPreference({ displayMode: DisplayMode.Expanded }); + setSortPreference({ displayMode: DisplayMode.Expanded }); }; - const displayCondensed = async () => { + const displayCondensed = () => { logEvent(events.DP_DISPLAY_CONDENSED); - await setSortPreference({ displayMode: DisplayMode.Condensed }); + setSortPreference({ displayMode: DisplayMode.Condensed }); }; const renderCheckBox = (value: boolean) => ( diff --git a/app/views/E2ESaveYourPasswordView.tsx b/app/views/E2ESaveYourPasswordView.tsx index cc7bac83..72be2809 100644 --- a/app/views/E2ESaveYourPasswordView.tsx +++ b/app/views/E2ESaveYourPasswordView.tsx @@ -81,11 +81,11 @@ class E2ESaveYourPasswordView extends React.Component { + init = () => { const { server } = this.props; try { // Set stored password on local state - const password = await UserPreferences.getStringAsync(`${server}-${E2E_RANDOM_PASSWORD_KEY}`); + const password = UserPreferences.getString(`${server}-${E2E_RANDOM_PASSWORD_KEY}`); if (this.mounted) { this.setState({ password: password! }); } else { @@ -97,11 +97,11 @@ class E2ESaveYourPasswordView extends React.Component { + onSaved = () => { logEvent(events.E2E_SAVE_PW_SAVED); const { navigation, server, dispatch } = this.props; // Remove stored password - await UserPreferences.removeItem(`${server}-${E2E_RANDOM_PASSWORD_KEY}`); + UserPreferences.removeItem(`${server}-${E2E_RANDOM_PASSWORD_KEY}`); // Hide encryption banner dispatch(encryptionSetBanner()); navigation.pop(); diff --git a/app/views/NewServerView/index.tsx b/app/views/NewServerView/index.tsx index 06d56280..51f20a98 100644 --- a/app/views/NewServerView/index.tsx +++ b/app/views/NewServerView/index.tsx @@ -181,7 +181,7 @@ class NewServerView extends React.Component this.submit({ fromServerHistory: true, username: serverHistory?.username })); }; - submit = async ({ fromServerHistory = false, username }: ISubmitParams = {}) => { + submit = ({ fromServerHistory = false, username }: ISubmitParams = {}) => { logEvent(events.NS_CONNECT_TO_WORKSPACE); const { text, certificate } = this.state; const { dispatch } = this.props; @@ -193,10 +193,12 @@ class NewServerView extends React.Component { + basicAuth = (server: string, text: string) => { try { const parsedUrl = parse(text, true); if (parsedUrl.auth.length) { const credentials = Base64.encode(parsedUrl.auth); - await UserPreferences.setStringAsync(`${BASIC_AUTH_KEY}-${server}`, credentials); + UserPreferences.setString(`${BASIC_AUTH_KEY}-${server}`, credentials); setBasicAuth(credentials); } } catch { diff --git a/app/views/RoomsListView/ServerDropdown.tsx b/app/views/RoomsListView/ServerDropdown.tsx index be40179f..29992810 100644 --- a/app/views/RoomsListView/ServerDropdown.tsx +++ b/app/views/RoomsListView/ServerDropdown.tsx @@ -135,7 +135,7 @@ class ServerDropdown extends Component ({ biometry: !biometry }), - async () => { + () => { const { biometry } = this.state; - await userPreferences.setBoolAsync(BIOMETRY_ENABLED_KEY, biometry); + userPreferences.setBool(BIOMETRY_ENABLED_KEY, biometry); } ); }; diff --git a/app/views/ThemeView.tsx b/app/views/ThemeView.tsx index b73e7b9b..b3862bed 100644 --- a/app/views/ThemeView.tsx +++ b/app/views/ThemeView.tsx @@ -97,11 +97,11 @@ class ThemeView extends React.Component { this.setTheme(changes); }; - setTheme = async (theme: Partial) => { + setTheme = (theme: Partial) => { const { setTheme, themePreferences } = this.props; const newTheme = { ...themePreferences, ...theme }; setTheme(newTheme); - await UserPreferences.setMapAsync(THEME_PREFERENCES_KEY, newTheme); + UserPreferences.setMap(THEME_PREFERENCES_KEY, newTheme); }; renderIcon = () => { diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 08c8e3c0..7f6b2189 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -177,9 +177,9 @@ PODS: - libwebp/mux (1.1.0): - libwebp/demux - libwebp/webp (1.1.0) - - MMKV (1.2.1): - - MMKVCore (~> 1.2.1) - - MMKVCore (1.2.1) + - MMKV (1.2.10): + - MMKVCore (~> 1.2.10) + - MMKVCore (1.2.12) - nanopb (1.30905.0): - nanopb/decode (= 1.30905.0) - nanopb/encode (= 1.30905.0) @@ -410,9 +410,9 @@ PODS: - react-native-jitsi-meet (3.6.0): - JitsiMeetSDK (= 3.6.0) - React - - react-native-mmkv-storage (0.3.5): - - MMKV (= 1.2.1) - - React + - react-native-mmkv-storage (0.6.12): + - MMKV (= 1.2.10) + - React-Core - react-native-netinfo (6.0.0): - React-Core - react-native-notifications (2.1.7): @@ -950,7 +950,7 @@ SPEC CHECKSUMS: EXVideoThumbnails: 442c3abadb51a81551a3b53705b7560de390e6f7 EXWebBrowser: 76783ba5dcb8699237746ecf41a9643d428a4cc5 FBLazyVector: e686045572151edef46010a6f819ade377dfeb4b - FBReactNativeSpec: 8a1012a62d7a3d667376b413656d780b8e4680ce + FBReactNativeSpec: 110d69378fce79af38271c39894b59fec7890221 Firebase: 919186c8e119dd9372a45fd1dd17a8a942bc1892 FirebaseAnalytics: 5fa308e1b13f838d0f6dc74719ac2a72e8c5afc4 FirebaseCore: 8cd4f8ea22075e0ee582849b1cf79d8816506085 @@ -974,8 +974,8 @@ SPEC CHECKSUMS: KeyCommands: f66c535f698ed14b3d3a4e58859d79a827ea907e libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 libwebp: 946cb3063cea9236285f7e9a8505d806d30e07f3 - MMKV: 67253edee25a34edf332f91d73fa94a9e038b971 - MMKVCore: fe398984acac1fa33f92795d1b5fd0a334c944af + MMKV: 76033b9ace2006623308910a3afcc0e25eba3140 + MMKVCore: df0565f6b58463604731a68ba6cd89bc0b2d1d55 nanopb: c43f40fadfe79e8b8db116583945847910cbabc9 OpenSSL-Universal: 1aa4f6a6ee7256b83db99ec1ccdaa80d10f9af9b PromisesObjC: b48e0338dbbac2207e611750777895f7a5811b75 @@ -997,7 +997,7 @@ SPEC CHECKSUMS: react-native-cookies: 2cb6ef472da68610dfcf0eaee68464c244943abd react-native-document-picker: f1b5398801b332c77bc62ae0eae2116f49bdff26 react-native-jitsi-meet: 3e3ac5d0445091154119f94342efd55c8b1124ce - react-native-mmkv-storage: 48729fe90e850ef2fdc9d3714b7030c7c51d82b0 + react-native-mmkv-storage: 88bcb10bbe85a8122061d17d03abcc64a02fe1c9 react-native-netinfo: e849fc21ca2f4128a5726c801a82fc6f4a6db50d react-native-notifications: ee8fd739853e72694f3af8b374c8ccb106b7b227 react-native-orientation-locker: f0ca1a8e5031dab6b74bfb4ab33a17ed2c2fcb0d diff --git a/ios/Shared/RocketChat/Storage.swift b/ios/Shared/RocketChat/Storage.swift index 34d9f882..7f249689 100644 --- a/ios/Shared/RocketChat/Storage.swift +++ b/ios/Shared/RocketChat/Storage.swift @@ -25,37 +25,35 @@ class Storage { // get mmkv instance password from keychain var key: Data? - secureStorage.getSecureKey(instanceID.toHex()) { (response) -> () in - if let password = response?[1] as? String { - key = password.data(using: .utf8) - } + if let password: String = secureStorage.getSecureKey(instanceID.toHex()) { + key = password.data(using: .utf8) } - + guard let cryptKey = key else { return } - + // Get App Group directory let suiteName = Bundle.main.object(forInfoDictionaryKey: "AppGroup") as! String guard let directory = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: suiteName) else { return } - + // Set App Group dir MMKV.initialize(rootDir: nil, groupDir: directory.path, logLevel: MMKVLogLevel.none) self.mmkv = MMKV(mmapID: mmapID, cryptKey: cryptKey, mode: MMKVMode.multiProcess) } - + func getCredentials(server: String) -> Credentials? { if let userId = self.mmkv?.string(forKey: "reactnativemeteor_usertoken-\(server)") { if let userToken = self.mmkv?.string(forKey: "reactnativemeteor_usertoken-\(userId)") { return Credentials(userId: userId, userToken: userToken) } } - + return nil } - + func getPrivateKey(server: String) -> String? { return self.mmkv?.string(forKey: "\(server)-RC_E2E_PRIVATE_KEY") } diff --git a/package.json b/package.json index 5d7325a8..cd28c186 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,7 @@ "react-native-keycommands": "2.0.3", "react-native-localize": "2.1.1", "react-native-mime-types": "2.3.0", - "react-native-mmkv-storage": "0.3.5", + "react-native-mmkv-storage": "0.6.12", "react-native-modal": "11.10.0", "react-native-navigation-bar-color": "2.0.1", "react-native-notifications": "2.1.7", diff --git a/patches/react-native-mmkv-storage+0.3.5.patch b/patches/react-native-mmkv-storage+0.3.5.patch deleted file mode 100644 index e39d7571..00000000 --- a/patches/react-native-mmkv-storage+0.3.5.patch +++ /dev/null @@ -1,157 +0,0 @@ -diff --git a/node_modules/react-native-mmkv-storage/android/src/main/java/com/ammarahmed/mmkv/StorageGetters.java b/node_modules/react-native-mmkv-storage/android/src/main/java/com/ammarahmed/mmkv/StorageGetters.java -index 568e369..229b911 100644 ---- a/node_modules/react-native-mmkv-storage/android/src/main/java/com/ammarahmed/mmkv/StorageGetters.java -+++ b/node_modules/react-native-mmkv-storage/android/src/main/java/com/ammarahmed/mmkv/StorageGetters.java -@@ -52,8 +52,12 @@ public class StorageGetters { - case Constants.DATA_TYPE_MAP: - case Constants.DATA_TYPE_ARRAY: - Bundle bundle = kv.decodeParcelable(key, Bundle.class); -- WritableMap map = Arguments.fromBundle(bundle); -- callback.invoke(null, map); -+ if (bundle == null) { -+ callback.invoke(null, null); -+ } else { -+ WritableMap map = Arguments.fromBundle(bundle); -+ callback.invoke(null, map); -+ } - break; - - } -diff --git a/node_modules/react-native-mmkv-storage/ios/SecureStorage.m b/node_modules/react-native-mmkv-storage/ios/SecureStorage.m -index 70f3a01..30d7251 100644 ---- a/node_modules/react-native-mmkv-storage/ios/SecureStorage.m -+++ b/node_modules/react-native-mmkv-storage/ios/SecureStorage.m -@@ -46,7 +46,6 @@ - (void) setSecureKey: (NSString *)key value:(NSString *)value - - (NSString *) getSecureKey:(NSString *)key - callback:(RCTResponseSenderBlock)callback - { -- - @try { - [self handleAppUninstallation]; - NSString *value = [self searchKeychainCopyMatching:key]; -@@ -130,7 +129,8 @@ - (void) removeSecureKey:(NSString *)key - - - (NSMutableDictionary *)newSearchDictionary:(NSString *)identifier { - NSMutableDictionary *searchDictionary = [[NSMutableDictionary alloc] init]; -- serviceName = [[NSBundle mainBundle] bundleIdentifier]; -+ // this value is shared by main app and extensions, so, is the best to be used here -+ serviceName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"AppGroup"]; - - [searchDictionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass]; - -@@ -139,6 +139,9 @@ - (NSMutableDictionary *)newSearchDictionary:(NSString *)identifier { - [searchDictionary setObject:encodedIdentifier forKey:(id)kSecAttrAccount]; - [searchDictionary setObject:serviceName forKey:(id)kSecAttrService]; - -+ NSString *keychainGroup = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"KeychainGroup"]; -+ [searchDictionary setObject:keychainGroup forKey:(id)kSecAttrAccessGroup]; -+ - return searchDictionary; - } - -@@ -240,10 +243,13 @@ - (void)clearSecureKeyStore - - - (void)handleAppUninstallation - { -- if (![[NSUserDefaults standardUserDefaults] boolForKey:@"RnSksIsAppInstalled"]) { -+ // use app group user defaults to prevent clear when it's share extension -+ NSString *suiteName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"AppGroup"]; -+ NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:suiteName]; -+ if (![userDefaults boolForKey:@"RnSksIsAppInstalled"]) { - [self clearSecureKeyStore]; -- [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"RnSksIsAppInstalled"]; -- [[NSUserDefaults standardUserDefaults] synchronize]; -+ [userDefaults setBool:YES forKey:@"RnSksIsAppInstalled"]; -+ [userDefaults synchronize]; - } - } - -diff --git a/node_modules/react-native-mmkv-storage/ios/StorageGetters.m b/node_modules/react-native-mmkv-storage/ios/StorageGetters.m -index 909d056..d62814a 100644 ---- a/node_modules/react-native-mmkv-storage/ios/StorageGetters.m -+++ b/node_modules/react-native-mmkv-storage/ios/StorageGetters.m -@@ -38,8 +38,13 @@ +(void) getItem:(NSString *)ID - if ([kv containsKey:key]) { - switch (type.integerValue) { - case 1: -- -- callback(@[[NSNull null], [kv getObjectOfClass:NSString.class forKey:key]]); -+ { -+ NSString* string = [kv getObjectOfClass:NSString.class forKey:key]; -+ if (!string) { -+ string = (NSString *)[NSNull null]; -+ } -+ callback(@[[NSNull null], string]); -+ } - break; - case 2: - -diff --git a/node_modules/react-native-mmkv-storage/ios/StorageIndexer.m b/node_modules/react-native-mmkv-storage/ios/StorageIndexer.m -index e7c914b..891cf93 100644 ---- a/node_modules/react-native-mmkv-storage/ios/StorageIndexer.m -+++ b/node_modules/react-native-mmkv-storage/ios/StorageIndexer.m -@@ -58,7 +58,11 @@ + (void) removeKeyFromIndexer:(MMKV *)kv - if (index != NULL && [index containsObject:key]) { - - [index removeObject:key]; -- [kv setObject:index forKey:stringsIndexKey]; -+ if (!index || [index count] == 0) { -+ [kv removeValueForKey:stringsIndexKey]; -+ } else { -+ [kv setObject:index forKey:stringsIndexKey]; -+ } - return; - } - -@@ -67,7 +71,11 @@ + (void) removeKeyFromIndexer:(MMKV *)kv - if (index != NULL && [index containsObject:key]) { - - [index removeObject:key]; -- [kv setObject:index forKey:intIndexKey]; -+ if (!index || [index count] == 0) { -+ [kv removeValueForKey:intIndexKey]; -+ } else { -+ [kv setObject:index forKey:intIndexKey]; -+ } - return; - } - -@@ -76,7 +84,11 @@ + (void) removeKeyFromIndexer:(MMKV *)kv - if (index != NULL && [index containsObject:key]) { - - [index removeObject:key]; -- [kv setObject:index forKey:boolIndexKey]; -+ if (!index || [index count] == 0) { -+ [kv removeValueForKey:boolIndexKey]; -+ } else { -+ [kv setObject:index forKey:boolIndexKey]; -+ } - return; - } - -@@ -85,7 +97,11 @@ + (void) removeKeyFromIndexer:(MMKV *)kv - if (index != NULL && [index containsObject:key]) { - - [index removeObject:key]; -- [kv setObject:index forKey:mapIndexKey]; -+ if (!index || [index count] == 0) { -+ [kv removeValueForKey:mapIndexKey]; -+ } else { -+ [kv setObject:index forKey:mapIndexKey]; -+ } - return; - } - -@@ -94,7 +110,11 @@ + (void) removeKeyFromIndexer:(MMKV *)kv - if (index != NULL && [index containsObject:key]) { - - [index removeObject:key]; -- [kv setObject:index forKey:arrayIndexKey]; -+ if (!index || [index count] == 0) { -+ [kv removeValueForKey:arrayIndexKey]; -+ } else { -+ [kv setObject:index forKey:arrayIndexKey]; -+ } - return; - } - diff --git a/patches/react-native-mmkv-storage+0.6.12.patch b/patches/react-native-mmkv-storage+0.6.12.patch new file mode 100644 index 00000000..55142169 --- /dev/null +++ b/patches/react-native-mmkv-storage+0.6.12.patch @@ -0,0 +1,44 @@ +diff --git a/node_modules/react-native-mmkv-storage/ios/SecureStorage.m b/node_modules/react-native-mmkv-storage/ios/SecureStorage.m +index eacd030..f4b1b96 100644 +--- a/node_modules/react-native-mmkv-storage/ios/SecureStorage.m ++++ b/node_modules/react-native-mmkv-storage/ios/SecureStorage.m +@@ -96,6 +96,9 @@ NSString *serviceName = nil; + + - (NSMutableDictionary *)newSearchDictionary:(NSString *)identifier { + NSMutableDictionary *searchDictionary = [[NSMutableDictionary alloc] init]; ++ // this value is shared by main app and extensions, so, is the best to be used here ++ serviceName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"AppGroup"]; ++ + if(serviceName == nil){ + serviceName = [[NSBundle mainBundle] bundleIdentifier]; + } +@@ -107,6 +110,9 @@ NSString *serviceName = nil; + [searchDictionary setObject:encodedIdentifier forKey:(id)kSecAttrAccount]; + [searchDictionary setObject:serviceName forKey:(id)kSecAttrService]; + ++ NSString *keychainGroup = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"KeychainGroup"]; ++ [searchDictionary setObject:keychainGroup forKey:(id)kSecAttrAccessGroup]; ++ + return searchDictionary; + } + +@@ -208,11 +214,14 @@ NSString *serviceName = nil; + + - (void)handleAppUninstallation + { +- // if (![[NSUserDefaults standardUserDefaults] boolForKey:@"RnSksIsAppInstalled"]) { +- // [self clearSecureKeyStore]; +- //[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"IsAppInstalled"]; +- [[NSUserDefaults standardUserDefaults] synchronize]; +- // } ++ // use app group user defaults to prevent clear when it's share extension ++ NSString *suiteName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"AppGroup"]; ++ NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:suiteName]; ++ if (![userDefaults boolForKey:@"RnSksIsAppInstalled"]) { ++ [self clearSecureKeyStore]; ++ [userDefaults setBool:YES forKey:@"RnSksIsAppInstalled"]; ++ [userDefaults synchronize]; ++ } + } + + - (void) setServiceName:(NSString *)_serviceName diff --git a/patches/react-native-webview+10.3.2.patch b/patches/react-native-webview+10.3.2.patch index 08b0e972..df46fcf8 100644 --- a/patches/react-native-webview+10.3.2.patch +++ b/patches/react-native-webview+10.3.2.patch @@ -102,7 +102,7 @@ index ab869cf..08ce7ce 100644 ignoreErrFailedForThisURL = url; } diff --git a/node_modules/react-native-webview/apple/RNCWebView.m b/node_modules/react-native-webview/apple/RNCWebView.m -index 02b4238..04bad05 100644 +index 02b4238..e0635ed 100644 --- a/node_modules/react-native-webview/apple/RNCWebView.m +++ b/node_modules/react-native-webview/apple/RNCWebView.m @@ -17,6 +17,9 @@ @@ -115,7 +115,7 @@ index 02b4238..04bad05 100644 static NSTimer *keyboardTimer; static NSString *const HistoryShimName = @"ReactNativeHistoryShim"; static NSString *const MessageHandlerName = @"ReactNativeWebView"; -@@ -737,6 +740,68 @@ + (void)setCustomCertificatesForHost:(nullable NSDictionary*)certificates { +@@ -737,6 +740,68 @@ static NSDictionary* customCertificatesForHost; customCertificatesForHost = certificates; } @@ -184,29 +184,27 @@ index 02b4238..04bad05 100644 - (void) webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable))completionHandler -@@ -746,7 +811,34 @@ - (void) webView:(WKWebView *)webView +@@ -746,7 +811,32 @@ static NSDictionary* customCertificatesForHost; host = webView.URL.host; } if ([[challenge protectionSpace] authenticationMethod] == NSURLAuthenticationMethodClientCertificate) { - completionHandler(NSURLSessionAuthChallengeUseCredential, clientAuthenticationCredential); + NSString *host = challenge.protectionSpace.host; -+ ++ + // Read the clientSSL info from MMKV + __block NSDictionary *clientSSL; + SecureStorage *secureStorage = [[SecureStorage alloc] init]; + + // https://github.com/ammarahm-ed/react-native-mmkv-storage/blob/master/src/loader.js#L31 -+ [secureStorage getSecureKey:[self stringToHex:@"com.MMKV.default"] callback:^(NSArray *response) { -+ // Error happened -+ if ([response objectAtIndex:0] != [NSNull null]) { -+ return; -+ } -+ NSString *key = [response objectAtIndex:1]; -+ NSData *cryptKey = [key dataUsingEncoding:NSUTF8StringEncoding]; -+ MMKV *mmkv = [MMKV mmkvWithID:@"default" cryptKey:cryptKey mode:MMKVMultiProcess]; ++ NSString *key = [secureStorage getSecureKey:[self stringToHex:@"com.MMKV.default"]]; + -+ clientSSL = [mmkv getObjectOfClass:[NSDictionary class] forKey:host]; -+ }]; ++ if (key == NULL) { ++ return; ++ } ++ ++ NSData *cryptKey = [key dataUsingEncoding:NSUTF8StringEncoding]; ++ MMKV *mmkv = [MMKV mmkvWithID:@"default" cryptKey:cryptKey mode:MMKVMultiProcess]; ++ clientSSL = [mmkv getObjectOfClass:[NSDictionary class] forKey:host]; + + NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; + diff --git a/patches/rn-fetch-blob+0.12.0.patch b/patches/rn-fetch-blob+0.12.0.patch index c1e80942..5b0c59c5 100644 --- a/patches/rn-fetch-blob+0.12.0.patch +++ b/patches/rn-fetch-blob+0.12.0.patch @@ -23,7 +23,7 @@ index 602d51d..920d975 100644 public String getName() { return "RNFetchBlob"; diff --git a/node_modules/rn-fetch-blob/ios/RNFetchBlobRequest.m b/node_modules/rn-fetch-blob/ios/RNFetchBlobRequest.m -index cdbe6b1..bee6228 100644 +index cdbe6b1..1699c6c 100644 --- a/node_modules/rn-fetch-blob/ios/RNFetchBlobRequest.m +++ b/node_modules/rn-fetch-blob/ios/RNFetchBlobRequest.m @@ -15,6 +15,9 @@ @@ -36,12 +36,18 @@ index cdbe6b1..bee6228 100644 typedef NS_ENUM(NSUInteger, ResponseFormat) { UTF8, -@@ -450,16 +453,109 @@ - (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSen +@@ -450,16 +453,107 @@ typedef NS_ENUM(NSUInteger, ResponseFormat) { } } +- +-- (void) URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable credantial))completionHandler +-(NSURLCredential *)getUrlCredential:(NSURLAuthenticationChallenge *)challenge path:(NSString *)path password:(NSString *)password -+{ + { +- if ([[options valueForKey:CONFIG_TRUSTY] boolValue]) { +- completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]); +- } else { +- completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]); + NSString *authMethod = [[challenge protectionSpace] authenticationMethod]; + SecTrustRef serverTrust = challenge.protectionSpace.serverTrust; + @@ -75,7 +81,7 @@ index cdbe6b1..bee6228 100644 + NSDictionary* firstItem = nil; + if ((status == errSecSuccess) && ([items count]>0)) { + firstItem = items[0]; -+ } + } + + SecIdentityRef identity = (SecIdentityRef)CFBridgingRetain(firstItem[(id)kSecImportItemIdentity]); + SecCertificateRef certificate = NULL; @@ -89,17 +95,12 @@ index cdbe6b1..bee6228 100644 + + return [NSURLCredential credentialWithIdentity:identity certificates:certificates persistence:NSURLCredentialPersistenceNone]; + } - --- (void) URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable credantial))completionHandler ++ + return [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; +} + +- (NSString *)stringToHex:(NSString *)string - { -- if ([[options valueForKey:CONFIG_TRUSTY] boolValue]) { -- completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]); -- } else { -- completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]); ++{ + char *utf8 = (char *)[string UTF8String]; + NSMutableString *hex = [NSMutableString string]; + while (*utf8) [hex appendFormat:@"%02X", *utf8++ & 0x00FF]; @@ -110,23 +111,21 @@ index cdbe6b1..bee6228 100644 +-(void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler +{ + NSString *host = challenge.protectionSpace.host; -+ ++ + // Read the clientSSL info from MMKV + __block NSDictionary *clientSSL; + SecureStorage *secureStorage = [[SecureStorage alloc] init]; + + // https://github.com/ammarahm-ed/react-native-mmkv-storage/blob/master/src/loader.js#L31 -+ [secureStorage getSecureKey:[self stringToHex:@"com.MMKV.default"] callback:^(NSArray *response) { -+ // Error happened -+ if ([response objectAtIndex:0] != [NSNull null]) { -+ return; - } -+ NSString *key = [response objectAtIndex:1]; -+ NSData *cryptKey = [key dataUsingEncoding:NSUTF8StringEncoding]; -+ MMKV *mmkv = [MMKV mmkvWithID:@"default" cryptKey:cryptKey mode:MMKVMultiProcess]; ++ NSString *key = [secureStorage getSecureKey:[self stringToHex:@"com.MMKV.default"]]; + -+ clientSSL = [mmkv getObjectOfClass:[NSDictionary class] forKey:host]; -+ }]; ++ if (key == NULL) { ++ return; ++ } ++ ++ NSData *cryptKey = [key dataUsingEncoding:NSUTF8StringEncoding]; ++ MMKV *mmkv = [MMKV mmkvWithID:@"default" cryptKey:cryptKey mode:MMKVMultiProcess]; ++ clientSSL = [mmkv getObjectOfClass:[NSDictionary class] forKey:host]; + + NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; + diff --git a/yarn.lock b/yarn.lock index 8b576f36..5e789fb7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14424,10 +14424,10 @@ react-native-mime-types@2.3.0: dependencies: mime-db "~1.37.0" -react-native-mmkv-storage@0.3.5: - version "0.3.5" - resolved "https://registry.yarnpkg.com/react-native-mmkv-storage/-/react-native-mmkv-storage-0.3.5.tgz#9c2f064c0efdaf960e9646c68dc6ae7f11640fa5" - integrity sha512-xp0E55Qdi81k8CTeq3PUrXGwT2tMmfNfmYvZ7Emq9qWpvg3ko1/M6B1kXEXOgEou/hgqB503TGcsR/mpN5HSMA== +react-native-mmkv-storage@0.6.12: + version "0.6.12" + resolved "https://registry.yarnpkg.com/react-native-mmkv-storage/-/react-native-mmkv-storage-0.6.12.tgz#e9e052e26c3dea6818211919d2586cb260ee8e24" + integrity sha512-9gJlnGSAJkeWanNE24GPqEn4NMiNFVBpPsTZAQ5YDEKEX/gG7bGdHCTNkXc6L826QxLEPKjpPmf9ZqgPM2G8TQ== react-native-modal@11.10.0: version "11.10.0" @@ -14632,7 +14632,7 @@ react-native-webview@10.3.2: react-native@RocketChat/react-native#0.64.2: version "0.64.2" - resolved "https://codeload.github.com/RocketChat/react-native/tar.gz/6c015e1ec93d2f9a79a7033a7e160d76cb4df4aa" + resolved "https://codeload.github.com/RocketChat/react-native/tar.gz/eefc7ead3d343d5514118d79ff5e0ac7fc1761ec" dependencies: "@jest/create-cache-key-function" "^27.0.2" "@react-native-community/cli" "^5.0.1-alpha.1"