Merge branch 'develop' into fix.e2efetchkeys-startup

This commit is contained in:
Gerzon Z 2022-03-10 10:19:34 -04:00
commit 9a9edc7f94
45 changed files with 351 additions and 431 deletions

View File

@ -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({

View File

@ -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

View File

@ -53,17 +53,9 @@ 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];
String password = secureKeystore.getSecureKey(alias);
mmkv = MMKV.mmkvWithID("default", MMKV.SINGLE_PROCESS_MODE, password);
}
}
});
}
public String getAvatarUri() {
if (type == null) {

View File

@ -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();
};

View File

@ -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;
@ -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;
};

View File

@ -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;
}

View File

@ -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;
@ -111,6 +111,7 @@ export interface INotificationPreferences {
desktopNotifications: TNotifications;
pushNotifications: TNotifications;
emailNotificationMode?: 'mentions' | 'nothing';
language?: string;
}
export interface IUserPreferences {

View File

@ -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 }) => {};
};
};

View File

@ -1,3 +1,4 @@
import { IParams } from '../../IProfileViewInterfaces';
import type { ITeam } from '../../ITeam';
import type { IUser } from '../../IUser';
import { INotificationPreferences, IUserPreferences, IUserRegistered } from '../../IUser';
@ -26,7 +27,7 @@ export type UsersEndpoints = {
};
};
'users.setPreferences': {
POST: (params: { userId: IUser['_id']; data: Partial<INotificationPreferences> }) => {
POST: (params: { userId?: IUser['_id']; data: Partial<INotificationPreferences> }) => {
user: IUserPreferences;
success: boolean;
};
@ -37,4 +38,21 @@ export type UsersEndpoints = {
'users.setStatus': {
POST: (params: { status: string; message: string }) => {};
};
'users.updateOwnBasicInfo': {
POST: (params: { data: IParams | Pick<IParams, 'username'>; customFields?: { [key: string | number]: string } }) => {
user: IUser;
};
};
'users.getUsernameSuggestion': {
GET: () => { result: string };
};
'users.resetAvatar': {
POST: (params: { userId: string }) => {};
};
'users.getPreferences': {
GET: (params: { userId: IUser['_id'] }) => {
preferences: INotificationPreferences;
success: boolean;
};
};
};

View File

@ -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 (
<SafeAreaProvider initialMetrics={initialWindowMetrics}>
<SafeAreaProvider
initialMetrics={initialWindowMetrics}
style={{ backgroundColor: themes[this.state.theme].backgroundColor }}>
<AppearanceProvider>
<Provider store={store}>
<ThemeContext.Provider

View File

@ -118,8 +118,8 @@ class Encryption {
// Persist keys on UserPreferences
persistKeys = async (server: string, publicKey: string, privateKey: string) => {
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);

View File

@ -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<void> {
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 });

View File

@ -115,8 +115,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
}
@ -155,7 +155,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) {
@ -461,14 +461,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,

View File

@ -1,8 +1,9 @@
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';
import { IAvatarSuggestion, IParams } from '../../../definitions/IProfileViewInterfaces';
export const createChannel = ({
name,
@ -289,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) =>
@ -545,22 +544,16 @@ 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<IParams, 'username'>, 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: any): any =>
export const saveUserPreferences = (data: Partial<INotificationPreferences>) =>
// RC 0.62.0
// TODO: missing definitions from server
// @ts-ignore
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) =>
@ -573,14 +566,12 @@ export const getRoomRoles = (roomId: string, type: SubscriptionType): any =>
// @ts-ignore
sdk.get(`${roomTypeToApiType(type)}.roles`, { roomId });
export const getAvatarSuggestion = () =>
export const getAvatarSuggestion = (): Promise<IAvatarSuggestion> =>
// 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 = ({
@ -591,14 +582,12 @@ export const setAvatarFromService = ({
data: any;
contentType?: string;
service?: string | null;
}) =>
}): Promise<void> =>
// 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 =>

View File

@ -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);
}
}

View File

@ -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

View File

@ -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);

View File

@ -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));
}

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -69,7 +69,7 @@ const openChangePasscodeModal = ({ force }: { force: boolean }) =>
export const changePasscode = async ({ force = false }: { force: boolean }): Promise<void> => {
const passcode = await openChangePasscodeModal({ force });
await UserPreferences.setStringAsync(PASSCODE_KEY, sha256(passcode));
UserPreferences.setString(PASSCODE_KEY, sha256(passcode));
};
export const biometryAuth = (force?: boolean): Promise<LocalAuthentication.LocalAuthenticationResult> =>
@ -86,12 +86,12 @@ export const biometryAuth = (force?: boolean): Promise<LocalAuthentication.Local
const checkBiometry = async () => {
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<void> => {
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) {

View File

@ -37,7 +37,7 @@ const appSchemeURL = (url: string, browser: string): string => {
const openLink = async (url: string, theme = 'light'): Promise<void> => {
try {
const browser = await UserPreferences.getStringAsync(DEFAULT_BROWSER_KEY);
const browser = UserPreferences.getString(DEFAULT_BROWSER_KEY);
if (browser === 'inApp') {
await WebBrowser.openBrowserAsync(url, {

View File

@ -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);
}
}
},

View File

@ -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') {

View File

@ -73,9 +73,9 @@ class DefaultBrowserView extends React.Component<IDefaultBrowserViewProps, IDefa
}
}
async componentDidMount() {
componentDidMount() {
this.mounted = true;
const browser = await UserPreferences.getStringAsync(DEFAULT_BROWSER_KEY);
const browser = UserPreferences.getString(DEFAULT_BROWSER_KEY);
this.setState({ browser });
}
@ -104,11 +104,11 @@ class DefaultBrowserView extends React.Component<IDefaultBrowserViewProps, IDefa
return browser === value;
};
changeDefaultBrowser = async (newBrowser: TValue) => {
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);

View File

@ -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<SettingsStackParamList, 'DisplayPrefsView'>;
@ -45,53 +45,49 @@ const DisplayPrefsView = (props: IDisplayPrefsView): JSX.Element => {
}
}, []);
const setSortPreference = async (param: Partial<IPreferences>) => {
try {
const setSortPreference = (param: Partial<IPreferences>) => {
dispatch(setPreference(param));
await RocketChat.saveSortPreference(param);
} catch (e) {
log(e);
}
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) => (

View File

@ -81,11 +81,11 @@ class E2ESaveYourPasswordView extends React.Component<IE2ESaveYourPasswordViewPr
this.mounted = true;
}
init = async () => {
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<IE2ESaveYourPasswordViewPr
}
};
onSaved = async () => {
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();

View File

@ -181,7 +181,7 @@ class NewServerView extends React.Component<INewServerViewProps, INewServerViewS
this.setState({ text: serverHistory.url }, () => 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<INewServerViewProps, INewServerViewS
const server = this.completeUrl(text);
// Save info - SSL Pinning
await UserPreferences.setStringAsync(`${RocketChat.CERTIFICATE_KEY}-${server}`, certificate);
if (certificate) {
UserPreferences.setString(`${RocketChat.CERTIFICATE_KEY}-${server}`, certificate);
}
// Save info - HTTP Basic Authentication
await this.basicAuth(server, text);
this.basicAuth(server, text);
if (fromServerHistory) {
dispatch(serverRequest(server, username, true));
@ -213,12 +215,12 @@ class NewServerView extends React.Component<INewServerViewProps, INewServerViewS
dispatch(serverRequest('https://open.rocket.chat'));
};
basicAuth = async (server: string, text: string) => {
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 {

View File

@ -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<INotificationPreferenc
}
}
saveNotificationSettings = async (key: string, value: string | boolean, params: any) => {
saveNotificationSettings = async (key: string, value: string | boolean, params: IRoomNotifications) => {
// @ts-ignore
logEvent(events[`NP_${key.toUpperCase()}`]);
const { room } = this.state;

View File

@ -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<IProfileViewProps, IProfileViewState> {
private name: any;

View File

@ -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';

View File

@ -135,7 +135,7 @@ class ServerDropdown extends Component<IServerDropdownProps, IServerDropdownStat
this.close();
if (currentServer !== server) {
logEvent(events.RL_CHANGE_SERVER);
const userId = await UserPreferences.getStringAsync(`${RocketChat.TOKEN_KEY}-${server}`);
const userId = UserPreferences.getString(`${RocketChat.TOKEN_KEY}-${server}`);
if (isMasterDetail) {
goRoom({ item: {}, isMasterDetail });
}

View File

@ -339,7 +339,7 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS
this.getSubscriptions();
}
// Update current item in case of another action triggers an update on rooms reducer
if (isMasterDetail && item?.rid !== rooms[0].rid && !dequal(rooms, prevProps.rooms)) {
if (isMasterDetail && rooms[0] && item?.rid !== rooms[0].rid && !dequal(rooms, prevProps.rooms)) {
// eslint-disable-next-line react/no-did-update-set-state
// @ts-ignore
this.setState({ item: { rid: rooms[0] } });

View File

@ -92,7 +92,7 @@ class ScreenLockConfigView extends React.Component<IScreenLockConfigViewProps, I
const { server } = this.props;
const serversDB = database.servers;
const serversCollection = serversDB.get('servers');
const hasBiometry = (await userPreferences.getBoolAsync(BIOMETRY_ENABLED_KEY)) ?? DEFAULT_BIOMETRY;
const hasBiometry = userPreferences.getBool(BIOMETRY_ENABLED_KEY) ?? DEFAULT_BIOMETRY;
try {
this.serverRecord = await serversCollection.find(server);
this.setState({
@ -147,9 +147,9 @@ class ScreenLockConfigView extends React.Component<IScreenLockConfigViewProps, I
logEvent(events.SLC_TOGGLE_BIOMETRY);
this.setState(
({ biometry }) => ({ biometry: !biometry }),
async () => {
() => {
const { biometry } = this.state;
await userPreferences.setBoolAsync(BIOMETRY_ENABLED_KEY, biometry);
userPreferences.setBool(BIOMETRY_ENABLED_KEY, biometry);
}
);
};

View File

@ -97,11 +97,11 @@ class ThemeView extends React.Component<IThemeViewProps> {
this.setTheme(changes);
};
setTheme = async (theme: Partial<IThemePreference>) => {
setTheme = (theme: Partial<IThemePreference>) => {
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 = () => {

View File

@ -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,9 +59,11 @@ class UserNotificationPreferencesView extends React.Component<
const { user } = this.props;
const { id } = user;
const result = await RocketChat.getUserPreferences(id);
if (result.success) {
const { preferences } = result;
this.setState({ preferences, loading: true });
}
}
findDefaultOption = (key: TKey) => {
const { preferences } = this.state;

View File

@ -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

View File

@ -25,11 +25,9 @@ class Storage {
// get mmkv instance password from keychain
var key: Data?
secureStorage.getSecureKey(instanceID.toHex()) { (response) -> () in
if let password = response?[1] as? String {
if let password: String = secureStorage.getSecureKey(instanceID.toHex()) {
key = password.data(using: .utf8)
}
}
guard let cryptKey = key else {
return

View File

@ -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",

View File

@ -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;
}

View File

@ -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

View File

@ -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,7 +184,7 @@ 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) {
@ -196,17 +196,15 @@ index 02b4238..04bad05 100644
+ 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]) {
+ NSString *key = [secureStorage getSecureKey:[self stringToHex:@"com.MMKV.default"]];
+
+ if (key == NULL) {
+ return;
+ }
+ NSString *key = [response objectAtIndex:1];
+
+ 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];
+

View File

@ -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];
@ -116,17 +117,15 @@ index cdbe6b1..bee6228 100644
+ 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]) {
+ NSString *key = [secureStorage getSecureKey:[self stringToHex:@"com.MMKV.default"]];
+
+ if (key == NULL) {
+ return;
}
+ NSString *key = [response objectAtIndex:1];
+ }
+
+ 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];
+

View File

@ -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"