From abd97221333d5b2cf2b0635d1a363b7b5364f7d7 Mon Sep 17 00:00:00 2001 From: Alex Junior Date: Wed, 9 Mar 2022 13:49:52 -0300 Subject: [PATCH 1/9] chore: add rest api return (#3864) --- app/definitions/IUser.ts | 1 + app/definitions/rest/v1/users.ts | 2 +- app/lib/rocketchat/services/restApi.ts | 4 +--- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/app/definitions/IUser.ts b/app/definitions/IUser.ts index aaf49bd28..6dfbdf5e7 100644 --- a/app/definitions/IUser.ts +++ b/app/definitions/IUser.ts @@ -111,6 +111,7 @@ export interface INotificationPreferences { desktopNotifications: TNotifications; pushNotifications: TNotifications; emailNotificationMode?: 'mentions' | 'nothing'; + language?: string; } export interface IUserPreferences { diff --git a/app/definitions/rest/v1/users.ts b/app/definitions/rest/v1/users.ts index e8e27c9d4..b72f4821a 100644 --- a/app/definitions/rest/v1/users.ts +++ b/app/definitions/rest/v1/users.ts @@ -26,7 +26,7 @@ export type UsersEndpoints = { }; }; 'users.setPreferences': { - POST: (params: { userId: IUser['_id']; data: Partial }) => { + POST: (params: { userId?: IUser['_id']; data: Partial }) => { user: IUserPreferences; success: boolean; }; diff --git a/app/lib/rocketchat/services/restApi.ts b/app/lib/rocketchat/services/restApi.ts index aa3e40142..6bb6e6c1c 100644 --- a/app/lib/rocketchat/services/restApi.ts +++ b/app/lib/rocketchat/services/restApi.ts @@ -551,10 +551,8 @@ export const saveUserProfile = (data: any, customFields?: any): any => // @ts-ignore sdk.post('users.updateOwnBasicInfo', { data, customFields }); -export const saveUserPreferences = (data: any): any => +export const saveUserPreferences = (data: Partial) => // RC 0.62.0 - // TODO: missing definitions from server - // @ts-ignore sdk.post('users.setPreferences', { data }); export const saveNotificationSettings = (roomId: string, notifications: any): any => From b9629251216d4ef628e18a0dc78f2cf9351c33a3 Mon Sep 17 00:00:00 2001 From: Alex Junior Date: Wed, 9 Mar 2022 14:04:25 -0300 Subject: [PATCH 2/9] chore: add rest api return (#3865) --- app/definitions/IRoom.ts | 12 +++++++++++- app/definitions/IUser.ts | 2 +- app/definitions/rest/v1/rooms.ts | 5 ++++- app/lib/rocketchat/services/restApi.ts | 6 ++---- app/views/NotificationPreferencesView/index.tsx | 3 ++- 5 files changed, 20 insertions(+), 8 deletions(-) diff --git a/app/definitions/IRoom.ts b/app/definitions/IRoom.ts index 378e7b798..552d7b5a9 100644 --- a/app/definitions/IRoom.ts +++ b/app/definitions/IRoom.ts @@ -4,7 +4,7 @@ import { IMessage } from './IMessage'; import { IRocketChatRecord } from './IRocketChatRecord'; import { IServedBy } from './IServedBy'; import { IVisitor, SubscriptionType } from './ISubscription'; -import { IUser } from './IUser'; +import { IUser, TNotifications } from './IUser'; interface IRequestTranscript { email: string; @@ -202,3 +202,13 @@ export interface IServerRoom extends IRocketChatRecord { livechatData?: any; tags?: string[]; } + +export interface IRoomNotifications { + disableNotifications?: boolean; + muteGroupMentions?: boolean; + hideUnreadStatus?: boolean; + audioNotificationsValue?: string; + desktopNotifications?: TNotifications; + mobilePushNotifications?: TNotifications; + emailNotifications?: TNotifications; +} diff --git a/app/definitions/IUser.ts b/app/definitions/IUser.ts index 6dfbdf5e7..93bce8805 100644 --- a/app/definitions/IUser.ts +++ b/app/definitions/IUser.ts @@ -103,7 +103,7 @@ export interface IUserSettings { [key: string]: any; }; } -type TNotifications = 'default' | 'all' | 'mentions' | 'nothing'; +export type TNotifications = 'default' | 'all' | 'mentions' | 'nothing'; export interface INotificationPreferences { id: string; diff --git a/app/definitions/rest/v1/rooms.ts b/app/definitions/rest/v1/rooms.ts index d4bb971bc..ebcb29cd4 100644 --- a/app/definitions/rest/v1/rooms.ts +++ b/app/definitions/rest/v1/rooms.ts @@ -1,5 +1,5 @@ import type { IMessage } from '../../IMessage'; -import type { IServerRoom } from '../../IRoom'; +import type { IRoomNotifications, IServerRoom } from '../../IRoom'; import type { IUser } from '../../IUser'; export type RoomsEndpoints = { @@ -41,4 +41,7 @@ export type RoomsEndpoints = { 'rooms.favorite': { POST: (params: { roomId: string; favorite: boolean }) => {}; }; + 'rooms.saveNotification': { + POST: (params: { roomId: string; notifications: IRoomNotifications }) => {}; + }; }; diff --git a/app/lib/rocketchat/services/restApi.ts b/app/lib/rocketchat/services/restApi.ts index 6bb6e6c1c..8eabe4566 100644 --- a/app/lib/rocketchat/services/restApi.ts +++ b/app/lib/rocketchat/services/restApi.ts @@ -1,7 +1,7 @@ import sdk from './sdk'; import { TEAM_TYPE } from '../../../definitions/ITeam'; import roomTypeToApiType, { RoomTypes } from '../methods/roomTypeToApiType'; -import { SubscriptionType, INotificationPreferences } from '../../../definitions'; +import { SubscriptionType, INotificationPreferences, IRoomNotifications } from '../../../definitions'; import { ISpotlight } from '../../../definitions/ISpotlight'; export const createChannel = ({ @@ -555,10 +555,8 @@ export const saveUserPreferences = (data: Partial) => // RC 0.62.0 sdk.post('users.setPreferences', { data }); -export const saveNotificationSettings = (roomId: string, notifications: any): any => +export const saveNotificationSettings = (roomId: string, notifications: IRoomNotifications) => // RC 0.63.0 - // TODO: missing definitions from server - // @ts-ignore sdk.post('rooms.saveNotification', { roomId, notifications }); export const getSingleMessage = (msgId: string) => diff --git a/app/views/NotificationPreferencesView/index.tsx b/app/views/NotificationPreferencesView/index.tsx index 71edd8d05..bf76d9e38 100644 --- a/app/views/NotificationPreferencesView/index.tsx +++ b/app/views/NotificationPreferencesView/index.tsx @@ -18,6 +18,7 @@ import log, { events, logEvent } from '../../utils/log'; import sharedStyles from '../Styles'; import { OPTIONS } from './options'; import { ChatsStackParamList } from '../../stacks/types'; +import { IRoomNotifications } from '../../definitions'; const styles = StyleSheet.create({ pickerText: { @@ -73,7 +74,7 @@ class NotificationPreferencesView extends React.Component { + saveNotificationSettings = async (key: string, value: string | boolean, params: IRoomNotifications) => { // @ts-ignore logEvent(events[`NP_${key.toUpperCase()}`]); const { room } = this.state; From 60ecbe284ca6e18430f1b011c9eff78a342779e2 Mon Sep 17 00:00:00 2001 From: Reinaldo Neto <47038980+reinaldonetof@users.noreply.github.com> Date: Wed, 9 Mar 2022 15:10:36 -0300 Subject: [PATCH 3/9] Chore: Migrate REST API - saveUserProfile to Typescript (#3863) * Chore: Migrate REST API - saveUserProfile to Typescript * minor tweak --- .../IProfileViewInterfaces.ts} | 2 +- app/definitions/rest/v1/users.ts | 6 ++++++ app/lib/rocketchat/services/restApi.ts | 5 ++--- app/views/ProfileView/index.tsx | 10 +++++++++- app/views/RoomInfoEditView/index.tsx | 2 +- 5 files changed, 19 insertions(+), 6 deletions(-) rename app/{views/ProfileView/interfaces.ts => definitions/IProfileViewInterfaces.ts} (96%) diff --git a/app/views/ProfileView/interfaces.ts b/app/definitions/IProfileViewInterfaces.ts similarity index 96% rename from app/views/ProfileView/interfaces.ts rename to app/definitions/IProfileViewInterfaces.ts index 0fe592f32..491f7b201 100644 --- a/app/views/ProfileView/interfaces.ts +++ b/app/definitions/IProfileViewInterfaces.ts @@ -1,7 +1,7 @@ import { StackNavigationProp } from '@react-navigation/stack'; import React from 'react'; -import { ProfileStackParamList } from '../../stacks/types'; +import { ProfileStackParamList } from '../stacks/types'; export interface IUser { id: string; diff --git a/app/definitions/rest/v1/users.ts b/app/definitions/rest/v1/users.ts index b72f4821a..3b92eaef0 100644 --- a/app/definitions/rest/v1/users.ts +++ b/app/definitions/rest/v1/users.ts @@ -1,3 +1,4 @@ +import { IParams } from '../../IProfileViewInterfaces'; import type { ITeam } from '../../ITeam'; import type { IUser } from '../../IUser'; import { INotificationPreferences, IUserPreferences, IUserRegistered } from '../../IUser'; @@ -37,4 +38,9 @@ export type UsersEndpoints = { 'users.setStatus': { POST: (params: { status: string; message: string }) => {}; }; + 'users.updateOwnBasicInfo': { + POST: (params: { data: IParams | Pick; customFields?: { [key: string | number]: string } }) => { + user: IUser; + }; + }; }; diff --git a/app/lib/rocketchat/services/restApi.ts b/app/lib/rocketchat/services/restApi.ts index 8eabe4566..99eea86f4 100644 --- a/app/lib/rocketchat/services/restApi.ts +++ b/app/lib/rocketchat/services/restApi.ts @@ -3,6 +3,7 @@ import { TEAM_TYPE } from '../../../definitions/ITeam'; import roomTypeToApiType, { RoomTypes } from '../methods/roomTypeToApiType'; import { SubscriptionType, INotificationPreferences, IRoomNotifications } from '../../../definitions'; import { ISpotlight } from '../../../definitions/ISpotlight'; +import { IParams } from '../../../definitions/IProfileViewInterfaces'; export const createChannel = ({ name, @@ -545,10 +546,8 @@ export const saveRoomSettings = ( // RC 0.55.0 sdk.methodCallWrapper('saveRoomSettings', rid, params); -export const saveUserProfile = (data: any, customFields?: any): any => +export const saveUserProfile = (data: IParams | Pick, customFields?: { [key: string | number]: string }) => // RC 0.62.2 - // TODO: missing definitions from server - // @ts-ignore sdk.post('users.updateOwnBasicInfo', { data, customFields }); export const saveUserPreferences = (data: Partial) => diff --git a/app/views/ProfileView/index.tsx b/app/views/ProfileView/index.tsx index fc409002d..d7ecbc399 100644 --- a/app/views/ProfileView/index.tsx +++ b/app/views/ProfileView/index.tsx @@ -31,7 +31,15 @@ import { withTheme } from '../../theme'; import { getUserSelector } from '../../selectors/login'; import SafeAreaView from '../../containers/SafeAreaView'; import styles from './styles'; -import { IAvatar, IAvatarButton, INavigationOptions, IParams, IProfileViewProps, IProfileViewState, IUser } from './interfaces'; +import { + IAvatar, + IAvatarButton, + INavigationOptions, + IParams, + IProfileViewProps, + IProfileViewState, + IUser +} from '../../definitions/IProfileViewInterfaces'; class ProfileView extends React.Component { private name: any; diff --git a/app/views/RoomInfoEditView/index.tsx b/app/views/RoomInfoEditView/index.tsx index d79af6d40..6f8b8a274 100644 --- a/app/views/RoomInfoEditView/index.tsx +++ b/app/views/RoomInfoEditView/index.tsx @@ -34,7 +34,7 @@ import log, { events, logEvent } from '../../utils/log'; import { MessageTypeValues } from '../../utils/messageTypes'; import random from '../../utils/random'; import scrollPersistTaps from '../../utils/scrollPersistTaps'; -import { IAvatar } from '../ProfileView/interfaces'; +import { IAvatar } from '../../definitions/IProfileViewInterfaces'; import sharedStyles from '../Styles'; import styles from './styles'; import SwitchContainer from './SwitchContainer'; From cd00366613b1f77eb5a1f0478f87856296ab2327 Mon Sep 17 00:00:00 2001 From: Alex Junior Date: Wed, 9 Mar 2022 16:41:26 -0300 Subject: [PATCH 4/9] Chore: Update react-native-mmkv-storage to 0.6.12 (#3634) * chore: updating mmkv library * feat: updating android ejson to use the getSecureKey new version * feat: updating IOS files to use the getSecureKey new version * feat: changing all mmkv methods to use sync calls * feat: changing mmkv methods from Screen lock * feat: changing all mmkv methods from login, ssl certificate and change/add server * feat: changing all mmkv methods from login, ssl certificate and change/add server * feat: changing all mmkv methods from logout * feat: changing all mmkv methods from e2e * fix: small fix at encryption and server drop down * feat: changing all mmkv methods from set theme * feat: changing all mmkv methods from openLink * fix: setting up mmkv to works property on Android * fix: fix an error to set the theme when open the app * refactor: change the react-native branch (temporary) * refactor: removing all `Async` from mmkv functions name * refactor: removing await from unnecessary functions, removing console.log and update cocoapods * refactor: removing unnecessary undefined from methods * feat: creating a custom hook for mmkv * refactor: changing the fetchPasscode to use the useUserPreferences hook * refactor: changing setTheme from app/index * refactor: small fix on setTheme * chore: update mmkv to 0.6.11 * chore: update mmkv to 0.6.11 * chore: minor tweak * chore: update mmkv to 0.6.12 * chore: mock NativeModules * chore: fix test mmkv * chore: removing custom MMKV JSI module, since is no more necessary after 0.6.11 version * feat: removing some async calls from mmkv after update from develop * feat: creating a function to get the initialTheme * feat: removing unnecessary try/catch * fix: fixing the blink white when open the app * feat: changing useMMKVStorage to create from mmkv lib * test: creating a mock for mmkv create function * chore: fix errors on tablet * minor tweak --- __tests__/Storyshots.test.js | 20 +++ .../rocket/reactnative/MainApplication.java | 3 +- .../java/chat/rocket/reactnative/Ejson.java | 12 +- app/containers/Passcode/PasscodeEnter.tsx | 10 +- app/index.tsx | 21 ++- app/lib/encryption/encryption.ts | 10 +- app/lib/methods/logout.ts | 30 ++-- app/lib/rocketchat/rocketchat.js | 17 +- app/lib/userPreferences.ts | 33 ++-- app/sagas/deepLinking.js | 4 +- app/sagas/encryption.js | 8 +- app/sagas/init.js | 14 +- app/sagas/login.js | 6 +- app/sagas/selectServer.js | 17 +- app/share.tsx | 17 +- app/utils/localAuthentication.ts | 8 +- app/utils/openLink.ts | 2 +- app/utils/sslPinning.ts | 14 +- app/utils/theme.ts | 11 ++ app/views/DefaultBrowserView.tsx | 8 +- app/views/DisplayPrefsView.tsx | 44 +++-- app/views/E2ESaveYourPasswordView.tsx | 8 +- app/views/NewServerView/index.tsx | 12 +- app/views/RoomsListView/ServerDropdown.tsx | 2 +- app/views/RoomsListView/index.tsx | 2 +- app/views/ScreenLockConfigView.tsx | 6 +- app/views/ThemeView.tsx | 4 +- ios/Podfile.lock | 20 +-- ios/Shared/RocketChat/Storage.swift | 18 +- package.json | 2 +- patches/react-native-mmkv-storage+0.3.5.patch | 157 ------------------ .../react-native-mmkv-storage+0.6.12.patch | 44 +++++ patches/react-native-webview+10.3.2.patch | 26 ++- patches/rn-fetch-blob+0.12.0.patch | 43 +++-- yarn.lock | 10 +- 35 files changed, 276 insertions(+), 387 deletions(-) delete mode 100644 patches/react-native-mmkv-storage+0.3.5.patch create mode 100644 patches/react-native-mmkv-storage+0.6.12.patch diff --git a/__tests__/Storyshots.test.js b/__tests__/Storyshots.test.js index 1315f9bb5..638fbb0ee 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 bf7dc5ef7..c0361e78d 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 e44c6b722..fb083df13 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 0a9b6b1f9..f61bf91a3 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 89e6c5270..508bec332 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 e9e3daebe..0086fe102 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 cbcacdf00..6396d5e8a 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 6f4818da6..a9720202a 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 900e6a145..c10025394 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 cdf938bcd..e99f8b72b 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 3671e83f4..a7efced62 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 346e3be15..adde0dad3 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 c67af42d1..492191ee4 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 8a756d91a..1dddbe874 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 141268b4d..9a2255f7d 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 4048b3add..70abcec72 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 27b100228..76ccc9a5b 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 0e9d8e058..1f2e848c8 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 895f70860..6e726c75c 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 f793eb920..4405cd0a7 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 cc7bac83f..72be28097 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 06d562803..51f20a980 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 be40179f0..299928100 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 b73e7b9be..b3862bed2 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 08c8e3c0d..7f6b2189c 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 34d9f8822..7f2496897 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 5d7325a8a..cd28c186e 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 e39d75717..000000000 --- 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 000000000..551421698 --- /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 08b0e9721..df46fcf8b 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 c1e809423..5b0c59c59 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 8b576f36e..5e789fb72 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" From 4ea8d613a939fed07ed2351b47d013c82ccff2ef Mon Sep 17 00:00:00 2001 From: Alex Junior Date: Wed, 9 Mar 2022 21:16:20 -0300 Subject: [PATCH 5/9] Chore: Migrate REST API - getUserPreferences to Typescript (#3830) --- app/definitions/rest/v1/users.ts | 6 ++++++ app/lib/rocketchat/services/restApi.ts | 4 +--- .../UserNotificationPreferencesView/index.tsx | 15 +++++++-------- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/app/definitions/rest/v1/users.ts b/app/definitions/rest/v1/users.ts index 3b92eaef0..0a9644965 100644 --- a/app/definitions/rest/v1/users.ts +++ b/app/definitions/rest/v1/users.ts @@ -43,4 +43,10 @@ export type UsersEndpoints = { user: IUser; }; }; + 'users.getPreferences': { + GET: (params: { userId: IUser['_id'] }) => { + preferences: INotificationPreferences; + success: boolean; + }; + }; }; diff --git a/app/lib/rocketchat/services/restApi.ts b/app/lib/rocketchat/services/restApi.ts index 99eea86f4..5c834ae5a 100644 --- a/app/lib/rocketchat/services/restApi.ts +++ b/app/lib/rocketchat/services/restApi.ts @@ -290,10 +290,8 @@ export const getChannelInfo = (roomId: string) => // RC 0.48.0 sdk.get('channels.info', { roomId }); -export const getUserPreferences = (userId: string): any => +export const getUserPreferences = (userId: string) => // RC 0.62.0 - // TODO: missing definitions from server - // @ts-ignore sdk.get('users.getPreferences', { userId }); export const getRoomInfo = (roomId: string) => diff --git a/app/views/UserNotificationPreferencesView/index.tsx b/app/views/UserNotificationPreferencesView/index.tsx index 79a681732..a4d23291b 100644 --- a/app/views/UserNotificationPreferencesView/index.tsx +++ b/app/views/UserNotificationPreferencesView/index.tsx @@ -15,6 +15,7 @@ import { getUserSelector } from '../../selectors/login'; import sharedStyles from '../Styles'; import { OPTIONS } from './options'; import { ProfileStackParamList } from '../../stacks/types'; +import { INotificationPreferences } from '../../definitions'; const styles = StyleSheet.create({ pickerText: { @@ -26,11 +27,7 @@ const styles = StyleSheet.create({ type TKey = 'desktopNotifications' | 'pushNotifications' | 'emailNotificationMode'; interface IUserNotificationPreferencesViewState { - preferences: { - desktopNotifications?: string; - pushNotifications?: string; - emailNotificationMode?: string; - }; + preferences: INotificationPreferences; loading: boolean; } @@ -53,7 +50,7 @@ class UserNotificationPreferencesView extends React.Component< constructor(props: IUserNotificationPreferencesViewProps) { super(props); this.state = { - preferences: {}, + preferences: {} as INotificationPreferences, loading: false }; } @@ -62,8 +59,10 @@ class UserNotificationPreferencesView extends React.Component< const { user } = this.props; const { id } = user; const result = await RocketChat.getUserPreferences(id); - const { preferences } = result; - this.setState({ preferences, loading: true }); + if (result.success) { + const { preferences } = result; + this.setState({ preferences, loading: true }); + } } findDefaultOption = (key: TKey) => { From 5d284532018eeab7776a1d2a3e4b2bbce8ab4d44 Mon Sep 17 00:00:00 2001 From: Reinaldo Neto <47038980+reinaldonetof@users.noreply.github.com> Date: Thu, 10 Mar 2022 09:25:25 -0300 Subject: [PATCH 6/9] Chore: Migrate REST API - getAvatarSuggestion to Typescript (#3869) --- app/definitions/IProfileViewInterfaces.ts | 16 +++++++++------- app/lib/rocketchat/services/restApi.ts | 4 ++-- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/app/definitions/IProfileViewInterfaces.ts b/app/definitions/IProfileViewInterfaces.ts index 491f7b201..ba6d0659b 100644 --- a/app/definitions/IProfileViewInterfaces.ts +++ b/app/definitions/IProfileViewInterfaces.ts @@ -57,6 +57,14 @@ export interface IAvatar { service?: any; } +export interface IAvatarSuggestion { + [service: string]: { + url: string; + blob: string; + contentType: string; + }; +} + export interface IProfileViewState { saving: boolean; name: string; @@ -66,13 +74,7 @@ export interface IProfileViewState { currentPassword: string | null; avatarUrl: string | null; avatar: IAvatar; - avatarSuggestions: { - [service: string]: { - url: string; - blob: string; - contentType: string; - }; - }; + avatarSuggestions: IAvatarSuggestion; customFields: { [key: string | number]: string; }; diff --git a/app/lib/rocketchat/services/restApi.ts b/app/lib/rocketchat/services/restApi.ts index 5c834ae5a..268f2140c 100644 --- a/app/lib/rocketchat/services/restApi.ts +++ b/app/lib/rocketchat/services/restApi.ts @@ -3,7 +3,7 @@ import { TEAM_TYPE } from '../../../definitions/ITeam'; import roomTypeToApiType, { RoomTypes } from '../methods/roomTypeToApiType'; import { SubscriptionType, INotificationPreferences, IRoomNotifications } from '../../../definitions'; import { ISpotlight } from '../../../definitions/ISpotlight'; -import { IParams } from '../../../definitions/IProfileViewInterfaces'; +import { IAvatarSuggestion, IParams } from '../../../definitions/IProfileViewInterfaces'; export const createChannel = ({ name, @@ -566,7 +566,7 @@ export const getRoomRoles = (roomId: string, type: SubscriptionType): any => // @ts-ignore sdk.get(`${roomTypeToApiType(type)}.roles`, { roomId }); -export const getAvatarSuggestion = () => +export const getAvatarSuggestion = (): Promise => // RC 0.51.0 sdk.methodCallWrapper('getAvatarSuggestion'); From c4124af73adf5811eba82fe7d6331496eac72428 Mon Sep 17 00:00:00 2001 From: Reinaldo Neto <47038980+reinaldonetof@users.noreply.github.com> Date: Thu, 10 Mar 2022 09:37:54 -0300 Subject: [PATCH 7/9] Chore: Migrate REST API - resetAvatar to Typescript (#3870) --- app/definitions/rest/v1/users.ts | 3 +++ app/lib/rocketchat/services/restApi.ts | 4 +--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/definitions/rest/v1/users.ts b/app/definitions/rest/v1/users.ts index 0a9644965..17b0e1e8e 100644 --- a/app/definitions/rest/v1/users.ts +++ b/app/definitions/rest/v1/users.ts @@ -43,6 +43,9 @@ export type UsersEndpoints = { user: IUser; }; }; + 'users.resetAvatar': { + POST: (params: { userId: string }) => {}; + }; 'users.getPreferences': { GET: (params: { userId: IUser['_id'] }) => { preferences: INotificationPreferences; diff --git a/app/lib/rocketchat/services/restApi.ts b/app/lib/rocketchat/services/restApi.ts index 268f2140c..d5c212d90 100644 --- a/app/lib/rocketchat/services/restApi.ts +++ b/app/lib/rocketchat/services/restApi.ts @@ -570,10 +570,8 @@ export const getAvatarSuggestion = (): Promise => // RC 0.51.0 sdk.methodCallWrapper('getAvatarSuggestion'); -export const resetAvatar = (userId: string): any => +export const resetAvatar = (userId: string) => // RC 0.55.0 - // TODO: missing definitions from server - // @ts-ignore sdk.post('users.resetAvatar', { userId }); export const setAvatarFromService = ({ From 61b8f3e40a33846ae4067f07f252bb2fd60e8e2a Mon Sep 17 00:00:00 2001 From: Reinaldo Neto <47038980+reinaldonetof@users.noreply.github.com> Date: Thu, 10 Mar 2022 10:17:22 -0300 Subject: [PATCH 8/9] Chore: Migrate REST API - setAvatarFromService to Typescript (#3871) --- app/lib/rocketchat/services/restApi.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/rocketchat/services/restApi.ts b/app/lib/rocketchat/services/restApi.ts index d5c212d90..fe4e3a1aa 100644 --- a/app/lib/rocketchat/services/restApi.ts +++ b/app/lib/rocketchat/services/restApi.ts @@ -582,7 +582,7 @@ export const setAvatarFromService = ({ data: any; contentType?: string; service?: string | null; -}) => +}): Promise => // RC 0.51.0 sdk.methodCallWrapper('setAvatarFromService', data, contentType, service); From 4dc43313abc900e2601e1fdf0d5d4fbc4dae33a4 Mon Sep 17 00:00:00 2001 From: Reinaldo Neto <47038980+reinaldonetof@users.noreply.github.com> Date: Thu, 10 Mar 2022 10:44:55 -0300 Subject: [PATCH 9/9] Chore: Migrate REST API - getUsernameSuggestion to Typescript (#3872) --- app/definitions/rest/v1/users.ts | 3 +++ app/lib/rocketchat/services/restApi.ts | 4 +--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/definitions/rest/v1/users.ts b/app/definitions/rest/v1/users.ts index 17b0e1e8e..035e62509 100644 --- a/app/definitions/rest/v1/users.ts +++ b/app/definitions/rest/v1/users.ts @@ -43,6 +43,9 @@ export type UsersEndpoints = { user: IUser; }; }; + 'users.getUsernameSuggestion': { + GET: () => { result: string }; + }; 'users.resetAvatar': { POST: (params: { userId: string }) => {}; }; diff --git a/app/lib/rocketchat/services/restApi.ts b/app/lib/rocketchat/services/restApi.ts index fe4e3a1aa..cc5f9e361 100644 --- a/app/lib/rocketchat/services/restApi.ts +++ b/app/lib/rocketchat/services/restApi.ts @@ -586,10 +586,8 @@ export const setAvatarFromService = ({ // RC 0.51.0 sdk.methodCallWrapper('setAvatarFromService', data, contentType, service); -export const getUsernameSuggestion = (): any => +export const getUsernameSuggestion = () => // RC 0.65.0 - // TODO: missing definitions from server - // @ts-ignore sdk.get('users.getUsernameSuggestion'); export const getFiles = (roomId: string, type: RoomTypes, offset: number): any =>