[NEW] Encrypt user credentials and preferences (#2247)

* install react-native-mmkv-storage

* wip ios migration

* change all js rn-user-defaults -> react-native-mmkv-storage

* remove all rn-user-defaults native references (iOS)

* android migration from rn-user-defaults to react-native-mmkv-storage

* ios app group accessible mmkv

* handle get errors

* remove access of credentials from legacy native apps

* remove data of user defaults

* remove no longer necessary import

* js mmkv encryption

* run migration only once

* reply from notification android

* fix app group key access at native level ios

* encrypt user credentials using a specific key

* ios encrypt with random key

* use a random key at the first encryption

* encrypt migrated data on js land

* remove unused function

* reply notifications ios should be working

* use fix instanceID

* android ejson retrieve encrypted data

* remove encryption migrated data for a while

* encryption working between app and share extension

* fix patch react-native-notifications

* ssl pinning working using mmkv encrypted data

* improve react-native-notifications

* run encrypt migration data only once

* fix build

* fix patches magic string

* fix mmkv id

* mmkv -> userPreferences

* fix instance id on android migration

* cast our oldest sharedPreferences string into an object

* revert log remove

* create currentServer Rocket.Chat key

* wrap mmkv api class

* change the get logic

* move userPreferences to lib

* move encrypt migrated data to userPreferences class

* check if the new object is new before insert

* invalidate ci yarn cache

* fix sort migration from android shared preferences

* fix splashscreen forever

* invalidate yarn cache

* invalidate yarn cache

* fix patch

* Minor change

* fix android notifications looking for wrong mmkv instance

* Fix some issues on iOS mmkv native access

* Remove unnecessary code

* Fix notification reply and ssl pinning

* WIP NotificationService use MMKV credentials

* Add KeychainGroup

* Notification idOnly get credentials from mmkv

* Some fixes

* Invalidate yarn cache

* Pods

* Use MMKVAppExtension on NotificationService

Co-authored-by: Diego Mello <diegolmello@gmail.com>
This commit is contained in:
Djorkaeff Alexandre 2020-08-19 14:14:22 -03:00 committed by GitHub
parent 6555687891
commit e2f17a5a23
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
214 changed files with 37442 additions and 17428 deletions

View File

@ -240,6 +240,7 @@ dependencies {
implementation "com.google.code.gson:gson:2.8.5" implementation "com.google.code.gson:gson:2.8.5"
implementation "com.github.bumptech.glide:glide:4.9.0" implementation "com.github.bumptech.glide:glide:4.9.0"
annotationProcessor "com.github.bumptech.glide:compiler:4.9.0" annotationProcessor "com.github.bumptech.glide:compiler:4.9.0"
implementation "com.tencent:mmkv-static:1.2.1"
} }
// Run this once to be able to run the application with BUCK // Run this once to be able to run the application with BUCK

View File

@ -1,8 +1,28 @@
package chat.rocket.reactnative; package chat.rocket.reactnative;
import android.content.SharedPreferences; import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.Callback;
import chat.rocket.userdefaults.RNUserDefaultsModule; import com.ammarahmed.mmkv.SecureKeystore;
import com.tencent.mmkv.MMKV;
import java.math.BigInteger;
class RNCallback implements Callback {
public void invoke(Object... args) {
}
}
class Utils {
static public String toHex(String arg) {
try {
return String.format("%x", new BigInteger(1, arg.getBytes("UTF-8")));
} catch (Exception e) {
return "";
}
}
}
public class Ejson { public class Ejson {
String host; String host;
@ -12,8 +32,32 @@ public class Ejson {
String messageId; String messageId;
String notificationType; String notificationType;
private MMKV mmkv;
private String TOKEN_KEY = "reactnativemeteor_usertoken-"; private String TOKEN_KEY = "reactnativemeteor_usertoken-";
private SharedPreferences sharedPreferences = RNUserDefaultsModule.getPreferences(CustomPushNotification.reactApplicationContext);
public Ejson() {
ReactApplicationContext reactApplicationContext = CustomPushNotification.reactApplicationContext;
// Start MMKV container
MMKV.initialize(reactApplicationContext);
SecureKeystore secureKeystore = new SecureKeystore(reactApplicationContext);
// https://github.com/ammarahm-ed/react-native-mmkv-storage/blob/master/src/loader.js#L31
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);
}
}
});
}
public String getAvatarUri() { public String getAvatarUri() {
if (type == null) { if (type == null) {
@ -23,11 +67,17 @@ public class Ejson {
} }
public String token() { public String token() {
return sharedPreferences.getString(TOKEN_KEY.concat(userId()), ""); if (mmkv != null) {
return mmkv.decodeString(TOKEN_KEY.concat(userId()));
}
return "";
} }
public String userId() { public String userId() {
return sharedPreferences.getString(TOKEN_KEY.concat(serverURL()), ""); if (mmkv != null) {
return mmkv.decodeString(TOKEN_KEY.concat(serverURL()));
}
return "";
} }
public String serverURL() { public String serverURL() {

View File

@ -15,8 +15,6 @@ import java.io.IOException;
import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactApplicationContext;
import chat.rocket.userdefaults.RNUserDefaultsModule;
class JsonResponse { class JsonResponse {
Data data; Data data;

View File

@ -1,13 +1,33 @@
package chat.rocket.reactnative; package chat.rocket.reactnative;
import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.ReactRootView;
import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
import android.os.Bundle; import android.os.Bundle;
import com.facebook.react.ReactFragmentActivity; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.content.SharedPreferences;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.ReactRootView;
import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.ReactFragmentActivity;
import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
import com.zoontek.rnbootsplash.RNBootSplash; import com.zoontek.rnbootsplash.RNBootSplash;
import com.tencent.mmkv.MMKV;
import com.google.gson.Gson;
class ThemePreferences {
String currentTheme;
String darkLevel;
}
class SortPreferences {
String sortBy;
Boolean groupByType;
Boolean showFavorites;
Boolean showUnread;
}
public class MainActivity extends ReactFragmentActivity { public class MainActivity extends ReactFragmentActivity {
@ -16,6 +36,55 @@ public class MainActivity extends ReactFragmentActivity {
// https://github.com/software-mansion/react-native-screens/issues/17#issuecomment-424704067 // https://github.com/software-mansion/react-native-screens/issues/17#issuecomment-424704067
super.onCreate(null); super.onCreate(null);
RNBootSplash.init(R.drawable.launch_screen, MainActivity.this); RNBootSplash.init(R.drawable.launch_screen, MainActivity.this);
MMKV.initialize(MainActivity.this);
// Start the MMKV container
MMKV defaultMMKV = MMKV.defaultMMKV();
boolean alreadyMigrated = defaultMMKV.decodeBool("alreadyMigrated");
if (!alreadyMigrated) {
// MMKV Instance that will be used by JS
MMKV mmkv = MMKV.mmkvWithID("default");
// SharedPreferences -> MMKV (Migration)
SharedPreferences sharedPreferences = getSharedPreferences("react-native", Context.MODE_PRIVATE);
mmkv.importFromSharedPreferences(sharedPreferences);
// SharedPreferences only save strings, so we saved this value as a String and now we'll need to cast into a MMKV object
// Theme preferences object
String THEME_PREFERENCES_KEY = "RC_THEME_PREFERENCES_KEY";
String themeJson = sharedPreferences.getString(THEME_PREFERENCES_KEY, "");
if (!themeJson.isEmpty()) {
ThemePreferences themePreferences = new Gson().fromJson(themeJson, ThemePreferences.class);
WritableMap themeMap = new Arguments().createMap();
themeMap.putString("currentTheme", themePreferences.currentTheme);
themeMap.putString("darkLevel", themePreferences.darkLevel);
Bundle bundle = Arguments.toBundle(themeMap);
mmkv.encode(THEME_PREFERENCES_KEY, bundle);
}
// Sort preferences object
String SORT_PREFS_KEY = "RC_SORT_PREFS_KEY";
String sortJson = sharedPreferences.getString(SORT_PREFS_KEY, "");
if (!sortJson.isEmpty()) {
SortPreferences sortPreferences = new Gson().fromJson(sortJson, SortPreferences.class);
WritableMap sortMap = new Arguments().createMap();
sortMap.putString("sortBy", sortPreferences.sortBy);
sortMap.putBoolean("groupByType", sortPreferences.groupByType);
sortMap.putBoolean("showFavorites", sortPreferences.showFavorites);
sortMap.putBoolean("showUnread", sortPreferences.showUnread);
Bundle bundle = Arguments.toBundle(sortMap);
mmkv.encode(SORT_PREFS_KEY, bundle);
}
// Remove all our keys of SharedPreferences
sharedPreferences.edit().clear().commit();
// Mark migration complete
defaultMMKV.encode("alreadyMigrated", true);
}
} }
/** /**

View File

@ -25,7 +25,6 @@ import okhttp3.Request;
import okhttp3.RequestBody; import okhttp3.RequestBody;
import okhttp3.Response; import okhttp3.Response;
import chat.rocket.userdefaults.RNUserDefaultsModule;
import com.wix.reactnativenotifications.core.NotificationIntentAdapter; import com.wix.reactnativenotifications.core.NotificationIntentAdapter;
public class ReplyBroadcast extends BroadcastReceiver { public class ReplyBroadcast extends BroadcastReceiver {

View File

@ -1,6 +0,0 @@
export const SERVERS = 'kServers';
export const TOKEN = 'kAuthToken';
export const USER_ID = 'kUserId';
export const SERVER_URL = 'kAuthServerURL';
export const SERVER_NAME = 'kServerName';
export const SERVER_ICON = 'kServerIconURL';

View File

@ -1,6 +1,5 @@
import React, { useEffect, useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import { useAsyncStorage } from '@react-native-community/async-storage'; import { useAsyncStorage } from '@react-native-community/async-storage';
import RNUserDefaults from 'rn-user-defaults';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { gestureHandlerRootHOC } from 'react-native-gesture-handler'; import { gestureHandlerRootHOC } from 'react-native-gesture-handler';
import * as Haptics from 'expo-haptics'; import * as Haptics from 'expo-haptics';
@ -14,6 +13,7 @@ import {
} from '../../constants/localAuthentication'; } from '../../constants/localAuthentication';
import { resetAttempts, biometryAuth } from '../../utils/localAuthentication'; import { resetAttempts, biometryAuth } from '../../utils/localAuthentication';
import { getLockedUntil, getDiff } from './utils'; import { getLockedUntil, getDiff } from './utils';
import UserPreferences from '../../lib/userPreferences';
import I18n from '../../i18n'; import I18n from '../../i18n';
const PasscodeEnter = ({ theme, hasBiometry, finishProcess }) => { const PasscodeEnter = ({ theme, hasBiometry, finishProcess }) => {
@ -26,7 +26,7 @@ const PasscodeEnter = ({ theme, hasBiometry, finishProcess }) => {
const { setItem: setLockedUntil } = useAsyncStorage(LOCKED_OUT_TIMER_KEY); const { setItem: setLockedUntil } = useAsyncStorage(LOCKED_OUT_TIMER_KEY);
const fetchPasscode = async() => { const fetchPasscode = async() => {
const p = await RNUserDefaults.get(PASSCODE_KEY); const p = await UserPreferences.getStringAsync(PASSCODE_KEY);
setPasscode(p); setPasscode(p);
}; };

View File

@ -2,7 +2,6 @@ import React from 'react';
import { Linking, Dimensions } from 'react-native'; import { Linking, Dimensions } from 'react-native';
import { AppearanceProvider } from 'react-native-appearance'; import { AppearanceProvider } from 'react-native-appearance';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import RNUserDefaults from 'rn-user-defaults';
import { KeyCommandsEmitter } from 'react-native-keycommands'; import { KeyCommandsEmitter } from 'react-native-keycommands';
import RNScreens from 'react-native-screens'; import RNScreens from 'react-native-screens';
import { SafeAreaProvider, initialWindowMetrics } from 'react-native-safe-area-context'; import { SafeAreaProvider, initialWindowMetrics } from 'react-native-safe-area-context';
@ -13,6 +12,7 @@ import {
subscribeTheme, subscribeTheme,
unsubscribeTheme unsubscribeTheme
} from './utils/theme'; } from './utils/theme';
import UserPreferences from './lib/userPreferences';
import EventEmitter from './utils/events'; import EventEmitter from './utils/events';
import { appInit, appInitLocalSettings, setMasterDetail as setMasterDetailAction } from './actions/app'; import { appInit, appInitLocalSettings, setMasterDetail as setMasterDetailAction } from './actions/app';
import { deepLinkingOpen } from './actions/deepLinking'; import { deepLinkingOpen } from './actions/deepLinking';
@ -37,7 +37,6 @@ import InAppNotification from './containers/InAppNotification';
import { ActionSheetProvider } from './containers/ActionSheet'; import { ActionSheetProvider } from './containers/ActionSheet';
import debounce from './utils/debounce'; import debounce from './utils/debounce';
RNScreens.enableScreens(); RNScreens.enableScreens();
const parseDeepLinking = (url) => { const parseDeepLinking = (url) => {
@ -106,7 +105,7 @@ export default class Root extends React.Component {
} }
init = async() => { init = async() => {
RNUserDefaults.objectForKey(THEME_PREFERENCES_KEY).then(this.setTheme); UserPreferences.getMapAsync(THEME_PREFERENCES_KEY).then(this.setTheme);
const [notification, deepLinking] = await Promise.all([initializePushNotifications(), Linking.getInitialURL()]); const [notification, deepLinking] = await Promise.all([initializePushNotifications(), Linking.getInitialURL()]);
const parsedDeepLinkingURL = parseDeepLinking(deepLinking); const parsedDeepLinkingURL = parseDeepLinking(deepLinking);
store.dispatch(appInitLocalSettings()); store.dispatch(appInitLocalSettings());

View File

@ -1,30 +1,26 @@
import RNUserDefaults from 'rn-user-defaults';
import * as FileSystem from 'expo-file-system'; import * as FileSystem from 'expo-file-system';
import { Rocketchat as RocketchatClient } from '@rocket.chat/sdk'; import { Rocketchat as RocketchatClient } from '@rocket.chat/sdk';
import { SERVERS, SERVER_URL } from '../../constants/userDefaults';
import { getDeviceToken } from '../../notifications/push'; import { getDeviceToken } from '../../notifications/push';
import { extractHostname } from '../../utils/server'; import { extractHostname } from '../../utils/server';
import { BASIC_AUTH_KEY } from '../../utils/fetch'; import { BASIC_AUTH_KEY } from '../../utils/fetch';
import database, { getDatabase } from '../database'; import database, { getDatabase } from '../database';
import RocketChat from '../rocketchat'; import RocketChat from '../rocketchat';
import { useSsl } from '../../utils/url'; import { useSsl } from '../../utils/url';
import UserPreferences from '../userPreferences';
async function removeServerKeys({ server, userId }) { async function removeServerKeys({ server, userId }) {
await RNUserDefaults.clear(`${ RocketChat.TOKEN_KEY }-${ server }`); await UserPreferences.removeItem(`${ RocketChat.TOKEN_KEY }-${ server }`);
await RNUserDefaults.clear(`${ RocketChat.TOKEN_KEY }-${ userId }`); await UserPreferences.removeItem(`${ RocketChat.TOKEN_KEY }-${ userId }`);
await RNUserDefaults.clear(`${ BASIC_AUTH_KEY }-${ server }`); await UserPreferences.removeItem(`${ BASIC_AUTH_KEY }-${ server }`);
} }
async function removeSharedCredentials({ server }) { async function removeSharedCredentials({ server }) {
// clear certificate for server - SSL Pinning
try { try {
const servers = await RNUserDefaults.objectForKey(SERVERS); const certificate = await UserPreferences.getMapAsync(extractHostname(server));
await RNUserDefaults.setObjectForKey(SERVERS, servers && servers.filter(srv => srv[SERVER_URL] !== server));
// clear certificate for server - SSL Pinning
const certificate = await RNUserDefaults.objectForKey(extractHostname(server));
if (certificate && certificate.path) { if (certificate && certificate.path) {
await RNUserDefaults.clear(extractHostname(server)); await UserPreferences.removeItem(extractHostname(server));
await FileSystem.deleteAsync(certificate.path); await FileSystem.deleteAsync(certificate.path);
} }
} catch (e) { } catch (e) {
@ -36,7 +32,7 @@ async function removeServerData({ server }) {
try { try {
const batch = []; const batch = [];
const serversDB = database.servers; const serversDB = database.servers;
const userId = await RNUserDefaults.get(`${ RocketChat.TOKEN_KEY }-${ server }`); const userId = await UserPreferences.getStringAsync(`${ RocketChat.TOKEN_KEY }-${ server }`);
const usersCollection = serversDB.collections.get('users'); const usersCollection = serversDB.collections.get('users');
if (userId) { if (userId) {
@ -56,8 +52,8 @@ async function removeServerData({ server }) {
} }
async function removeCurrentServer() { async function removeCurrentServer() {
await RNUserDefaults.clear('currentServer'); await UserPreferences.removeItem(RocketChat.CURRENT_SERVER);
await RNUserDefaults.clear(RocketChat.TOKEN_KEY); await UserPreferences.removeItem(RocketChat.TOKEN_KEY);
} }
async function removeServerDatabase({ server }) { async function removeServerDatabase({ server }) {
@ -71,9 +67,9 @@ async function removeServerDatabase({ server }) {
export async function removeServer({ server }) { export async function removeServer({ server }) {
try { try {
const userId = await RNUserDefaults.get(`${ RocketChat.TOKEN_KEY }-${ server }`); const userId = await UserPreferences.getStringAsync(`${ RocketChat.TOKEN_KEY }-${ server }`);
if (userId) { if (userId) {
const resume = await RNUserDefaults.get(`${ RocketChat.TOKEN_KEY }-${ userId }`); const resume = await UserPreferences.getStringAsync(`${ RocketChat.TOKEN_KEY }-${ userId }`);
const sdk = new RocketchatClient({ host: server, protocol: 'ddp', useSsl: useSsl(server) }); const sdk = new RocketchatClient({ host: server, protocol: 'ddp', useSsl: useSsl(server) });
await sdk.login({ resume }); await sdk.login({ resume });
@ -89,7 +85,7 @@ export async function removeServer({ server }) {
await removeServerData({ server }); await removeServerData({ server });
await removeServerDatabase({ server }); await removeServerDatabase({ server });
} catch (e) { } catch (e) {
console.log('removePush', e); console.log('removeServer', e);
} }
} }

View File

@ -1,7 +1,6 @@
import { InteractionManager } from 'react-native'; import { InteractionManager } from 'react-native';
import semver from 'semver'; import semver from 'semver';
import { Rocketchat as RocketchatClient } from '@rocket.chat/sdk'; import { Rocketchat as RocketchatClient } from '@rocket.chat/sdk';
import RNUserDefaults from 'rn-user-defaults';
import { Q } from '@nozbe/watermelondb'; import { Q } from '@nozbe/watermelondb';
import AsyncStorage from '@react-native-community/async-storage'; import AsyncStorage from '@react-native-community/async-storage';
@ -51,8 +50,10 @@ import I18n from '../i18n';
import { twoFactor } from '../utils/twoFactor'; import { twoFactor } from '../utils/twoFactor';
import { selectServerFailure } from '../actions/server'; import { selectServerFailure } from '../actions/server';
import { useSsl } from '../utils/url'; import { useSsl } from '../utils/url';
import UserPreferences from './userPreferences';
const TOKEN_KEY = 'reactnativemeteor_usertoken'; const TOKEN_KEY = 'reactnativemeteor_usertoken';
const CURRENT_SERVER = 'currentServer';
const SORT_PREFS_KEY = 'RC_SORT_PREFS_KEY'; const SORT_PREFS_KEY = 'RC_SORT_PREFS_KEY';
export const THEME_PREFERENCES_KEY = 'RC_THEME_PREFERENCES_KEY'; export const THEME_PREFERENCES_KEY = 'RC_THEME_PREFERENCES_KEY';
export const CRASH_REPORT_KEY = 'RC_CRASH_REPORT_KEY'; export const CRASH_REPORT_KEY = 'RC_CRASH_REPORT_KEY';
@ -63,6 +64,7 @@ const STATUSES = ['offline', 'online', 'away', 'busy'];
const RocketChat = { const RocketChat = {
TOKEN_KEY, TOKEN_KEY,
CURRENT_SERVER,
callJitsi, callJitsi,
async subscribeRooms() { async subscribeRooms() {
if (!this.roomsSub) { if (!this.roomsSub) {
@ -89,13 +91,6 @@ const RocketChat = {
// RC 0.51.0 // RC 0.51.0
return this.methodCallWrapper(type ? 'createPrivateGroup' : 'createChannel', name, users, readOnly, {}, { broadcast }); return this.methodCallWrapper(type ? 'createPrivateGroup' : 'createChannel', name, users, readOnly, {}, { broadcast });
}, },
async getUserToken() {
try {
return await RNUserDefaults.get(TOKEN_KEY);
} catch (error) {
console.warn(`RNUserDefaults error: ${ error.message }`);
}
},
async getWebsocketInfo({ server }) { async getWebsocketInfo({ server }) {
const sdk = new RocketchatClient({ host: server, protocol: 'ddp', useSsl: useSsl(server) }); const sdk = new RocketchatClient({ host: server, protocol: 'ddp', useSsl: useSsl(server) });
@ -307,7 +302,7 @@ const RocketChat = {
// set User info // set User info
try { try {
const userId = await RNUserDefaults.get(`${ RocketChat.TOKEN_KEY }-${ server }`); const userId = await UserPreferences.getStringAsync(`${ RocketChat.TOKEN_KEY }-${ server }`);
const userCollections = serversDB.collections.get('users'); const userCollections = serversDB.collections.get('users');
let user = null; let user = null;
if (userId) { if (userId) {
@ -1062,17 +1057,13 @@ const RocketChat = {
return JSON.parse(allowCrashReport); return JSON.parse(allowCrashReport);
}, },
async getSortPreferences() { async getSortPreferences() {
const prefs = await RNUserDefaults.objectForKey(SORT_PREFS_KEY); const prefs = await UserPreferences.getMapAsync(SORT_PREFS_KEY);
return prefs; return prefs;
}, },
async saveSortPreference(param) { async saveSortPreference(param) {
try { let prefs = await RocketChat.getSortPreferences();
let prefs = await RocketChat.getSortPreferences(); prefs = { ...prefs, ...param };
prefs = { ...prefs, ...param }; return UserPreferences.setMapAsync(SORT_PREFS_KEY, prefs);
return await RNUserDefaults.setObjectForKey(SORT_PREFS_KEY, prefs);
} catch (error) {
console.warn(error);
}
}, },
async getLoginServices(server) { async getLoginServices(server) {
try { try {

View File

@ -0,0 +1,80 @@
import MMKVStorage from 'react-native-mmkv-storage';
import log from '../utils/log';
const MMKV = new MMKVStorage.Loader()
// MODES.MULTI_PROCESS = ACCESSIBLE BY APP GROUP (iOS)
.setProcessingMode(MMKVStorage.MODES.MULTI_PROCESS)
.withEncryption()
.initialize();
class UserPreferences {
constructor() {
this.mmkv = MMKV;
this.encryptMigratedData();
}
// It should run only once
async encryptMigratedData() {
try {
const encryptMigration = await this.getBoolAsync('encryptMigration');
if (!encryptMigration) {
// Encrypt the migrated data
await this.mmkv.encryption.encrypt();
// Mark as completed
await this.setBoolAsync('encryptMigration', true);
}
} catch (e) {
log(e);
}
}
async getStringAsync(key) {
try {
const value = await this.mmkv.getStringAsync(key);
return value;
} catch {
return null;
}
}
setStringAsync(key, value) {
return this.mmkv.setStringAsync(key, value);
}
async getBoolAsync(key) {
try {
const value = await this.mmkv.getBoolAsync(key);
return value;
} catch {
return null;
}
}
setBoolAsync(key, value) {
return this.mmkv.setBoolAsync(key, value);
}
async getMapAsync(key) {
try {
const value = await this.mmkv.getMapAsync(key);
return value;
} catch {
return null;
}
}
setMapAsync(key, value) {
return this.mmkv.setMapAsync(key, value);
}
removeItem(key) {
return this.mmkv.removeItem(key);
}
}
const userPreferences = new UserPreferences();
export default userPreferences;

View File

@ -1,8 +1,8 @@
import { import {
takeLatest, take, select, put, all, delay takeLatest, take, select, put, all, delay
} from 'redux-saga/effects'; } from 'redux-saga/effects';
import RNUserDefaults from 'rn-user-defaults';
import UserPreferences from '../lib/userPreferences';
import Navigation from '../lib/Navigation'; import Navigation from '../lib/Navigation';
import * as types from '../actions/actionsTypes'; import * as types from '../actions/actionsTypes';
import { selectServerRequest, serverInitAdd } from '../actions/server'; import { selectServerRequest, serverInitAdd } from '../actions/server';
@ -105,8 +105,8 @@ const handleOpen = function* handleOpen({ params }) {
} }
const [server, user] = yield all([ const [server, user] = yield all([
RNUserDefaults.get('currentServer'), UserPreferences.getStringAsync(RocketChat.CURRENT_SERVER),
RNUserDefaults.get(`${ RocketChat.TOKEN_KEY }-${ host }`) UserPreferences.getStringAsync(`${ RocketChat.TOKEN_KEY }-${ host }`)
]); ]);
// TODO: needs better test // TODO: needs better test

View File

@ -1,21 +1,14 @@
import { put, takeLatest, all } from 'redux-saga/effects'; import { put, takeLatest, all } from 'redux-saga/effects';
import RNUserDefaults from 'rn-user-defaults';
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
import RNBootSplash from 'react-native-bootsplash'; import RNBootSplash from 'react-native-bootsplash';
import AsyncStorage from '@react-native-community/async-storage';
import UserPreferences from '../lib/userPreferences';
import { selectServerRequest } from '../actions/server'; import { selectServerRequest } from '../actions/server';
import { setAllPreferences } from '../actions/sortPreferences'; import { setAllPreferences } from '../actions/sortPreferences';
import { toggleCrashReport } from '../actions/crashReport'; import { toggleCrashReport } from '../actions/crashReport';
import { APP } from '../actions/actionsTypes'; import { APP } from '../actions/actionsTypes';
import RocketChat from '../lib/rocketchat'; import RocketChat from '../lib/rocketchat';
import log from '../utils/log'; import log from '../utils/log';
import {
SERVERS, SERVER_ICON, SERVER_NAME, SERVER_URL, TOKEN, USER_ID
} from '../constants/userDefaults';
import { isIOS } from '../utils/deviceInfo';
import database from '../lib/database'; import database from '../lib/database';
import protectedFunction from '../lib/methods/helpers/protectedFunction';
import { localAuthenticate } from '../utils/localAuthentication'; import { localAuthenticate } from '../utils/localAuthentication';
import { appStart, ROOT_OUTSIDE, appReady } from '../actions/app'; import { appStart, ROOT_OUTSIDE, appReady } from '../actions/app';
@ -29,71 +22,15 @@ export const initLocalSettings = function* initLocalSettings() {
const restore = function* restore() { const restore = function* restore() {
try { try {
let hasMigration; const { token, server } = yield all({
if (isIOS) { token: UserPreferences.getStringAsync(RocketChat.TOKEN_KEY),
hasMigration = yield AsyncStorage.getItem('hasMigration'); server: UserPreferences.getStringAsync(RocketChat.CURRENT_SERVER)
}
let { token, server } = yield all({
token: RNUserDefaults.get(RocketChat.TOKEN_KEY),
server: RNUserDefaults.get('currentServer')
}); });
if (!hasMigration && isIOS) {
let servers = yield RNUserDefaults.objectForKey(SERVERS);
// if not have current
if (servers && servers.length !== 0 && (!token || !server)) {
server = servers[0][SERVER_URL];
token = servers[0][TOKEN];
}
// get native credentials
if (servers) {
try {
// parse servers
servers = yield Promise.all(servers.map(async(s) => {
await RNUserDefaults.set(`${ RocketChat.TOKEN_KEY }-${ s[SERVER_URL] }`, s[USER_ID]);
return ({ id: s[SERVER_URL], name: s[SERVER_NAME], iconURL: s[SERVER_ICON] });
}));
const serversDB = database.servers;
yield serversDB.action(async() => {
const serversCollection = serversDB.collections.get('servers');
const allServerRecords = await serversCollection.query().fetch();
// filter servers
let serversToCreate = servers.filter(i1 => !allServerRecords.find(i2 => i1.id === i2.id));
// Create
serversToCreate = serversToCreate.map(record => serversCollection.prepareCreate(protectedFunction((s) => {
s._raw = sanitizedRaw({ id: record.id }, serversCollection.schema);
Object.assign(s, record);
})));
const allRecords = serversToCreate;
try {
await serversDB.batch(...allRecords);
} catch (e) {
log(e);
}
return allRecords.length;
});
} catch (e) {
log(e);
}
}
try {
yield AsyncStorage.setItem('hasMigration', '1');
} catch (e) {
log(e);
}
}
if (!token || !server) { if (!token || !server) {
yield all([ yield all([
RNUserDefaults.clear(RocketChat.TOKEN_KEY), UserPreferences.removeItem(RocketChat.TOKEN_KEY),
RNUserDefaults.clear('currentServer') UserPreferences.removeItem(RocketChat.CURRENT_SERVER)
]); ]);
yield put(appStart({ root: ROOT_OUTSIDE })); yield put(appStart({ root: ROOT_OUTSIDE }));
} else { } else {

View File

@ -1,7 +1,6 @@
import { import {
put, call, takeLatest, select, take, fork, cancel, race, delay put, call, takeLatest, select, take, fork, cancel, race, delay
} from 'redux-saga/effects'; } from 'redux-saga/effects';
import RNUserDefaults from 'rn-user-defaults';
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord'; import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
import moment from 'moment'; import moment from 'moment';
import 'moment/min/locales'; import 'moment/min/locales';
@ -26,6 +25,7 @@ import { inviteLinksRequest } from '../actions/inviteLinks';
import { showErrorAlert } from '../utils/info'; import { showErrorAlert } from '../utils/info';
import { localAuthenticate } from '../utils/localAuthentication'; import { localAuthenticate } from '../utils/localAuthentication';
import { setActiveUsers } from '../actions/activeUsers'; import { setActiveUsers } from '../actions/activeUsers';
import UserPreferences from '../lib/userPreferences';
const getServer = state => state.server.server; const getServer = state => state.server.server;
const loginWithPasswordCall = args => RocketChat.loginWithPassword(args); const loginWithPasswordCall = args => RocketChat.loginWithPassword(args);
@ -88,7 +88,7 @@ const fetchUsersPresence = function* fetchUserPresence() {
const handleLoginSuccess = function* handleLoginSuccess({ user }) { const handleLoginSuccess = function* handleLoginSuccess({ user }) {
try { try {
const adding = yield select(state => state.server.adding); const adding = yield select(state => state.server.adding);
yield RNUserDefaults.set(RocketChat.TOKEN_KEY, user.token); yield UserPreferences.setStringAsync(RocketChat.TOKEN_KEY, user.token);
RocketChat.getUserPresence(user.id); RocketChat.getUserPresence(user.id);
@ -131,8 +131,8 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) {
} }
}); });
yield RNUserDefaults.set(`${ RocketChat.TOKEN_KEY }-${ server }`, user.id); yield UserPreferences.setStringAsync(`${ RocketChat.TOKEN_KEY }-${ server }`, user.id);
yield RNUserDefaults.set(`${ RocketChat.TOKEN_KEY }-${ user.id }`, user.token); yield UserPreferences.setStringAsync(`${ RocketChat.TOKEN_KEY }-${ user.id }`, user.token);
yield put(setUser(user)); yield put(setUser(user));
EventEmitter.emit('connected'); EventEmitter.emit('connected');
@ -182,9 +182,10 @@ const handleLogout = function* handleLogout({ forcedByServer }) {
if (servers.length > 0) { if (servers.length > 0) {
for (let i = 0; i < servers.length; i += 1) { for (let i = 0; i < servers.length; i += 1) {
const newServer = servers[i].id; const newServer = servers[i].id;
const token = yield RNUserDefaults.get(`${ RocketChat.TOKEN_KEY }-${ newServer }`); const token = yield UserPreferences.getStringAsync(`${ RocketChat.TOKEN_KEY }-${ newServer }`);
if (token) { if (token) {
return yield put(selectServerRequest(newServer)); yield put(selectServerRequest(newServer));
return;
} }
} }
} }

View File

@ -1,6 +1,5 @@
import { put, takeLatest } from 'redux-saga/effects'; import { put, takeLatest } from 'redux-saga/effects';
import { Alert } from 'react-native'; import { Alert } from 'react-native';
import RNUserDefaults from 'rn-user-defaults';
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord'; import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
import semver from 'semver'; import semver from 'semver';
@ -16,9 +15,9 @@ import database from '../lib/database';
import log, { logServerVersion } from '../utils/log'; import log, { logServerVersion } from '../utils/log';
import { extractHostname } from '../utils/server'; import { extractHostname } from '../utils/server';
import I18n from '../i18n'; import I18n from '../i18n';
import { SERVERS, TOKEN, SERVER_URL } from '../constants/userDefaults';
import { BASIC_AUTH_KEY, setBasicAuth } from '../utils/fetch'; import { BASIC_AUTH_KEY, setBasicAuth } from '../utils/fetch';
import { appStart, ROOT_INSIDE, ROOT_OUTSIDE } from '../actions/app'; import { appStart, ROOT_INSIDE, ROOT_OUTSIDE } from '../actions/app';
import UserPreferences from '../lib/userPreferences';
import { inquiryReset } from '../actions/inquiry'; import { inquiryReset } from '../actions/inquiry';
const getServerInfo = function* getServerInfo({ server, raiseError = true }) { const getServerInfo = function* getServerInfo({ server, raiseError = true }) {
@ -68,8 +67,8 @@ const handleSelectServer = function* handleSelectServer({ server, version, fetch
try { try {
yield put(inquiryReset()); yield put(inquiryReset());
const serversDB = database.servers; const serversDB = database.servers;
yield RNUserDefaults.set('currentServer', server); yield UserPreferences.setStringAsync(RocketChat.CURRENT_SERVER, server);
const userId = yield RNUserDefaults.get(`${ RocketChat.TOKEN_KEY }-${ server }`); const userId = yield UserPreferences.getStringAsync(`${ RocketChat.TOKEN_KEY }-${ server }`);
const userCollections = serversDB.collections.get('users'); const userCollections = serversDB.collections.get('users');
let user = null; let user = null;
if (userId) { if (userId) {
@ -85,17 +84,12 @@ const handleSelectServer = function* handleSelectServer({ server, version, fetch
statusText: userRecord.statusText, statusText: userRecord.statusText,
roles: userRecord.roles roles: userRecord.roles
}; };
} catch (e) { } catch {
// We only run it if not has user on DB // Do nothing
const servers = yield RNUserDefaults.objectForKey(SERVERS);
const userCredentials = servers && servers.find(srv => srv[SERVER_URL] === server);
user = userCredentials && {
token: userCredentials[TOKEN]
};
} }
} }
const basicAuth = yield RNUserDefaults.get(`${ BASIC_AUTH_KEY }-${ server }`); const basicAuth = yield UserPreferences.getStringAsync(`${ BASIC_AUTH_KEY }-${ server }`);
setBasicAuth(basicAuth); setBasicAuth(basicAuth);
// Check for running requests and abort them before connecting to the server // Check for running requests and abort them before connecting to the server
@ -136,7 +130,7 @@ const handleSelectServer = function* handleSelectServer({ server, version, fetch
const handleServerRequest = function* handleServerRequest({ server, certificate }) { const handleServerRequest = function* handleServerRequest({ server, certificate }) {
try { try {
if (certificate) { if (certificate) {
yield RNUserDefaults.setObjectForKey(extractHostname(server), certificate); yield UserPreferences.setMapAsync(extractHostname(server), certificate);
} }
const serverInfo = yield getServerInfo({ server }); const serverInfo = yield getServerInfo({ server });

View File

@ -5,7 +5,6 @@ import { NavigationContainer } from '@react-navigation/native';
import { AppearanceProvider } from 'react-native-appearance'; import { AppearanceProvider } from 'react-native-appearance';
import { createStackNavigator } from '@react-navigation/stack'; import { createStackNavigator } from '@react-navigation/stack';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import RNUserDefaults from 'rn-user-defaults';
import { import {
defaultTheme, defaultTheme,
@ -13,6 +12,7 @@ import {
subscribeTheme, subscribeTheme,
unsubscribeTheme unsubscribeTheme
} from './utils/theme'; } from './utils/theme';
import UserPreferences from './lib/userPreferences';
import Navigation from './lib/ShareNavigation'; import Navigation from './lib/ShareNavigation';
import store from './lib/createStore'; import store from './lib/createStore';
import { supportSystemTheme } from './utils/deviceInfo'; import { supportSystemTheme } from './utils/deviceInfo';
@ -138,9 +138,9 @@ class Root extends React.Component {
} }
init = async() => { init = async() => {
RNUserDefaults.objectForKey(THEME_PREFERENCES_KEY).then(this.setTheme); UserPreferences.getMapAsync(THEME_PREFERENCES_KEY).then(this.setTheme);
const currentServer = await RNUserDefaults.get('currentServer');
const token = await RNUserDefaults.get(RocketChat.TOKEN_KEY); const [currentServer, token] = await Promise.all([UserPreferences.getStringAsync(RocketChat.CURRENT_SERVER), UserPreferences.getStringAsync(RocketChat.TOKEN_KEY)]);
if (currentServer && token) { if (currentServer && token) {
await localAuthenticate(currentServer); await localAuthenticate(currentServer);

View File

@ -2,9 +2,9 @@ import * as LocalAuthentication from 'expo-local-authentication';
import moment from 'moment'; import moment from 'moment';
import RNBootSplash from 'react-native-bootsplash'; import RNBootSplash from 'react-native-bootsplash';
import AsyncStorage from '@react-native-community/async-storage'; import AsyncStorage from '@react-native-community/async-storage';
import RNUserDefaults from 'rn-user-defaults';
import { sha256 } from 'js-sha256'; import { sha256 } from 'js-sha256';
import UserPreferences from '../lib/userPreferences';
import store from '../lib/createStore'; import store from '../lib/createStore';
import database from '../lib/database'; import database from '../lib/database';
import { isIOS } from './deviceInfo'; import { isIOS } from './deviceInfo';
@ -51,7 +51,7 @@ const openChangePasscodeModal = ({ force }) => new Promise((resolve, reject) =>
export const changePasscode = async({ force = false }) => { export const changePasscode = async({ force = false }) => {
const passcode = await openChangePasscodeModal({ force }); const passcode = await openChangePasscodeModal({ force });
await RNUserDefaults.set(PASSCODE_KEY, sha256(passcode)); await UserPreferences.setStringAsync(PASSCODE_KEY, sha256(passcode));
}; };
export const biometryAuth = force => LocalAuthentication.authenticateAsync({ export const biometryAuth = force => LocalAuthentication.authenticateAsync({
@ -80,7 +80,7 @@ const checkBiometry = async(serverRecord) => {
}; };
export const checkHasPasscode = async({ force = true, serverRecord }) => { export const checkHasPasscode = async({ force = true, serverRecord }) => {
const storedPasscode = await RNUserDefaults.get(PASSCODE_KEY); const storedPasscode = await UserPreferences.getStringAsync(PASSCODE_KEY);
if (!storedPasscode) { if (!storedPasscode) {
await changePasscode({ force }); await changePasscode({ force });
await checkBiometry(serverRecord); await checkBiometry(serverRecord);

View File

@ -1,8 +1,8 @@
import { Linking } from 'react-native'; import { Linking } from 'react-native';
import * as WebBrowser from 'expo-web-browser'; import * as WebBrowser from 'expo-web-browser';
import RNUserDefaults from 'rn-user-defaults';
import parse from 'url-parse'; import parse from 'url-parse';
import UserPreferences from '../lib/userPreferences';
import { themes } from '../constants/colors'; import { themes } from '../constants/colors';
export const DEFAULT_BROWSER_KEY = 'DEFAULT_BROWSER_KEY'; export const DEFAULT_BROWSER_KEY = 'DEFAULT_BROWSER_KEY';
@ -37,7 +37,7 @@ const appSchemeURL = (url, browser) => {
const openLink = async(url, theme = 'light') => { const openLink = async(url, theme = 'light') => {
try { try {
const browser = await RNUserDefaults.get(DEFAULT_BROWSER_KEY); const browser = await UserPreferences.getStringAsync(DEFAULT_BROWSER_KEY);
if (browser) { if (browser) {
const schemeUrl = appSchemeURL(url, browser.replace(':', '')); const schemeUrl = appSchemeURL(url, browser.replace(':', ''));

View File

@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
import { import {
StyleSheet, FlatList, View, Text, Linking StyleSheet, FlatList, View, Text, Linking
} from 'react-native'; } from 'react-native';
import RNUserDefaults from 'rn-user-defaults';
import I18n from '../i18n'; import I18n from '../i18n';
import { withTheme } from '../theme'; import { withTheme } from '../theme';
@ -16,6 +15,7 @@ import { CustomIcon } from '../lib/Icons';
import { DEFAULT_BROWSER_KEY } from '../utils/openLink'; import { DEFAULT_BROWSER_KEY } from '../utils/openLink';
import { isIOS } from '../utils/deviceInfo'; import { isIOS } from '../utils/deviceInfo';
import SafeAreaView from '../containers/SafeAreaView'; import SafeAreaView from '../containers/SafeAreaView';
import UserPreferences from '../lib/userPreferences';
import { logEvent, events } from '../utils/log'; import { logEvent, events } from '../utils/log';
const DEFAULT_BROWSERS = [ const DEFAULT_BROWSERS = [
@ -81,12 +81,8 @@ class DefaultBrowserView extends React.Component {
async componentDidMount() { async componentDidMount() {
this.mounted = true; this.mounted = true;
try { const browser = await UserPreferences.getStringAsync(DEFAULT_BROWSER_KEY);
const browser = await RNUserDefaults.get(DEFAULT_BROWSER_KEY); this.setState({ browser });
this.setState({ browser });
} catch {
// do nothing
}
} }
init = () => { init = () => {
@ -117,7 +113,7 @@ class DefaultBrowserView extends React.Component {
logEvent(events.DB_CHANGE_DEFAULT_BROWSER, { browser: newBrowser }); logEvent(events.DB_CHANGE_DEFAULT_BROWSER, { browser: newBrowser });
try { try {
const browser = newBrowser !== 'inApp' ? newBrowser : null; const browser = newBrowser !== 'inApp' ? newBrowser : null;
await RNUserDefaults.set(DEFAULT_BROWSER_KEY, browser); await UserPreferences.setStringAsync(DEFAULT_BROWSER_KEY, browser);
this.setState({ browser }); this.setState({ browser });
} catch { } catch {
logEvent(events.DB_CHANGE_DEFAULT_BROWSER_F); logEvent(events.DB_CHANGE_DEFAULT_BROWSER_F);

View File

@ -6,10 +6,10 @@ import {
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import * as FileSystem from 'expo-file-system'; import * as FileSystem from 'expo-file-system';
import DocumentPicker from 'react-native-document-picker'; import DocumentPicker from 'react-native-document-picker';
import RNUserDefaults from 'rn-user-defaults';
import { Base64 } from 'js-base64'; import { Base64 } from 'js-base64';
import parse from 'url-parse'; import parse from 'url-parse';
import UserPreferences from '../lib/userPreferences';
import EventEmitter from '../utils/events'; import EventEmitter from '../utils/events';
import { selectServerRequest, serverRequest } from '../actions/server'; import { selectServerRequest, serverRequest } from '../actions/server';
import { inviteLinksClear as inviteLinksClearAction } from '../actions/inviteLinks'; import { inviteLinksClear as inviteLinksClearAction } from '../actions/inviteLinks';
@ -180,7 +180,7 @@ class NewServerView extends React.Component {
const parsedUrl = parse(text, true); const parsedUrl = parse(text, true);
if (parsedUrl.auth.length) { if (parsedUrl.auth.length) {
const credentials = Base64.encode(parsedUrl.auth); const credentials = Base64.encode(parsedUrl.auth);
await RNUserDefaults.set(`${ BASIC_AUTH_KEY }-${ server }`, credentials); await UserPreferences.setStringAsync(`${ BASIC_AUTH_KEY }-${ server }`, credentials);
setBasicAuth(credentials); setBasicAuth(credentials);
} }
} catch { } catch {

View File

@ -5,7 +5,6 @@ import {
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { connect, batch } from 'react-redux'; import { connect, batch } from 'react-redux';
import equal from 'deep-equal'; import equal from 'deep-equal';
import RNUserDefaults from 'rn-user-defaults';
import { withSafeAreaInsets } from 'react-native-safe-area-context'; import { withSafeAreaInsets } from 'react-native-safe-area-context';
import { toggleServerDropdown as toggleServerDropdownAction } from '../../actions/rooms'; import { toggleServerDropdown as toggleServerDropdownAction } from '../../actions/rooms';
@ -26,6 +25,7 @@ import { showConfirmationAlert } from '../../utils/info';
import { logEvent, events } from '../../utils/log'; import { logEvent, events } from '../../utils/log';
import { headerHeight } from '../../containers/Header'; import { headerHeight } from '../../containers/Header';
import { goRoom } from '../../utils/goRoom'; import { goRoom } from '../../utils/goRoom';
import UserPreferences from '../../lib/userPreferences';
const ROW_HEIGHT = 68; const ROW_HEIGHT = 68;
const ANIMATION_DURATION = 200; const ANIMATION_DURATION = 200;
@ -150,7 +150,7 @@ class ServerDropdown extends Component {
this.close(); this.close();
if (currentServer !== server) { if (currentServer !== server) {
logEvent(events.RL_CHANGE_SERVER); logEvent(events.RL_CHANGE_SERVER);
const userId = await RNUserDefaults.get(`${ RocketChat.TOKEN_KEY }-${ server }`); const userId = await UserPreferences.getStringAsync(`${ RocketChat.TOKEN_KEY }-${ server }`);
if (isMasterDetail) { if (isMasterDetail) {
goRoom({ item: {}, isMasterDetail }); goRoom({ item: {}, isMasterDetail });
} }

View File

@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
import { import {
FlatList, Text, View, StyleSheet FlatList, Text, View, StyleSheet
} from 'react-native'; } from 'react-native';
import RNUserDefaults from 'rn-user-defaults';
import I18n from '../i18n'; import I18n from '../i18n';
import { withTheme } from '../theme'; import { withTheme } from '../theme';
@ -16,6 +15,7 @@ import { CustomIcon } from '../lib/Icons';
import { THEME_PREFERENCES_KEY } from '../lib/rocketchat'; import { THEME_PREFERENCES_KEY } from '../lib/rocketchat';
import { supportSystemTheme } from '../utils/deviceInfo'; import { supportSystemTheme } from '../utils/deviceInfo';
import SafeAreaView from '../containers/SafeAreaView'; import SafeAreaView from '../containers/SafeAreaView';
import UserPreferences from '../lib/userPreferences';
import { events, logEvent } from '../utils/log'; import { events, logEvent } from '../utils/log';
const THEME_GROUP = 'THEME_GROUP'; const THEME_GROUP = 'THEME_GROUP';
@ -111,7 +111,7 @@ class ThemeView extends React.Component {
const { setTheme, themePreferences } = this.props; const { setTheme, themePreferences } = this.props;
const newTheme = { ...themePreferences, ...theme }; const newTheme = { ...themePreferences, ...theme };
setTheme(newTheme); setTheme(newTheme);
await RNUserDefaults.setObjectForKey(THEME_PREFERENCES_KEY, newTheme); await UserPreferences.setMapAsync(THEME_PREFERENCES_KEY, newTheme);
}; };
renderSeparator = () => { renderSeparator = () => {

View File

@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>KeychainGroup</key>
<string>$(AppIdentifierPrefix)chat.rocket.reactnative</string>
<key>AppGroup</key> <key>AppGroup</key>
<string>group.ios.chat.rocket</string> <string>group.ios.chat.rocket</string>
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>

View File

@ -0,0 +1,6 @@
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "SecureStorage.h"
#import <MMKVAppExtension/MMKV.h>

View File

@ -6,5 +6,9 @@
<array> <array>
<string>group.ios.chat.rocket</string> <string>group.ios.chat.rocket</string>
</array> </array>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)chat.rocket.reactnative</string>
</array>
</dict> </dict>
</plist> </plist>

View File

@ -60,8 +60,30 @@ class NotificationService: UNNotificationServiceExtension {
return return
} }
let mmapID = "default"
let instanceID = "com.MMKV.\(mmapID)"
let secureStorage = SecureStorage()
var cryptKey: Data = Data()
// get mmkv instance password from keychain
secureStorage.getSecureKey(instanceID.toHex()) { (response) -> () in
guard let password = response?[1] as? String else {
// kill the process and show notification as it came from APN
exit(0)
}
cryptKey = password.data(using: .utf8)!
}
// Get App Group directory
let suiteName = Bundle.main.object(forInfoDictionaryKey: "AppGroup") as! String let suiteName = Bundle.main.object(forInfoDictionaryKey: "AppGroup") as! String
let userDefaults = UserDefaults(suiteName: suiteName) guard let directory = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: suiteName) else {
return
}
// Set App Group dir
MMKV.initialize(rootDir: nil, groupDir: directory.path, logLevel: MMKVLogLevel.none)
guard let mmkv = MMKV(mmapID: mmapID, cryptKey: cryptKey, mode: MMKVMode.multiProcess) else {
return
}
var server = data.host var server = data.host
if (server.last == "/") { if (server.last == "/") {
@ -69,8 +91,8 @@ class NotificationService: UNNotificationServiceExtension {
} }
let msgId = data.messageId let msgId = data.messageId
let userId = userDefaults?.string(forKey: "reactnativemeteor_usertoken-\(server)") ?? "" let userId = mmkv.string(forKey: "reactnativemeteor_usertoken-\(server)") ?? ""
let token = userDefaults?.string(forKey: "reactnativemeteor_usertoken-\(userId)") ?? "" let token = mmkv.string(forKey: "reactnativemeteor_usertoken-\(userId)") ?? ""
if userId.isEmpty || token.isEmpty { if userId.isEmpty || token.isEmpty {
contentHandler(bestAttemptContent) contentHandler(bestAttemptContent)

View File

@ -0,0 +1,48 @@
//
// SecureStorage.h
// RocketChatRN
//
// https://github.com/ammarahm-ed/react-native-mmkv-storage/blob/master/ios/SecureStorage.h
//
#if __has_include("RCTBridgeModule.h")
#import "RCTBridgeModule.h"
#else
#import <React/RCTBridgeModule.h>
#endif
#import <Foundation/Foundation.h>
@interface SecureStorage: NSObject
- (void) setSecureKey: (nonnull NSString *)key value:(nonnull NSString *)value
options: (nonnull NSDictionary *)options
callback:(nullable RCTResponseSenderBlock)callback;
- (nullable NSString *) getSecureKey:(nonnull NSString *)key
callback:(nullable RCTResponseSenderBlock)callback;
- (bool) secureKeyExists:(nonnull NSString *)key
callback:(nullable RCTResponseSenderBlock)callback;
- (void) removeSecureKey:(nonnull NSString *)key
callback:(nullable RCTResponseSenderBlock)callback;
- (BOOL)searchKeychainCopyMatchingExists:(nonnull NSString *)identifier;
- (nonnull NSString *)searchKeychainCopyMatching:(nonnull NSString *)identifier;
- (nonnull NSMutableDictionary *)newSearchDictionary:(nonnull NSString *)identifier;
- (BOOL)createKeychainValue:(nonnull NSString *)value forIdentifier:(nonnull NSString *)identifier options: (NSDictionary * __nullable)options;
- (BOOL)updateKeychainValue:(nonnull NSString *)password forIdentifier:(nonnull NSString *)identifier options:(NSDictionary * __nullable)options;
- (BOOL)deleteKeychainValue:(nonnull NSString *)identifier;
- (void)clearSecureKeyStore;
- (void)handleAppUninstallation;
@end

View File

@ -0,0 +1,287 @@
//
// SecureStorage.m
// NotificationService
//
// https://github.com/ammarahm-ed/react-native-mmkv-storage/blob/master/ios/SecureStorage.m
// Refer to /patches/react-native-mmkv-storage+0.3.5.patch
#if __has_include("RCTBridgeModule.h")
#import "RCTBridgeModule.h"
#else
#import <React/RCTBridgeModule.h>
#endif
#import "SecureStorage.h"
@implementation SecureStorage : NSObject
NSString *serviceName;
- (void) setSecureKey: (NSString *)key value:(NSString *)value
options: (NSDictionary *)options
callback:(RCTResponseSenderBlock)callback
{
@try {
[self handleAppUninstallation];
BOOL status = [self createKeychainValue: value forIdentifier: key options: options];
if (status) {
callback(@[[NSNull null],@"Key updated successfully" ]);
} else {
BOOL status = [self updateKeychainValue: value forIdentifier: key options: options];
if (status) {
callback(@[[NSNull null],@"Key updated successfully" ]);
} else {
callback(@[@"An error occurred", [NSNull null]]);
}
}
}
@catch (NSException *exception) {
callback(@[exception.reason, [NSNull null]]);
}
}
- (NSString *) getSecureKey:(NSString *)key
callback:(RCTResponseSenderBlock)callback
{
@try {
[self handleAppUninstallation];
NSString *value = [self searchKeychainCopyMatching:key];
if (value == nil) {
NSString* errorMessage = @"key does not present";
if (callback != NULL) {
callback(@[errorMessage, [NSNull null]]);
}
return NULL;
} else {
if (callback != NULL) {
callback(@[[NSNull null], value]);
}
return value;
}
}
@catch (NSException *exception) {
if (callback != NULL) {
callback(@[exception.reason, [NSNull null]]);
}
return NULL;
}
}
- (bool) secureKeyExists:(NSString *)key
callback:(RCTResponseSenderBlock)callback
{
@try {
[self handleAppUninstallation];
BOOL exists = [self searchKeychainCopyMatchingExists:key];
if (exists) {
if (callback != NULL) {
callback(@[[NSNull null], @true]);
}
return true;
} else {
if (callback != NULL) {
callback(@[[NSNull null], @false]);
}
return false;
}
}
@catch(NSException *exception) {
if (callback != NULL) {
callback(@[exception.reason, [NSNull null]]);
}
return NULL;
}
}
- (void) removeSecureKey:(NSString *)key
callback:(RCTResponseSenderBlock)callback
{
@try {
BOOL status = [self deleteKeychainValue:key];
if (status) {
callback(@[[NSNull null], @"key removed successfully"]);
} else {
NSString* errorMessage = @"Could not find the key to delete.";
callback(@[errorMessage, [NSNull null]]);
}
}
@catch(NSException *exception) {
callback(@[exception.reason, [NSNull null]]);
}
}
- (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"];
[searchDictionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
NSData *encodedIdentifier = [identifier dataUsingEncoding:NSUTF8StringEncoding];
[searchDictionary setObject:encodedIdentifier forKey:(id)kSecAttrGeneric];
[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;
}
- (NSString *)searchKeychainCopyMatching:(NSString *)identifier {
NSMutableDictionary *searchDictionary = [self newSearchDictionary:identifier];
// Add search attributes
[searchDictionary setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
// Add search return types
[searchDictionary setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
NSDictionary *found = nil;
CFTypeRef result = NULL;
OSStatus status = SecItemCopyMatching((CFDictionaryRef)searchDictionary,
(CFTypeRef *)&result);
NSString *value = nil;
found = (__bridge NSDictionary*)(result);
if (found) {
value = [[NSString alloc] initWithData:found encoding:NSUTF8StringEncoding];
}
return value;
}
- (BOOL)searchKeychainCopyMatchingExists:(NSString *)identifier {
NSMutableDictionary *searchDictionary = [self newSearchDictionary:identifier];
// Add search attributes
[searchDictionary setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
// Add search return types
[searchDictionary setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
CFTypeRef result = NULL;
OSStatus status = SecItemCopyMatching((CFDictionaryRef)searchDictionary,
(CFTypeRef *)&result);
if (status != errSecItemNotFound) {
return YES;
}
return NO;
}
- (BOOL)createKeychainValue:(NSString *)value forIdentifier:(NSString *)identifier options: (NSDictionary * __nullable)options {
CFStringRef accessible = accessibleValue(options);
NSMutableDictionary *dictionary = [self newSearchDictionary:identifier];
NSData *valueData = [value dataUsingEncoding:NSUTF8StringEncoding];
[dictionary setObject:valueData forKey:(id)kSecValueData];
dictionary[(__bridge NSString *)kSecAttrAccessible] = (__bridge id)accessible;
OSStatus status = SecItemAdd((CFDictionaryRef)dictionary, NULL);
if (status == errSecSuccess) {
return YES;
}
return NO;
}
- (BOOL)updateKeychainValue:(NSString *)password forIdentifier:(NSString *)identifier options:(NSDictionary * __nullable)options {
CFStringRef accessible = accessibleValue(options);
NSMutableDictionary *searchDictionary = [self newSearchDictionary:identifier];
NSMutableDictionary *updateDictionary = [[NSMutableDictionary alloc] init];
NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
[updateDictionary setObject:passwordData forKey:(id)kSecValueData];
updateDictionary[(__bridge NSString *)kSecAttrAccessible] = (__bridge id)accessible;
OSStatus status = SecItemUpdate((CFDictionaryRef)searchDictionary,
(CFDictionaryRef)updateDictionary);
if (status == errSecSuccess) {
return YES;
}
return NO;
}
- (BOOL)deleteKeychainValue:(NSString *)identifier {
NSMutableDictionary *searchDictionary = [self newSearchDictionary:identifier];
OSStatus status = SecItemDelete((CFDictionaryRef)searchDictionary);
if (status == errSecSuccess) {
return YES;
}
return NO;
}
- (void)clearSecureKeyStore
{
NSArray *secItemClasses = @[(__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrGeneric,
(__bridge id)kSecAttrAccount,
(__bridge id)kSecClassKey,
(__bridge id)kSecAttrService];
for (id secItemClass in secItemClasses) {
NSDictionary *spec = @{(__bridge id)kSecClass: secItemClass};
SecItemDelete((__bridge CFDictionaryRef)spec);
}
}
- (void)handleAppUninstallation
{
// 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];
}
}
NSError * secureKeyStoreError(NSString *errMsg)
{
NSError *error = [NSError errorWithDomain:serviceName code:200 userInfo:@{@"reason": errMsg}];
return error;
}
CFStringRef accessibleValue(NSDictionary *options)
{
if (options && options[@"accessible"] != nil) {
NSDictionary *keyMap = @{
@"AccessibleWhenUnlocked": (__bridge NSString *)kSecAttrAccessibleWhenUnlocked,
@"AccessibleAfterFirstUnlock": (__bridge NSString *)kSecAttrAccessibleAfterFirstUnlock,
@"AccessibleAlways": (__bridge NSString *)kSecAttrAccessibleAlways,
@"AccessibleWhenPasscodeSetThisDeviceOnly": (__bridge NSString *)kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
@"AccessibleWhenUnlockedThisDeviceOnly": (__bridge NSString *)kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
@"AccessibleAfterFirstUnlockThisDeviceOnly": (__bridge NSString *)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
@"AccessibleAlwaysThisDeviceOnly": (__bridge NSString *)kSecAttrAccessibleAlwaysThisDeviceOnly
};
NSString *result = keyMap[options[@"accessible"]];
if (result) {
return (__bridge CFStringRef)result;
}
}
return kSecAttrAccessibleAfterFirstUnlock;
}
@end

View File

@ -0,0 +1,13 @@
//
// String+Hex.swift
// NotificationService
//
// Created by Djorkaeff Alexandre Vilela Pereira on 8/6/20.
// Copyright © 2020 Facebook. All rights reserved.
//
extension String {
func toHex() -> String {
return unicodeScalars.map{ .init($0.value, radix: 16, uppercase: false) }.joined()
}
}

View File

@ -18,6 +18,11 @@ target 'ShareRocketChatRN' do
all_pods all_pods
end end
# used to get user credentials
target 'NotificationService' do
pod 'MMKVAppExtension'
end
post_install do |installer| post_install do |installer|
installer.pods_project.targets.each do |target| installer.pods_project.targets.each do |target|
target.build_configurations.each do |config| target.build_configurations.each do |config|

View File

@ -184,6 +184,11 @@ PODS:
- libwebp/mux (1.1.0): - libwebp/mux (1.1.0):
- libwebp/demux - libwebp/demux
- libwebp/webp (1.1.0) - libwebp/webp (1.1.0)
- MMKV (1.2.1):
- MMKVCore (~> 1.2.1)
- MMKVAppExtension (1.2.1):
- MMKVCore (~> 1.2.1)
- MMKVCore (1.2.1)
- nanopb (1.30905.0): - nanopb (1.30905.0):
- nanopb/decode (= 1.30905.0) - nanopb/decode (= 1.30905.0)
- nanopb/encode (= 1.30905.0) - nanopb/encode (= 1.30905.0)
@ -370,6 +375,9 @@ PODS:
- react-native-jitsi-meet (2.1.1): - react-native-jitsi-meet (2.1.1):
- JitsiMeetSDK (= 2.8.1) - JitsiMeetSDK (= 2.8.1)
- React - React
- react-native-mmkv-storage (0.3.5):
- MMKV (= 1.2.1)
- React
- react-native-notifications (2.1.7): - react-native-notifications (2.1.7):
- React - React
- react-native-orientation-locker (1.1.8): - react-native-orientation-locker (1.1.8):
@ -495,8 +503,6 @@ PODS:
- React - React
- RNScreens (2.9.0): - RNScreens (2.9.0):
- React - React
- RNUserDefaults (1.8.1):
- React
- RNVectorIcons (7.0.0): - RNVectorIcons (7.0.0):
- React - React
- SDWebImage (5.8.4): - SDWebImage (5.8.4):
@ -565,6 +571,7 @@ DEPENDENCIES:
- Folly (from `../node_modules/react-native/third-party-podspecs/Folly.podspec`) - Folly (from `../node_modules/react-native/third-party-podspecs/Folly.podspec`)
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`) - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
- KeyCommands (from `../node_modules/react-native-keycommands`) - KeyCommands (from `../node_modules/react-native-keycommands`)
- MMKVAppExtension
- RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`) - RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`)
- RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`) - RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`)
- React (from `../node_modules/react-native/`) - React (from `../node_modules/react-native/`)
@ -582,6 +589,7 @@ DEPENDENCIES:
- "react-native-cameraroll (from `../node_modules/@react-native-community/cameraroll`)" - "react-native-cameraroll (from `../node_modules/@react-native-community/cameraroll`)"
- react-native-document-picker (from `../node_modules/react-native-document-picker`) - react-native-document-picker (from `../node_modules/react-native-document-picker`)
- react-native-jitsi-meet (from `../node_modules/react-native-jitsi-meet`) - react-native-jitsi-meet (from `../node_modules/react-native-jitsi-meet`)
- react-native-mmkv-storage (from `../node_modules/react-native-mmkv-storage`)
- react-native-notifications (from `../node_modules/react-native-notifications`) - react-native-notifications (from `../node_modules/react-native-notifications`)
- react-native-orientation-locker (from `../node_modules/react-native-orientation-locker`) - react-native-orientation-locker (from `../node_modules/react-native-orientation-locker`)
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
@ -617,7 +625,6 @@ DEPENDENCIES:
- RNReanimated (from `../node_modules/react-native-reanimated`) - RNReanimated (from `../node_modules/react-native-reanimated`)
- RNRootView (from `../node_modules/rn-root-view`) - RNRootView (from `../node_modules/rn-root-view`)
- RNScreens (from `../node_modules/react-native-screens`) - RNScreens (from `../node_modules/react-native-screens`)
- RNUserDefaults (from `../node_modules/rn-user-defaults`)
- RNVectorIcons (from `../node_modules/react-native-vector-icons`) - RNVectorIcons (from `../node_modules/react-native-vector-icons`)
- UMAppLoader (from `../node_modules/unimodules-app-loader/ios`) - UMAppLoader (from `../node_modules/unimodules-app-loader/ios`)
- UMBarCodeScannerInterface (from `../node_modules/unimodules-barcode-scanner-interface/ios`) - UMBarCodeScannerInterface (from `../node_modules/unimodules-barcode-scanner-interface/ios`)
@ -658,6 +665,9 @@ SPEC REPOS:
- GoogleUtilities - GoogleUtilities
- JitsiMeetSDK - JitsiMeetSDK
- libwebp - libwebp
- MMKV
- MMKVAppExtension
- MMKVCore
- nanopb - nanopb
- OpenSSL-Universal - OpenSSL-Universal
- PromisesObjC - PromisesObjC
@ -733,6 +743,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-document-picker" :path: "../node_modules/react-native-document-picker"
react-native-jitsi-meet: react-native-jitsi-meet:
:path: "../node_modules/react-native-jitsi-meet" :path: "../node_modules/react-native-jitsi-meet"
react-native-mmkv-storage:
:path: "../node_modules/react-native-mmkv-storage"
react-native-notifications: react-native-notifications:
:path: "../node_modules/react-native-notifications" :path: "../node_modules/react-native-notifications"
react-native-orientation-locker: react-native-orientation-locker:
@ -803,8 +815,6 @@ EXTERNAL SOURCES:
:path: "../node_modules/rn-root-view" :path: "../node_modules/rn-root-view"
RNScreens: RNScreens:
:path: "../node_modules/react-native-screens" :path: "../node_modules/react-native-screens"
RNUserDefaults:
:path: "../node_modules/rn-user-defaults"
RNVectorIcons: RNVectorIcons:
:path: "../node_modules/react-native-vector-icons" :path: "../node_modules/react-native-vector-icons"
UMAppLoader: UMAppLoader:
@ -877,6 +887,9 @@ SPEC CHECKSUMS:
JitsiMeetSDK: 2984eac1343690bf1c0c72bde75b48b0148d0f79 JitsiMeetSDK: 2984eac1343690bf1c0c72bde75b48b0148d0f79
KeyCommands: f66c535f698ed14b3d3a4e58859d79a827ea907e KeyCommands: f66c535f698ed14b3d3a4e58859d79a827ea907e
libwebp: 946cb3063cea9236285f7e9a8505d806d30e07f3 libwebp: 946cb3063cea9236285f7e9a8505d806d30e07f3
MMKV: 67253edee25a34edf332f91d73fa94a9e038b971
MMKVAppExtension: d792aa7bd301285e2c3100c5ce15aa44fa26456f
MMKVCore: fe398984acac1fa33f92795d1b5fd0a334c944af
nanopb: c43f40fadfe79e8b8db116583945847910cbabc9 nanopb: c43f40fadfe79e8b8db116583945847910cbabc9
OpenSSL-Universal: 8b48cc0d10c1b2923617dfe5c178aa9ed2689355 OpenSSL-Universal: 8b48cc0d10c1b2923617dfe5c178aa9ed2689355
PromisesObjC: b48e0338dbbac2207e611750777895f7a5811b75 PromisesObjC: b48e0338dbbac2207e611750777895f7a5811b75
@ -895,6 +908,7 @@ SPEC CHECKSUMS:
react-native-cameraroll: ae0a7c0cc8462508855707ff623b1e789b692865 react-native-cameraroll: ae0a7c0cc8462508855707ff623b1e789b692865
react-native-document-picker: 825552b827012282baf4b7cbf119d3b37a280c90 react-native-document-picker: 825552b827012282baf4b7cbf119d3b37a280c90
react-native-jitsi-meet: f89bcb2cfbd5b15403b9c40738036c4f1af45d05 react-native-jitsi-meet: f89bcb2cfbd5b15403b9c40738036c4f1af45d05
react-native-mmkv-storage: 48729fe90e850ef2fdc9d3714b7030c7c51d82b0
react-native-notifications: ee8fd739853e72694f3af8b374c8ccb106b7b227 react-native-notifications: ee8fd739853e72694f3af8b374c8ccb106b7b227
react-native-orientation-locker: f0ca1a8e5031dab6b74bfb4ab33a17ed2c2fcb0d react-native-orientation-locker: f0ca1a8e5031dab6b74bfb4ab33a17ed2c2fcb0d
react-native-safe-area-context: 344b969c45af3d8464d36e8dea264942992ef033 react-native-safe-area-context: 344b969c45af3d8464d36e8dea264942992ef033
@ -930,7 +944,6 @@ SPEC CHECKSUMS:
RNReanimated: b5ccb50650ba06f6e749c7c329a1bc3ae0c88b43 RNReanimated: b5ccb50650ba06f6e749c7c329a1bc3ae0c88b43
RNRootView: 895a4813dedeaca82db2fa868ca1c333d790e494 RNRootView: 895a4813dedeaca82db2fa868ca1c333d790e494
RNScreens: c526239bbe0e957b988dacc8d75ac94ec9cb19da RNScreens: c526239bbe0e957b988dacc8d75ac94ec9cb19da
RNUserDefaults: c421fd97ad06b35c16608c5d0fe675db353f632d
RNVectorIcons: da6fe858f5a65d7bbc3379540a889b0b12aa5976 RNVectorIcons: da6fe858f5a65d7bbc3379540a889b0b12aa5976
SDWebImage: cf6922231e95550934da2ada0f20f2becf2ceba9 SDWebImage: cf6922231e95550934da2ada0f20f2becf2ceba9
SDWebImageWebPCoder: 36f8f47bd9879a8aea6044765c1351120fd8e3a8 SDWebImageWebPCoder: 36f8f47bd9879a8aea6044765c1351120fd8e3a8
@ -951,6 +964,6 @@ SPEC CHECKSUMS:
Yoga: d5bd05a2b6b94c52323745c2c2b64557c8c66f64 Yoga: d5bd05a2b6b94c52323745c2c2b64557c8c66f64
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
PODFILE CHECKSUM: 55c04243097892160d63f79f3a23157165b7ac68 PODFILE CHECKSUM: 4916aa46fbfaed764171540e6ed76fb07a8a95e7
COCOAPODS: 1.9.3 COCOAPODS: 1.9.3

View File

@ -1,29 +0,0 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <FirebaseCore/FIRConfiguration.h>
@class FIRAnalyticsConfiguration;
@interface FIRConfiguration ()
/**
* The configuration class for Firebase Analytics. This should be removed once the logic for
* enabling and disabling Analytics is moved to Analytics.
*/
@property(nonatomic, readwrite) FIRAnalyticsConfiguration *analyticsConfiguration;
@end

View File

@ -1,35 +0,0 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
#import <FirebaseCoreDiagnosticsInterop/FIRCoreDiagnosticsData.h>
NS_ASSUME_NONNULL_BEGIN
/** Implements the FIRCoreDiagnosticsData protocol to log diagnostics data. */
@interface FIRDiagnosticsData : NSObject <FIRCoreDiagnosticsData>
/** Inserts values into the diagnosticObjects dictionary if the value isn't nil.
*
* @param value The value to insert if it's not nil.
* @param key The key to associate it with.
*/
- (void)insertValue:(nullable id)value forKey:(NSString *)key;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,34 +0,0 @@
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
#import "FIRCoreDiagnosticsData.h"
NS_ASSUME_NONNULL_BEGIN
/** Allows the interoperation of FirebaseCore and FirebaseCoreDiagnostics. */
@protocol FIRCoreDiagnosticsInterop <NSObject>
/** Sends the given diagnostics data.
*
* @param diagnosticsData The diagnostics data object to send.
*/
+ (void)sendDiagnosticsData:(id<FIRCoreDiagnosticsData>)diagnosticsData;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,251 +0,0 @@
# Firebase iOS Open Source Development [![Build Status](https://travis-ci.org/firebase/firebase-ios-sdk.svg?branch=master)](https://travis-ci.org/firebase/firebase-ios-sdk)
This repository contains a subset of the Firebase iOS SDK source. It currently
includes FirebaseCore, FirebaseABTesting, FirebaseAuth, FirebaseDatabase,
FirebaseFirestore, FirebaseFunctions, FirebaseInstanceID, FirebaseInAppMessaging,
FirebaseInAppMessagingDisplay, FirebaseMessaging, FirebaseRemoteConfig, and
FirebaseStorage.
The repository also includes GoogleUtilities source. The
[GoogleUtilities](GoogleUtilities/README.md) pod is
a set of utilities used by Firebase and other Google products.
Firebase is an app development platform with tools to help you build, grow and
monetize your app. More information about Firebase can be found at
[https://firebase.google.com](https://firebase.google.com).
## Installation
See the three subsections for details about three different installation methods.
1. [Standard pod install](README.md#standard-pod-install)
1. [Installing from the GitHub repo](README.md#installing-from-github)
1. [Experimental Carthage](README.md#carthage-ios-only)
### Standard pod install
Go to
[https://firebase.google.com/docs/ios/setup](https://firebase.google.com/docs/ios/setup).
### Installing from GitHub
For releases starting with 5.0.0, the source for each release is also deployed
to CocoaPods master and available via standard
[CocoaPods Podfile syntax](https://guides.cocoapods.org/syntax/podfile.html#pod).
These instructions can be used to access the Firebase repo at other branches,
tags, or commits.
#### Background
See
[the Podfile Syntax Reference](https://guides.cocoapods.org/syntax/podfile.html#pod)
for instructions and options about overriding pod source locations.
#### Accessing Firebase Source Snapshots
All of the official releases are tagged in this repo and available via CocoaPods. To access a local
source snapshot or unreleased branch, use Podfile directives like the following:
To access FirebaseFirestore via a branch:
```
pod 'FirebaseCore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master'
pod 'FirebaseFirestore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master'
```
To access FirebaseMessaging via a checked out version of the firebase-ios-sdk repo do:
```
pod 'FirebaseCore', :path => '/path/to/firebase-ios-sdk'
pod 'FirebaseMessaging', :path => '/path/to/firebase-ios-sdk'
```
### Carthage (iOS only)
Instructions for the experimental Carthage distribution are at
[Carthage](Carthage.md).
### Rome
Instructions for installing binary frameworks via
[Rome](https://github.com/CocoaPods/Rome) are at [Rome](Rome.md).
## Development
To develop Firebase software in this repository, ensure that you have at least
the following software:
* Xcode 10.1 (or later)
* CocoaPods 1.7.2 (or later)
* [CocoaPods generate](https://github.com/square/cocoapods-generate)
For the pod that you want to develop:
`pod gen Firebase{name here}.podspec --local-sources=./ --auto-open --platforms=ios`
Note: If the CocoaPods cache is out of date, you may need to run
`pod repo update` before the `pod gen` command.
Note: Set the `--platforms` option to `macos` or `tvos` to develop/test for
those platforms. Since 10.2, Xcode does not properly handle multi-platform
CocoaPods workspaces.
Firestore has a self contained Xcode project. See
[Firestore/README.md](Firestore/README.md).
### Development for Catalyst
* `pod gen {name here}.podspec --local-sources=./ --auto-open --platforms=ios`
* Check the Mac box in the App-iOS Build Settings
* Sign the App in the Settings Signing & Capabilities tab
* Click Pods in the Project Manager
* Add Signing to the iOS host app and unit test targets
* Select the Unit-unit scheme
* Run it to build and test
### Adding a New Firebase Pod
See [AddNewPod.md](AddNewPod.md).
### Code Formatting
To ensure that the code is formatted consistently, run the script
[./scripts/style.sh](https://github.com/firebase/firebase-ios-sdk/blob/master/scripts/style.sh)
before creating a PR.
Travis will verify that any code changes are done in a style compliant way. Install
`clang-format` and `swiftformat`.
These commands will get the right versions:
```
brew upgrade https://raw.githubusercontent.com/Homebrew/homebrew-core/e3496d9/Formula/clang-format.rb
brew upgrade https://raw.githubusercontent.com/Homebrew/homebrew-core/7963c3d/Formula/swiftformat.rb
```
Note: if you already have a newer version of these installed you may need to
`brew switch` to this version.
To update this section, find the versions of clang-format and swiftformat.rb to
match the versions in the CI failure logs
[here](https://github.com/Homebrew/homebrew-core/tree/master/Formula).
### Running Unit Tests
Select a scheme and press Command-u to build a component and run its unit tests.
#### Viewing Code Coverage
First, make sure that [xcov](https://github.com/nakiostudio/xcov) is installed with `gem install xcov`.
After running the `AllUnitTests_iOS` scheme in Xcode, execute
`xcov --workspace Firebase.xcworkspace --scheme AllUnitTests_iOS --output_directory xcov_output`
at Example/ in the terminal. This will aggregate the coverage, and you can run `open xcov_output/index.html` to see the results.
### Running Sample Apps
In order to run the sample apps and integration tests, you'll need valid
`GoogleService-Info.plist` files for those samples. The Firebase Xcode project contains dummy plist
files without real values, but can be replaced with real plist files. To get your own
`GoogleService-Info.plist` files:
1. Go to the [Firebase Console](https://console.firebase.google.com/)
2. Create a new Firebase project, if you don't already have one
3. For each sample app you want to test, create a new Firebase app with the sample app's bundle
identifier (e.g. `com.google.Database-Example`)
4. Download the resulting `GoogleService-Info.plist` and replace the appropriate dummy plist file
(e.g. in [Example/Database/App/](Example/Database/App/));
Some sample apps like Firebase Messaging ([Example/Messaging/App](Example/Messaging/App)) require
special Apple capabilities, and you will have to change the sample app to use a unique bundle
identifier that you can control in your own Apple Developer account.
## Specific Component Instructions
See the sections below for any special instructions for those components.
### Firebase Auth
If you're doing specific Firebase Auth development, see
[the Auth Sample README](Example/Auth/README.md) for instructions about
building and running the FirebaseAuth pod along with various samples and tests.
### Firebase Database
To run the Database Integration tests, make your database authentication rules
[public](https://firebase.google.com/docs/database/security/quickstart).
### Firebase Storage
To run the Storage Integration tests, follow the instructions in
[FIRStorageIntegrationTests.m](Example/Storage/Tests/Integration/FIRStorageIntegrationTests.m).
#### Push Notifications
Push notifications can only be delivered to specially provisioned App IDs in the developer portal.
In order to actually test receiving push notifications, you will need to:
1. Change the bundle identifier of the sample app to something you own in your Apple Developer
account, and enable that App ID for push notifications.
2. You'll also need to
[upload your APNs Provider Authentication Key or certificate to the Firebase Console](https://firebase.google.com/docs/cloud-messaging/ios/certs)
at **Project Settings > Cloud Messaging > [Your Firebase App]**.
3. Ensure your iOS device is added to your Apple Developer portal as a test device.
#### iOS Simulator
The iOS Simulator cannot register for remote notifications, and will not receive push notifications.
In order to receive push notifications, you'll have to follow the steps above and run the app on a
physical device.
## Community Supported Efforts
We've seen an amazing amount of interest and contributions to improve the Firebase SDKs, and we are
very grateful! We'd like to empower as many developers as we can to be able to use Firebase and
participate in the Firebase community.
### tvOS, macOS, and Catalyst
Thanks to contributions from the community, FirebaseABTesting, FirebaseAuth, FirebaseCore,
FirebaseDatabase, FirebaseMessaging, FirebaseFirestore,
FirebaseFunctions, FirebaseRemoteConfig, and FirebaseStorage now compile, run unit tests, and work on
tvOS, macOS, and Catalyst.
For tvOS, checkout the [Sample](Example/tvOSSample).
Keep in mind that macOS, Catalyst and tvOS are not officially supported by Firebase, and this
repository is actively developed primarily for iOS. While we can catch basic unit test issues with
Travis, there may be some changes where the SDK no longer works as expected on macOS or tvOS. If you
encounter this, please [file an issue](https://github.com/firebase/firebase-ios-sdk/issues).
To install, add a subset of the following to the Podfile:
```
pod 'Firebase/ABTesting'
pod 'Firebase/Auth'
pod 'Firebase/Database'
pod 'Firebase/Firestore'
pod 'Firebase/Functions'
pod 'Firebase/Messaging'
pod 'Firebase/RemoteConfig'
pod 'Firebase/Storage'
```
#### Additional Catalyst Notes
* FirebaseAuth and FirebaseMessaging require adding `Keychain Sharing Capability`
to Build Settings.
* FirebaseFirestore requires signing the
[gRPC Resource target](https://github.com/firebase/firebase-ios-sdk/issues/3500#issuecomment-518741681).
## Roadmap
See [Roadmap](ROADMAP.md) for more about the Firebase iOS SDK Open Source
plans and directions.
## Contributing
See [Contributing](CONTRIBUTING.md) for more information on contributing to the Firebase
iOS SDK.
## License
The contents of this repository is licensed under the
[Apache License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0).
Your use of Firebase is governed by the
[Terms of Service for Firebase Services](https://firebase.google.com/terms/).

1
ios/Pods/Headers/Private/MMKV/MMKV.h generated Symbolic link
View File

@ -0,0 +1 @@
../../../MMKV/iOS/MMKV/MMKV/MMKV.h

View File

@ -0,0 +1 @@
../../../MMKV/iOS/MMKV/MMKV/MMKVHandler.h

View File

@ -0,0 +1 @@
../../../MMKVAppExtension/iOS/MMKV/MMKV/MMKV.h

View File

@ -0,0 +1 @@
../../../MMKVAppExtension/iOS/MMKV/MMKV/MMKVHandler.h

View File

@ -0,0 +1 @@
../../../MMKVCore/Core/aes/AESCrypt.h

View File

@ -0,0 +1 @@
../../../MMKVCore/Core/crc32/Checksum.h

View File

@ -0,0 +1 @@
../../../MMKVCore/Core/CodedInputData.h

View File

@ -0,0 +1 @@
../../../MMKVCore/Core/CodedInputDataCrypt.h

View File

@ -0,0 +1 @@
../../../MMKVCore/Core/CodedOutputData.h

View File

@ -0,0 +1 @@
../../../MMKVCore/Core/InterProcessLock.h

View File

@ -0,0 +1 @@
../../../MMKVCore/Core/KeyValueHolder.h

View File

@ -0,0 +1 @@
../../../MMKVCore/Core/MMBuffer.h

1
ios/Pods/Headers/Private/MMKVCore/MMKV.h generated Symbolic link
View File

@ -0,0 +1 @@
../../../MMKVCore/Core/MMKV.h

View File

@ -0,0 +1 @@
../../../MMKVCore/Core/MMKVLog.h

View File

@ -0,0 +1 @@
../../../MMKVCore/Core/MMKVMetaInfo.hpp

View File

@ -0,0 +1 @@
../../../MMKVCore/Core/MMKVPredef.h

View File

@ -0,0 +1 @@
../../../MMKVCore/Core/MMKV_IO.h

View File

@ -0,0 +1 @@
../../../MMKVCore/Core/MMKV_OSX.h

View File

@ -0,0 +1 @@
../../../MMKVCore/Core/MemoryFile.h

View File

@ -0,0 +1 @@
../../../MMKVCore/Core/MiniPBCoder.h

View File

@ -0,0 +1 @@
../../../MMKVCore/Core/PBEncodeItem.hpp

View File

@ -0,0 +1 @@
../../../MMKVCore/Core/PBUtility.h

View File

@ -0,0 +1 @@
../../../MMKVCore/Core/ScopedLock.hpp

View File

@ -0,0 +1 @@
../../../MMKVCore/Core/ThreadLock.h

View File

@ -0,0 +1 @@
../../../MMKVCore/Core/aes/openssl/openssl_aes.h

View File

@ -0,0 +1 @@
../../../MMKVCore/Core/aes/openssl/openssl_aes_locl.h

View File

@ -0,0 +1 @@
../../../MMKVCore/Core/aes/openssl/openssl_arm_arch.h

View File

@ -0,0 +1 @@
../../../MMKVCore/Core/aes/openssl/openssl_md32_common.h

View File

@ -0,0 +1 @@
../../../MMKVCore/Core/aes/openssl/openssl_md5.h

View File

@ -0,0 +1 @@
../../../MMKVCore/Core/aes/openssl/openssl_md5_locl.h

View File

@ -0,0 +1 @@
../../../MMKVCore/Core/aes/openssl/openssl_opensslconf.h

View File

@ -1 +0,0 @@
../../../../../node_modules/rn-user-defaults/ios/RNUserDefaults.h

View File

@ -0,0 +1 @@
../../../../../node_modules/react-native-mmkv-storage/ios/IDStore.h

View File

@ -0,0 +1 @@
../../../../../node_modules/react-native-mmkv-storage/ios/MMKVStorage.h

View File

@ -0,0 +1 @@
../../../../../node_modules/react-native-mmkv-storage/ios/SecureStorage.h

View File

@ -0,0 +1 @@
../../../../../node_modules/react-native-mmkv-storage/ios/StorageGetters.h

View File

@ -0,0 +1 @@
../../../../../node_modules/react-native-mmkv-storage/ios/StorageIndexer.h

View File

@ -0,0 +1 @@
../../../../../node_modules/react-native-mmkv-storage/ios/StorageSetters.h

1
ios/Pods/Headers/Public/MMKV/MMKV.h generated Symbolic link
View File

@ -0,0 +1 @@
../../../MMKV/iOS/MMKV/MMKV/MMKV.h

1
ios/Pods/Headers/Public/MMKV/MMKVHandler.h generated Symbolic link
View File

@ -0,0 +1 @@
../../../MMKV/iOS/MMKV/MMKV/MMKVHandler.h

View File

@ -0,0 +1 @@
../../../MMKVAppExtension/iOS/MMKV/MMKV/MMKV.h

View File

@ -0,0 +1 @@
../../../MMKVAppExtension/iOS/MMKV/MMKV/MMKVHandler.h

View File

@ -0,0 +1 @@
../../../MMKVCore/Core/MMBuffer.h

1
ios/Pods/Headers/Public/MMKVCore/MMKV.h generated Symbolic link
View File

@ -0,0 +1 @@
../../../MMKVCore/Core/MMKV.h

1
ios/Pods/Headers/Public/MMKVCore/MMKVLog.h generated Symbolic link
View File

@ -0,0 +1 @@
../../../MMKVCore/Core/MMKVLog.h

View File

@ -0,0 +1 @@
../../../MMKVCore/Core/MMKVPredef.h

View File

@ -0,0 +1 @@
../../../MMKVCore/Core/PBUtility.h

View File

@ -0,0 +1 @@
../../../MMKVCore/Core/ScopedLock.hpp

View File

@ -0,0 +1 @@
../../../MMKVCore/Core/ThreadLock.h

View File

@ -0,0 +1 @@
../../../MMKVCore/Core/aes/openssl/openssl_md5.h

View File

@ -0,0 +1 @@
../../../MMKVCore/Core/aes/openssl/openssl_opensslconf.h

View File

@ -1 +0,0 @@
../../../../../node_modules/rn-user-defaults/ios/RNUserDefaults.h

View File

@ -0,0 +1 @@
../../../../../node_modules/react-native-mmkv-storage/ios/IDStore.h

View File

@ -0,0 +1 @@
../../../../../node_modules/react-native-mmkv-storage/ios/MMKVStorage.h

View File

@ -0,0 +1 @@
../../../../../node_modules/react-native-mmkv-storage/ios/SecureStorage.h

View File

@ -0,0 +1 @@
../../../../../node_modules/react-native-mmkv-storage/ios/StorageGetters.h

View File

@ -0,0 +1 @@
../../../../../node_modules/react-native-mmkv-storage/ios/StorageIndexer.h

View File

@ -0,0 +1 @@
../../../../../node_modules/react-native-mmkv-storage/ios/StorageSetters.h

View File

@ -1,27 +0,0 @@
{
"name": "RNUserDefaults",
"version": "1.8.1",
"summary": "Use `UserDefaults` (iOS) with React Native and `SharedPreferences` on AndroidOS.",
"description": "Use `UserDefaults` (iOS) with React Native and `SharedPreferences` on AndroidOS.",
"license": "MIT",
"authors": "djorkaeffalexandre",
"homepage": "https://github.com/RocketChat/rn-user-defaults.git",
"source": {
"git": "https://github.com/RocketChat/rn-user-defaults.git"
},
"requires_arc": true,
"platforms": {
"ios": "7.0"
},
"preserve_paths": [
"README.md",
"package.json",
"index.js"
],
"source_files": "iOS/*.{h,m}",
"dependencies": {
"React": [
]
}
}

View File

@ -0,0 +1,25 @@
{
"name": "react-native-mmkv-storage",
"version": "0.3.5",
"summary": "This library aims to provide a fast & reliable solution for you data storage needs in react-native apps. It uses [MMKV](https://github.com/Tencent/MMKV) by Tencent under the hood on Android and iOS both that is used by their WeChat app(more than 1 Billion users). Unlike other storage solutions for React Native, this library lets you store any kind of data type, in any number of database instances, with or without encryption in a very fast and efficient way.",
"homepage": "https://github.com/ammarahm-ed/react-native-mmkv-storage",
"license": "MIT",
"authors": "Ammar Ahmed for @ammarahm-ed",
"platforms": {
"ios": "9.0"
},
"source": {
"git": "https://github.com/ammarahm-ed/react-native-mmkv-storage",
"tag": "V0.3.5"
},
"source_files": "ios/**/*.{h,m}",
"requires_arc": true,
"dependencies": {
"React": [
],
"MMKV": [
"1.2.1"
]
}
}

189
ios/Pods/MMKV/LICENSE.TXT generated Normal file
View File

@ -0,0 +1,189 @@
Tencent is pleased to support the open source community by making MMKV available.
Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved.
If you have downloaded a copy of the MMKV binary from Tencent, please note that the MMKV binary is licensed under the BSD 3-Clause License.
If you have downloaded a copy of the MMKV source code from Tencent, please note that MMKV source code is licensed under the BSD 3-Clause License, except for the third-party components listed below which are subject to different license terms. Your integration of MMKV into your own projects may require compliance with the BSD 3-Clause License, as well as the other licenses applicable to the third-party components included within MMKV.
A copy of the BSD 3-Clause License is included in this file.
Other dependencies and licenses:
Open Source Software Licensed Under the OpenSSL License:
----------------------------------------------------------------------------------------
1. OpenSSL 1.1.0i
Copyright (c) 1998-2018 The OpenSSL Project.
All rights reserved.
Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
All rights reserved.
Terms of the OpenSSL License:
---------------------------------------------------
LICENSE ISSUES:
--------------------------------------------------------------------
The OpenSSL toolkit stays under a dual license, i.e. both the conditions of the OpenSSL License and the original SSLeay license apply to the toolkit.
See below for the actual license texts.
OpenSSL License:
--------------------------------------------------------------------
Copyright (c) 1998-2018 The OpenSSL Project. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. All advertising materials mentioning features or use of this software must display the following acknowledgment:
"This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to endorse or promote products derived from this software without prior written permission. For written permission, please contact openssl-core@openssl.org.
5. Products derived from this software may not be called "OpenSSL" nor may "OpenSSL" appear in their names without prior written permission of the OpenSSL Project.
6. Redistributions of any form whatsoever must retain the following acknowledgment: "This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit (http://www.openssl.org/)"
THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
====================================================================
* This product includes cryptographic software written by Eric Young (eay@cryptsoft.com). This product includes software written by Tim Hudson (tjh@cryptsoft.com).
Original SSLeay License:
--------------------------------------------------------------------
Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
All rights reserved.
This package is an SSL implementation written by Eric Young (eay@cryptsoft.com).
The implementation was written so as to conform with Netscapes SSL.
This library is free for commercial and non-commercial use as long as the following conditions are aheared to. The following conditions apply to all code found in this distribution, be it the RC4, RSA, lhash, DES, etc., code; not just the SSL code. The SSL documentation included with this distribution is covered by the same copyright terms except that the holder is Tim Hudson (tjh@cryptsoft.com).
Copyright remains Eric Young's, and as such any Copyright notices in the code are not to be removed. If this package is used in a product, Eric Young should be given attribution as the author of the parts of the library used. This can be in the form of a textual message at program startup or in documentation (online or textual) provided with the package.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. All advertising materials mentioning features or use of this software must display the following acknowledgement:" This product includes cryptographic software written by Eric Young (eay@cryptsoft.com)" The word 'cryptographic' can be left out if the rouines from the library being used are not cryptographic related :-).
4. If you include any Windows specific code (or a derivative thereof) from the apps directory (application code) you must include an acknowledgement: "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"
THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
The licence and distribution terms for any publically available version or derivative of this code cannot be changed. i.e. this code cannot simply be copied and put under another distribution licence [including the GNU Public Licence.]
Open Source Software Licensed Under the Apache License, Version 2.0:
The below software in this distribution may have been modified by THL A29 Limited (“Tencent Modifications”). All Tencent Modifications are Copyright (C) 2018 THL A29 Limited.
----------------------------------------------------------------------------------------
1. MultiprocessSharedPreferences v1.0
Copyright (C) 2014 seven456@gmail.com
Terms of the Apache License, Version 2.0:
--------------------------------------------------------------------
Apache License Version 2.0, January 2004 http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
“License” shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
“Licensor” shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
“Legal Entity” shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, “control” means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
“You” (or “Your”) shall mean an individual or Legal Entity exercising permissions granted by this License.
“Source” form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
“Object” form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
“Work” shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
“Derivative Works” shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
“Contribution” shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, “submitted” means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as “Not a Contribution.”
“Contributor” shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
a) You must give any other recipients of the Work or Derivative Works a copy of this License; and
b) You must cause any modified files to carry prominent notices stating that You changed the files; and
c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
d) If the Work includes a “NOTICE” text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Open Source Software Licensed Under the zlib License:
The below software in this distribution may have been modified by THL A29 Limited (“Tencent Modifications”). All Tencent Modifications are Copyright (C) 2018 THL A29 Limited.
----------------------------------------------------------------------------------------
1. zlib v1.2.11
Copyright (C) 1995-2017 Jean-loup Gailly and Mark Adler
Terms of the zlib License:
--------------------------------------------------------------------
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
Terms of the BSD 3-Clause License:
--------------------------------------------------------------------
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
Neither the name of [copyright holder] nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

287
ios/Pods/MMKV/README.md generated Normal file
View File

@ -0,0 +1,287 @@
[![license](https://img.shields.io/badge/license-BSD_3-brightgreen.svg?style=flat)](https://github.com/Tencent/MMKV/blob/master/LICENSE.TXT)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/Tencent/MMKV/pulls)
[![Release Version](https://img.shields.io/badge/release-1.2.1-brightgreen.svg)](https://github.com/Tencent/MMKV/releases)
[![Platform](https://img.shields.io/badge/Platform-%20Android%20%7C%20iOS%2FmacOS%20%7C%20Win32%20%7C%20POSIX-brightgreen.svg)](https://github.com/Tencent/MMKV/wiki/home)
中文版本请参看[这里](./readme_cn.md)
MMKV is an **efficient**, **small**, **easy-to-use** mobile key-value storage framework used in the WeChat application. It's currently available on **Android**, **iOS/macOS**, **Win32** and **POSIX**.
# MMKV for Android
## Features
* **Efficient**. MMKV uses mmap to keep memory synced with file, and protobuf to encode/decode values, making the most of Android to achieve best performance.
* **Multi-Process concurrency**: MMKV supports concurrent read-read and read-write access between processes.
* **Easy-to-use**. You can use MMKV as you go. All changes are saved immediately, no `sync`, no `apply` calls needed.
* **Small**.
* **A handful of files**: MMKV contains process locks, encode/decode helpers and mmap logics and nothing more. It's really tidy.
* **About 50K in binary size**: MMKV adds about 50K per architecture on App size, and much less when zipped (apk).
## Getting Started
### Installation Via Maven
Add the following lines to `build.gradle` on your app module:
```gradle
dependencies {
implementation 'com.tencent:mmkv-static:1.2.1'
// replace "1.2.1" with any available version
}
```
For other installation options, see [Android Setup](https://github.com/Tencent/MMKV/wiki/android_setup).
### Quick Tutorial
You can use MMKV as you go. All changes are saved immediately, no `sync`, no `apply` calls needed.
Setup MMKV on App startup, say your `Application` class, add these lines:
```Java
public void onCreate() {
super.onCreate();
String rootDir = MMKV.initialize(this);
System.out.println("mmkv root: " + rootDir);
//……
}
```
MMKV has a global instance, that can be used directly:
```Java
import com.tencent.mmkv.MMKV;
MMKV kv = MMKV.defaultMMKV();
kv.encode("bool", true);
boolean bValue = kv.decodeBool("bool");
kv.encode("int", Integer.MIN_VALUE);
int iValue = kv.decodeInt("int");
kv.encode("string", "Hello from mmkv");
String str = kv.decodeString("string");
```
MMKV also supports **Multi-Process Access**. Full tutorials can be found here [Android Tutorial](https://github.com/Tencent/MMKV/wiki/android_tutorial).
## Performance
Writing random `int` for 1000 times, we get this chart:
![](https://github.com/Tencent/MMKV/wiki/assets/profile_android_mini.png)
For more benchmark data, please refer to [our benchmark](https://github.com/Tencent/MMKV/wiki/android_benchmark).
# MMKV for iOS/macOS
## Features
* **Efficient**. MMKV uses mmap to keep memory synced with file, and protobuf to encode/decode values, making the most of iOS/macOS to achieve best performance.
* **Easy-to-use**. You can use MMKV as you go, no configurations needed. All changes are saved immediately, no `synchronize` calls needed.
* **Small**.
* **A handful of files**: MMKV contains encode/decode helpers and mmap logics and nothing more. It's really tidy.
* **Less than 30K in binary size**: MMKV adds less than 30K per architecture on App size, and much less when zipped (ipa).
## Getting Started
### Installation Via CocoaPods:
1. Install [CocoaPods](https://guides.CocoaPods.org/using/getting-started.html);
2. Open terminal, `cd` to your project directory, run `pod repo update` to make CocoaPods aware of the latest available MMKV versions;
3. Edit your Podfile, add `pod 'MMKV'` to your app target;
4. Run `pod install`;
5. Open the `.xcworkspace` file generated by CocoaPods;
6. Add `#import <MMKV/MMKV.h>` to your source file and we are done.
For other installation options, see [iOS/macOS Setup](https://github.com/Tencent/MMKV/wiki/iOS_setup).
### Quick Tutorial
You can use MMKV as you go, no configurations needed. All changes are saved immediately, no `synchronize` calls needed.
Setup MMKV on App startup, in your `-[MyApp application: didFinishLaunchingWithOptions:]`, add these lines:
```objective-c
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// init MMKV in the main thread
[MMKV initializeMMKV:nil];
//...
return YES;
}
```
MMKV has a global instance, that can be used directly:
```objective-c
MMKV *mmkv = [MMKV defaultMMKV];
[mmkv setBool:YES forKey:@"bool"];
BOOL bValue = [mmkv getBoolForKey:@"bool"];
[mmkv setInt32:-1024 forKey:@"int32"];
int32_t iValue = [mmkv getInt32ForKey:@"int32"];
[mmkv setString:@"hello, mmkv" forKey:@"string"];
NSString *str = [mmkv getStringForKey:@"string"];
```
MMKV also supports **Multi-Process Access**. Full tutorials can be found [here](https://github.com/Tencent/MMKV/wiki/iOS_tutorial).
## Performance
Writing random `int` for 10000 times, we get this chart:
![](https://github.com/Tencent/MMKV/wiki/assets/profile_mini.png)
For more benchmark data, please refer to [our benchmark](https://github.com/Tencent/MMKV/wiki/iOS_benchmark).
# MMKV for Win32
## Features
* **Efficient**. MMKV uses mmap to keep memory synced with file, and protobuf to encode/decode values, making the most of Windows to achieve best performance.
* **Multi-Process concurrency**: MMKV supports concurrent read-read and read-write access between processes.
* **Easy-to-use**. You can use MMKV as you go. All changes are saved immediately, no `save`, no `sync` calls needed.
* **Small**.
* **A handful of files**: MMKV contains process locks, encode/decode helpers and mmap logics and nothing more. It's really tidy.
* **About 10K in binary size**: MMKV adds about 10K on application size, and much less when zipped.
## Getting Started
### Installation Via Source
1. Getting source code from git repository:
```
git clone https://github.com/Tencent/MMKV.git
```
2. Add `Win32/MMKV/MMKV.vcxproj` to your solution;
3. Add `MMKV` project to your project's dependencies;
4. Add `$(OutDir)include` to your project's `C/C++` -> `General` -> `Additional Include Directories`;
5. Add `$(OutDir)` to your project's `Linker` -> `General` -> `Additional Library Directories`;
6. Add `MMKV.lib` to your project's `Linker` -> `Input` -> `Additional Dependencies`;
7. Add `#include <MMKV/MMKV.h>` to your source file and we are done.
note:
1. MMKV is compiled with `MT/MTd` runtime by default. If your project uses `MD/MDd`, you should change MMKV's setting to match your project's (`C/C++` -> `Code Generation` -> `Runtime Library`), or vise versa.
2. MMKV is developed with Visual Studio 2017, change the `Platform Toolset` if you use a different version of Visual Studio.
For other installation options, see [Win32 Setup](https://github.com/Tencent/MMKV/wiki/windows_setup).
### Quick Tutorial
You can use MMKV as you go. All changes are saved immediately, no `sync`, no `save` calls needed.
Setup MMKV on App startup, say in your `main()`, add these lines:
```C++
#include <MMKV/MMKV.h>
int main() {
std::wstring rootDir = getYourAppDocumentDir();
MMKV::initializeMMKV(rootDir);
//...
}
```
MMKV has a global instance, that can be used directly:
```C++
auto mmkv = MMKV::defaultMMKV();
mmkv->set(true, "bool");
std::cout << "bool = " << mmkv->getBool("bool") << std::endl;
mmkv->set(1024, "int32");
std::cout << "int32 = " << mmkv->getInt32("int32") << std::endl;
mmkv->set("Hello, MMKV for Win32", "string");
std::string result;
mmkv->getString("string", result);
std::cout << "string = " << result << std::endl;
```
MMKV also supports **Multi-Process Access**. Full tutorials can be found here [Win32 Tutorial](https://github.com/Tencent/MMKV/wiki/windows_tutorial).
# MMKV for POSIX
## Features
* **Efficient**. MMKV uses mmap to keep memory synced with file, and protobuf to encode/decode values, making the most of POSIX to achieve best performance.
* **Multi-Process concurrency**: MMKV supports concurrent read-read and read-write access between processes.
* **Easy-to-use**. You can use MMKV as you go. All changes are saved immediately, no `save`, no `sync` calls needed.
* **Small**.
* **A handful of files**: MMKV contains process locks, encode/decode helpers and mmap logics and nothing more. It's really tidy.
* **About 7K in binary size**: MMKV adds about 7K on application size, and much less when zipped.
## Getting Started
### Installation Via CMake
1. Getting source code from git repository:
```
git clone https://github.com/Tencent/MMKV.git
```
2. Edit your `CMakeLists.txt`, add those lines:
```cmake
add_subdirectory(mmkv/POSIX/src mmkv)
target_link_libraries(MyApp
mmkv)
```
3. Add `#include "MMKV.h"` to your source file and we are done.
For other installation options, see [POSIX Setup](https://github.com/Tencent/MMKV/wiki/posix_setup).
### Quick Tutorial
You can use MMKV as you go. All changes are saved immediately, no `sync`, no `save` calls needed.
Setup MMKV on App startup, say in your `main()`, add these lines:
```C++
#include "MMKV.h"
int main() {
std::string rootDir = getYourAppDocumentDir();
MMKV::initializeMMKV(rootDir);
//...
}
```
MMKV has a global instance, that can be used directly:
```C++
auto mmkv = MMKV::defaultMMKV();
mmkv->set(true, "bool");
std::cout << "bool = " << mmkv->getBool("bool") << std::endl;
mmkv->set(1024, "int32");
std::cout << "int32 = " << mmkv->getInt32("int32") << std::endl;
mmkv->set("Hello, MMKV for Win32", "string");
std::string result;
mmkv->getString("string", result);
std::cout << "string = " << result << std::endl;
```
MMKV also supports **Multi-Process Access**. Full tutorials can be found here [POSIX Tutorial](https://github.com/Tencent/MMKV/wiki/posix_tutorial).
## License
MMKV is published under the BSD 3-Clause license. For details check out the [LICENSE.TXT](./LICENSE.TXT).
## Change Log
Check out the [CHANGELOG.md](./CHANGELOG.md) for details of change history.
## Contributing
If you are interested in contributing, check out the [CONTRIBUTING.md](./CONTRIBUTING.md), also join our [Tencent OpenSource Plan](https://opensource.tencent.com/contribution).
To give clarity of what is expected of our members, MMKV has adopted the code of conduct defined by the Contributor Covenant, which is widely used. And we think it articulates our values well. For more, check out the [Code of Conduct](./CODE_OF_CONDUCT.md).
## FAQ & Feedback
Check out the [FAQ](https://github.com/Tencent/MMKV/wiki/FAQ) first. Should there be any questions, don't hesitate to create [issues](https://github.com/Tencent/MMKV/issues).

222
ios/Pods/MMKV/iOS/MMKV/MMKV/MMKV.h generated Normal file
View File

@ -0,0 +1,222 @@
/*
* Tencent is pleased to support the open source community by making
* MMKV available.
*
* Copyright (C) 2018 THL A29 Limited, a Tencent company.
* All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License"); you may not use
* this file except in compliance with the License. You may obtain a copy of
* the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "MMKVHandler.h"
typedef NS_ENUM(NSUInteger, MMKVMode) {
MMKVSingleProcess = 0x1,
MMKVMultiProcess = 0x2,
};
@interface MMKV : NSObject
NS_ASSUME_NONNULL_BEGIN
/// call this in main thread, before calling any other MMKV methods
/// @param rootDir the root dir of MMKV, passing nil defaults to {NSDocumentDirectory}/mmkv
/// @return root dir of MMKV
+ (NSString *)initializeMMKV:(nullable NSString *)rootDir NS_SWIFT_NAME(initialize(rootDir:));
/// call this in main thread, before calling any other MMKV methods
/// @param rootDir the root dir of MMKV, passing nil defaults to {NSDocumentDirectory}/mmkv
/// @param logLevel MMKVLogInfo by default, MMKVLogNone to disable all logging
/// @return root dir of MMKV
+ (NSString *)initializeMMKV:(nullable NSString *)rootDir logLevel:(MMKVLogLevel)logLevel NS_SWIFT_NAME(initialize(rootDir:logLevel:));
/// call this in main thread, before calling any other MMKV methods
/// @param rootDir the root dir of MMKV, passing nil defaults to {NSDocumentDirectory}/mmkv
/// @param groupDir the root dir of multi-process MMKV, MMKV with MMKVMultiProcess mode will be stored in groupDir/mmkv
/// @param logLevel MMKVLogInfo by default, MMKVLogNone to disable all logging
/// @return root dir of MMKV
+ (NSString *)initializeMMKV:(nullable NSString *)rootDir groupDir:(NSString *)groupDir logLevel:(MMKVLogLevel)logLevel NS_SWIFT_NAME(initialize(rootDir:groupDir:logLevel:));
/// a generic purpose instance (in MMKVSingleProcess mode)
+ (nullable instancetype)defaultMMKV;
/// @param mmapID any unique ID (com.tencent.xin.pay, etc), if you want a per-user mmkv, you could merge user-id within mmapID
+ (nullable instancetype)mmkvWithID:(NSString *)mmapID NS_SWIFT_NAME(init(mmapID:));
/// @param mmapID any unique ID (com.tencent.xin.pay, etc), if you want a per-user mmkv, you could merge user-id within mmapID
/// @param mode MMKVMultiProcess for multi-process MMKV
+ (nullable instancetype)mmkvWithID:(NSString *)mmapID mode:(MMKVMode)mode NS_SWIFT_NAME(init(mmapID:mode:));
/// @param mmapID any unique ID (com.tencent.xin.pay, etc), if you want a per-user mmkv, you could merge user-id within mmapID
/// @param cryptKey 16 bytes at most
+ (nullable instancetype)mmkvWithID:(NSString *)mmapID cryptKey:(nullable NSData *)cryptKey NS_SWIFT_NAME(init(mmapID:cryptKey:));
/// @param mmapID any unique ID (com.tencent.xin.pay, etc), if you want a per-user mmkv, you could merge user-id within mmapID
/// @param cryptKey 16 bytes at most
/// @param mode MMKVMultiProcess for multi-process MMKV
+ (nullable instancetype)mmkvWithID:(NSString *)mmapID cryptKey:(nullable NSData *)cryptKey mode:(MMKVMode)mode NS_SWIFT_NAME(init(mmapID:cryptKey:mode:));
/// @param mmapID any unique ID (com.tencent.xin.pay, etc), if you want a per-user mmkv, you could merge user-id within mmapID
/// @param relativePath custom path of the file, `NSDocumentDirectory/mmkv` by default
+ (nullable instancetype)mmkvWithID:(NSString *)mmapID relativePath:(nullable NSString *)relativePath NS_SWIFT_NAME(init(mmapID:relativePath:)) __attribute__((deprecated("use +mmkvWithID:rootPath: instead")));
/// @param mmapID any unique ID (com.tencent.xin.pay, etc), if you want a per-user mmkv, you could merge user-id within mmapID
/// @param rootPath custom path of the file, `NSDocumentDirectory/mmkv` by default
+ (nullable instancetype)mmkvWithID:(NSString *)mmapID rootPath:(nullable NSString *)rootPath NS_SWIFT_NAME(init(mmapID:rootPath:));
/// @param mmapID any unique ID (com.tencent.xin.pay, etc), if you want a per-user mmkv, you could merge user-id within mmapID
/// @param cryptKey 16 bytes at most
/// @param relativePath custom path of the file, `NSDocumentDirectory/mmkv` by default
+ (nullable instancetype)mmkvWithID:(NSString *)mmapID cryptKey:(nullable NSData *)cryptKey relativePath:(nullable NSString *)relativePath NS_SWIFT_NAME(init(mmapID:cryptKey:relativePath:)) __attribute__((deprecated("use +mmkvWithID:cryptKey:rootPath: instead")));
/// @param mmapID any unique ID (com.tencent.xin.pay, etc), if you want a per-user mmkv, you could merge user-id within mmapID
/// @param cryptKey 16 bytes at most
/// @param rootPath custom path of the file, `NSDocumentDirectory/mmkv` by default
+ (nullable instancetype)mmkvWithID:(NSString *)mmapID cryptKey:(nullable NSData *)cryptKey rootPath:(nullable NSString *)rootPath NS_SWIFT_NAME(init(mmapID:cryptKey:rootPath:));
// you can call this on applicationWillTerminate, it's totally fine if you don't call
+ (void)onAppTerminate;
+ (NSString *)mmkvBasePath;
// if you want to change the base path, do it BEFORE getting any MMKV instance
// otherwise the behavior is undefined
+ (void)setMMKVBasePath:(NSString *)basePath __attribute__((deprecated("use +initializeMMKV: instead", "+initializeMMKV:")));
// transform plain text into encrypted text, or vice versa by passing newKey = nil
// you can change existing crypt key with different key
- (BOOL)reKey:(nullable NSData *)newKey NS_SWIFT_NAME(reset(cryptKey:));
- (nullable NSData *)cryptKey;
// just reset cryptKey (will not encrypt or decrypt anything)
// usually you should call this method after other process reKey() the multi-process mmkv
- (void)checkReSetCryptKey:(nullable NSData *)cryptKey NS_SWIFT_NAME(checkReSet(cryptKey:));
- (BOOL)setObject:(nullable NSObject<NSCoding> *)object forKey:(NSString *)key NS_SWIFT_NAME(set(_:forKey:));
- (BOOL)setBool:(BOOL)value forKey:(NSString *)key NS_SWIFT_NAME(set(_:forKey:));
- (BOOL)setInt32:(int32_t)value forKey:(NSString *)key NS_SWIFT_NAME(set(_:forKey:));
- (BOOL)setUInt32:(uint32_t)value forKey:(NSString *)key NS_SWIFT_NAME(set(_:forKey:));
- (BOOL)setInt64:(int64_t)value forKey:(NSString *)key NS_SWIFT_NAME(set(_:forKey:));
- (BOOL)setUInt64:(uint64_t)value forKey:(NSString *)key NS_SWIFT_NAME(set(_:forKey:));
- (BOOL)setFloat:(float)value forKey:(NSString *)key NS_SWIFT_NAME(set(_:forKey:));
- (BOOL)setDouble:(double)value forKey:(NSString *)key NS_SWIFT_NAME(set(_:forKey:));
- (BOOL)setString:(NSString *)value forKey:(NSString *)key NS_SWIFT_NAME(set(_:forKey:));
- (BOOL)setDate:(NSDate *)value forKey:(NSString *)key NS_SWIFT_NAME(set(_:forKey:));
- (BOOL)setData:(NSData *)value forKey:(NSString *)key NS_SWIFT_NAME(set(_:forKey:));
- (nullable id)getObjectOfClass:(Class)cls forKey:(NSString *)key NS_SWIFT_NAME(object(of:forKey:));
- (BOOL)getBoolForKey:(NSString *)key __attribute__((swift_name("bool(forKey:)")));
- (BOOL)getBoolForKey:(NSString *)key defaultValue:(BOOL)defaultValue __attribute__((swift_name("bool(forKey:defaultValue:)")));
- (int32_t)getInt32ForKey:(NSString *)key NS_SWIFT_NAME(int32(forKey:));
- (int32_t)getInt32ForKey:(NSString *)key defaultValue:(int32_t)defaultValue NS_SWIFT_NAME(int32(forKey:defaultValue:));
- (uint32_t)getUInt32ForKey:(NSString *)key NS_SWIFT_NAME(uint32(forKey:));
- (uint32_t)getUInt32ForKey:(NSString *)key defaultValue:(uint32_t)defaultValue NS_SWIFT_NAME(uint32(forKey:defaultValue:));
- (int64_t)getInt64ForKey:(NSString *)key NS_SWIFT_NAME(int64(forKey:));
- (int64_t)getInt64ForKey:(NSString *)key defaultValue:(int64_t)defaultValue NS_SWIFT_NAME(int64(forKey:defaultValue:));
- (uint64_t)getUInt64ForKey:(NSString *)key NS_SWIFT_NAME(uint64(forKey:));
- (uint64_t)getUInt64ForKey:(NSString *)key defaultValue:(uint64_t)defaultValue NS_SWIFT_NAME(uint64(forKey:defaultValue:));
- (float)getFloatForKey:(NSString *)key NS_SWIFT_NAME(float(forKey:));
- (float)getFloatForKey:(NSString *)key defaultValue:(float)defaultValue NS_SWIFT_NAME(float(forKey:defaultValue:));
- (double)getDoubleForKey:(NSString *)key NS_SWIFT_NAME(double(forKey:));
- (double)getDoubleForKey:(NSString *)key defaultValue:(double)defaultValue NS_SWIFT_NAME(double(forKey:defaultValue:));
- (nullable NSString *)getStringForKey:(NSString *)key NS_SWIFT_NAME(string(forKey:));
- (nullable NSString *)getStringForKey:(NSString *)key defaultValue:(nullable NSString *)defaultValue NS_SWIFT_NAME(string(forKey:defaultValue:));
- (nullable NSDate *)getDateForKey:(NSString *)key NS_SWIFT_NAME(date(forKey:));
- (nullable NSDate *)getDateForKey:(NSString *)key defaultValue:(nullable NSDate *)defaultValue NS_SWIFT_NAME(date(forKey:defaultValue:));
- (nullable NSData *)getDataForKey:(NSString *)key NS_SWIFT_NAME(data(forKey:));
- (nullable NSData *)getDataForKey:(NSString *)key defaultValue:(nullable NSData *)defaultValue NS_SWIFT_NAME(data(forKey:defaultValue:));
// return the actual size consumption of the key's value
// Note: might be a little bigger than value's length
- (size_t)getValueSizeForKey:(NSString *)key NS_SWIFT_NAME(valueSize(forKey:));
// return size written into buffer
// return -1 on any error
- (int32_t)writeValueForKey:(NSString *)key toBuffer:(NSMutableData *)buffer NS_SWIFT_NAME(writeValue(forKey:buffer:));
- (BOOL)containsKey:(NSString *)key NS_SWIFT_NAME(contains(key:));
- (size_t)count;
- (size_t)totalSize;
- (size_t)actualSize;
- (void)enumerateKeys:(void (^)(NSString *key, BOOL *stop))block;
- (NSArray *)allKeys;
- (void)removeValueForKey:(NSString *)key NS_SWIFT_NAME(removeValue(forKey:));
- (void)removeValuesForKeys:(NSArray<NSString *> *)arrKeys NS_SWIFT_NAME(removeValues(forKeys:));
- (void)clearAll;
// MMKV's size won't reduce after deleting key-values
// call this method after lots of deleting if you care about disk usage
// note that `clearAll` has the similar effect of `trim`
- (void)trim;
// call this method if the instance is no longer needed in the near future
// any subsequent call to the instance is undefined behavior
- (void)close;
// call this method if you are facing memory-warning
// any subsequent call to the instance will load all key-values from file again
- (void)clearMemoryCache;
// you don't need to call this, really, I mean it
// unless you worry about running out of battery
- (void)sync;
- (void)async;
// check if content changed by other process
- (void)checkContentChanged;
+ (void)registerHandler:(id<MMKVHandler>)handler;
+ (void)unregiserHandler;
// MMKVLogInfo by default
// MMKVLogNone to disable all logging
+ (void)setLogLevel:(MMKVLogLevel)logLevel __attribute__((deprecated("use +initializeMMKV:logLevel: instead", "initializeMMKV:nil logLevel")));
// Migrate NSUserDefault data to MMKV
// return imported count of key-values
- (uint32_t)migrateFromUserDefaults:(NSUserDefaults *)userDaults NS_SWIFT_NAME(migrateFrom(userDefaults:));
// for CrashProtected Only
+ (BOOL)isFileValid:(NSString *)mmapID NS_SWIFT_NAME(isFileValid(for:));
+ (BOOL)isFileValid:(NSString *)mmapID rootPath:(nullable NSString *)path NS_SWIFT_NAME(isFileValid(for:rootPath:));
NS_ASSUME_NONNULL_END
@end

View File

@ -0,0 +1,60 @@
/*
* Tencent is pleased to support the open source community by making
* MMKV available.
*
* Copyright (C) 2018 THL A29 Limited, a Tencent company.
* All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License"); you may not use
* this file except in compliance with the License. You may obtain a copy of
* the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef MMKVHandler_h
#define MMKVHandler_h
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSUInteger, MMKVRecoverStrategic) {
MMKVOnErrorDiscard = 0,
MMKVOnErrorRecover,
};
typedef NS_ENUM(NSUInteger, MMKVLogLevel) {
MMKVLogDebug = 0, // not available for release/product build
MMKVLogInfo = 1, // default level
MMKVLogWarning,
MMKVLogError,
MMKVLogNone, // special level used to disable all log messages
};
// callback is called on the operating thread of the MMKV instance
@protocol MMKVHandler <NSObject>
@optional
// by default MMKV will discard all datas on crc32-check failure
// return `MMKVOnErrorRecover` to recover any data on the file
- (MMKVRecoverStrategic)onMMKVCRCCheckFail:(NSString *)mmapID;
// by default MMKV will discard all datas on file length mismatch
// return `MMKVOnErrorRecover` to recover any data on the file
- (MMKVRecoverStrategic)onMMKVFileLengthError:(NSString *)mmapID;
// by default MMKV will print log using NSLog
// implement this method to redirect MMKV's log
- (void)mmkvLogWithLevel:(MMKVLogLevel)level file:(const char *)file line:(int)line func:(const char *)funcname message:(NSString *)message;
// called when content is changed by other process
// doesn't guarantee real-time notification
- (void)onMMKVContentChange:(NSString *)mmapID;
@end
#endif /* MMKVHandler_h */

699
ios/Pods/MMKV/iOS/MMKV/MMKV/libMMKV.mm generated Normal file
View File

@ -0,0 +1,699 @@
/*
* Tencent is pleased to support the open source community by making
* MMKV available.
*
* Copyright (C) 2020 THL A29 Limited, a Tencent company.
* All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License"); you may not use
* this file except in compliance with the License. You may obtain a copy of
* the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "MMKV.h"
#import <MMKVCore/MMKV.h>
#import <MMKVCore/MMKVLog.h>
#import <MMKVCore/ScopedLock.hpp>
#import <MMKVCore/ThreadLock.h>
#import <MMKVCore/openssl_md5.h>
#if defined(MMKV_IOS) && !defined(MMKV_IOS_EXTENSION)
#import <UIKit/UIKit.h>
#endif
using namespace std;
static NSMutableDictionary *g_instanceDic = nil;
static mmkv::ThreadLock *g_lock;
static id<MMKVHandler> g_callbackHandler = nil;
static bool g_isLogRedirecting = false;
static NSString *g_basePath = nil;
static NSString *g_groupPath = nil;
#if defined(MMKV_IOS) && !defined(MMKV_IOS_EXTENSION)
static BOOL g_isRunningInAppExtension = NO;
#endif
static void LogHandler(mmkv::MMKVLogLevel level, const char *file, int line, const char *function, NSString *message);
static mmkv::MMKVRecoverStrategic ErrorHandler(const string &mmapID, mmkv::MMKVErrorType errorType);
static void ContentChangeHandler(const string &mmapID);
@implementation MMKV {
NSString *m_mmapID;
NSString *m_mmapKey;
mmkv::MMKV *m_mmkv;
}
#pragma mark - init
// protect from some old code that don't call +initializeMMKV:
+ (void)initialize {
if (self == MMKV.class) {
g_instanceDic = [[NSMutableDictionary alloc] init];
g_lock = new mmkv::ThreadLock();
g_lock->initialize();
mmkv::MMKV::minimalInit([self mmkvBasePath].UTF8String);
#if defined(MMKV_IOS) && !defined(MMKV_IOS_EXTENSION)
// just in case someone forget to set the MMKV_IOS_EXTENSION macro
if ([[[NSBundle mainBundle] bundlePath] hasSuffix:@".appex"]) {
g_isRunningInAppExtension = YES;
}
if (!g_isRunningInAppExtension) {
auto appState = [UIApplication sharedApplication].applicationState;
auto isInBackground = (appState == UIApplicationStateBackground);
mmkv::MMKV::setIsInBackground(isInBackground);
MMKVInfo("appState:%ld", (long) appState);
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didEnterBackground) name:UIApplicationDidEnterBackgroundNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didBecomeActive) name:UIApplicationDidBecomeActiveNotification object:nil];
}
#endif
}
}
+ (NSString *)initializeMMKV:(nullable NSString *)rootDir {
return [MMKV initializeMMKV:rootDir logLevel:MMKVLogInfo];
}
static BOOL g_hasCalledInitializeMMKV = NO;
+ (NSString *)initializeMMKV:(nullable NSString *)rootDir logLevel:(MMKVLogLevel)logLevel {
if (g_hasCalledInitializeMMKV) {
MMKVWarning("already called +initializeMMKV before, ignore this request");
return [self mmkvBasePath];
}
g_hasCalledInitializeMMKV = YES;
g_basePath = (rootDir != nil) ? rootDir : [self mmkvBasePath];
mmkv::MMKV::initializeMMKV(g_basePath.UTF8String, (mmkv::MMKVLogLevel) logLevel);
return [self mmkvBasePath];
}
+ (NSString *)initializeMMKV:(nullable NSString *)rootDir groupDir:(NSString *)groupDir logLevel:(MMKVLogLevel)logLevel {
auto ret = [MMKV initializeMMKV:rootDir logLevel:logLevel];
g_groupPath = [groupDir stringByAppendingPathComponent:@"mmkv"];
MMKVInfo("groupDir: %@", g_groupPath);
return ret;
}
// a generic purpose instance
+ (instancetype)defaultMMKV {
return [MMKV mmkvWithID:(@"" DEFAULT_MMAP_ID) cryptKey:nil rootPath:nil mode:MMKVSingleProcess];
}
// any unique ID (com.tencent.xin.pay, etc)
+ (instancetype)mmkvWithID:(NSString *)mmapID {
return [MMKV mmkvWithID:mmapID cryptKey:nil rootPath:nil mode:MMKVSingleProcess];
}
+ (instancetype)mmkvWithID:(NSString *)mmapID mode:(MMKVMode)mode {
auto rootPath = (mode == MMKVSingleProcess) ? nil : g_groupPath;
return [MMKV mmkvWithID:mmapID cryptKey:nil rootPath:rootPath mode:mode];
}
+ (instancetype)mmkvWithID:(NSString *)mmapID cryptKey:(NSData *)cryptKey {
return [MMKV mmkvWithID:mmapID cryptKey:cryptKey rootPath:nil mode:MMKVSingleProcess];
}
+ (instancetype)mmkvWithID:(NSString *)mmapID cryptKey:(nullable NSData *)cryptKey mode:(MMKVMode)mode {
auto rootPath = (mode == MMKVSingleProcess) ? nil : g_groupPath;
return [MMKV mmkvWithID:mmapID cryptKey:cryptKey rootPath:rootPath mode:mode];
}
+ (instancetype)mmkvWithID:(NSString *)mmapID rootPath:(nullable NSString *)rootPath {
return [MMKV mmkvWithID:mmapID cryptKey:nil rootPath:rootPath mode:MMKVSingleProcess];
}
+ (instancetype)mmkvWithID:(NSString *)mmapID relativePath:(nullable NSString *)relativePath {
return [MMKV mmkvWithID:mmapID cryptKey:nil rootPath:relativePath mode:MMKVSingleProcess];
}
+ (instancetype)mmkvWithID:(NSString *)mmapID cryptKey:(NSData *)cryptKey rootPath:(nullable NSString *)rootPath {
return [MMKV mmkvWithID:mmapID cryptKey:cryptKey rootPath:rootPath mode:MMKVSingleProcess];
}
+ (instancetype)mmkvWithID:(NSString *)mmapID cryptKey:(nullable NSData *)cryptKey relativePath:(nullable NSString *)relativePath {
return [MMKV mmkvWithID:mmapID cryptKey:cryptKey rootPath:relativePath mode:MMKVSingleProcess];
}
// relatePath and MMKVMultiProcess mode can't be set at the same time, so we hide this method from public
+ (instancetype)mmkvWithID:(NSString *)mmapID cryptKey:(NSData *)cryptKey rootPath:(nullable NSString *)rootPath mode:(MMKVMode)mode {
if (!g_hasCalledInitializeMMKV) {
MMKVError("MMKV not initialized properly, must call +initializeMMKV: in main thread before calling any other MMKV methods");
}
if (mmapID.length <= 0) {
return nil;
}
SCOPED_LOCK(g_lock);
if (mode == MMKVMultiProcess) {
if (!rootPath) {
rootPath = g_groupPath;
}
if (!rootPath) {
MMKVError("Getting a multi-process MMKV [%@] without setting groupDir makes no sense", mmapID);
MMKV_ASSERT(0);
}
}
NSString *kvKey = [MMKV mmapKeyWithMMapID:mmapID rootPath:rootPath];
MMKV *kv = [g_instanceDic objectForKey:kvKey];
if (kv == nil) {
kv = [[MMKV alloc] initWithMMapID:mmapID cryptKey:cryptKey rootPath:rootPath mode:mode];
if (!kv->m_mmkv) {
return nil;
}
kv->m_mmapKey = kvKey;
[g_instanceDic setObject:kv forKey:kvKey];
}
return kv;
}
- (instancetype)initWithMMapID:(NSString *)mmapID cryptKey:(NSData *)cryptKey rootPath:(NSString *)rootPath mode:(MMKVMode)mode {
if (self = [super init]) {
string pathTmp;
if (rootPath.length > 0) {
pathTmp = rootPath.UTF8String;
}
string cryptKeyTmp;
if (cryptKey.length > 0) {
cryptKeyTmp = string((char *) cryptKey.bytes, cryptKey.length);
}
string *rootPathPtr = pathTmp.empty() ? nullptr : &pathTmp;
string *cryptKeyPtr = cryptKeyTmp.empty() ? nullptr : &cryptKeyTmp;
m_mmkv = mmkv::MMKV::mmkvWithID(mmapID.UTF8String, (mmkv::MMKVMode) mode, cryptKeyPtr, rootPathPtr);
if (!m_mmkv) {
return self;
}
m_mmapID = [NSString stringWithUTF8String:m_mmkv->mmapID().c_str()];
#if defined(MMKV_IOS) && !defined(MMKV_IOS_EXTENSION)
if (!g_isRunningInAppExtension) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(onMemoryWarning)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
}
#endif
}
return self;
}
- (void)dealloc {
[self clearMemoryCache];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - Application state
#if defined(MMKV_IOS) && !defined(MMKV_IOS_EXTENSION)
- (void)onMemoryWarning {
MMKVInfo("cleaning on memory warning %@", m_mmapID);
[self clearMemoryCache];
}
+ (void)didEnterBackground {
mmkv::MMKV::setIsInBackground(true);
MMKVInfo("isInBackground:%d", true);
}
+ (void)didBecomeActive {
mmkv::MMKV::setIsInBackground(false);
MMKVInfo("isInBackground:%d", false);
}
#endif
- (void)clearAll {
m_mmkv->clearAll();
}
- (void)clearMemoryCache {
if (m_mmkv) {
m_mmkv->clearMemoryCache();
}
}
- (void)close {
SCOPED_LOCK(g_lock);
MMKVInfo("closing %@", m_mmapID);
m_mmkv->close();
m_mmkv = nullptr;
[g_instanceDic removeObjectForKey:m_mmapKey];
}
- (void)trim {
m_mmkv->trim();
}
#pragma mark - encryption & decryption
#ifndef MMKV_DISABLE_CRYPT
- (nullable NSData *)cryptKey {
auto str = m_mmkv->cryptKey();
if (str.length() > 0) {
return [NSData dataWithBytes:str.data() length:str.length()];
}
return nil;
}
- (BOOL)reKey:(nullable NSData *)newKey {
string key;
if (newKey.length > 0) {
key = string((char *) newKey.bytes, newKey.length);
}
return m_mmkv->reKey(key);
}
- (void)checkReSetCryptKey:(nullable NSData *)cryptKey {
if (cryptKey.length > 0) {
string key = string((char *) cryptKey.bytes, cryptKey.length);
m_mmkv->checkReSetCryptKey(&key);
} else {
m_mmkv->checkReSetCryptKey(nullptr);
}
}
#else
- (nullable NSData *)cryptKey {
return nil;
}
- (BOOL)reKey:(nullable NSData *)newKey {
return NO;
}
- (void)checkReSetCryptKey:(nullable NSData *)cryptKey {
}
#endif // MMKV_DISABLE_CRYPT
#pragma mark - set & get
- (BOOL)setObject:(nullable NSObject<NSCoding> *)object forKey:(NSString *)key {
return m_mmkv->set(object, key);
}
- (BOOL)setBool:(BOOL)value forKey:(NSString *)key {
return m_mmkv->set((bool) value, key);
}
- (BOOL)setInt32:(int32_t)value forKey:(NSString *)key {
return m_mmkv->set(value, key);
}
- (BOOL)setUInt32:(uint32_t)value forKey:(NSString *)key {
return m_mmkv->set(value, key);
}
- (BOOL)setInt64:(int64_t)value forKey:(NSString *)key {
return m_mmkv->set(value, key);
}
- (BOOL)setUInt64:(uint64_t)value forKey:(NSString *)key {
return m_mmkv->set(value, key);
}
- (BOOL)setFloat:(float)value forKey:(NSString *)key {
return m_mmkv->set(value, key);
}
- (BOOL)setDouble:(double)value forKey:(NSString *)key {
return m_mmkv->set(value, key);
}
- (BOOL)setString:(NSString *)value forKey:(NSString *)key {
return [self setObject:value forKey:key];
}
- (BOOL)setDate:(NSDate *)value forKey:(NSString *)key {
return [self setObject:value forKey:key];
}
- (BOOL)setData:(NSData *)value forKey:(NSString *)key {
return [self setObject:value forKey:key];
}
- (id)getObjectOfClass:(Class)cls forKey:(NSString *)key {
return m_mmkv->getObject(key, cls);
}
- (BOOL)getBoolForKey:(NSString *)key {
return [self getBoolForKey:key defaultValue:FALSE];
}
- (BOOL)getBoolForKey:(NSString *)key defaultValue:(BOOL)defaultValue {
return m_mmkv->getBool(key, defaultValue);
}
- (int32_t)getInt32ForKey:(NSString *)key {
return [self getInt32ForKey:key defaultValue:0];
}
- (int32_t)getInt32ForKey:(NSString *)key defaultValue:(int32_t)defaultValue {
return m_mmkv->getInt32(key, defaultValue);
}
- (uint32_t)getUInt32ForKey:(NSString *)key {
return [self getUInt32ForKey:key defaultValue:0];
}
- (uint32_t)getUInt32ForKey:(NSString *)key defaultValue:(uint32_t)defaultValue {
return m_mmkv->getUInt32(key, defaultValue);
}
- (int64_t)getInt64ForKey:(NSString *)key {
return [self getInt64ForKey:key defaultValue:0];
}
- (int64_t)getInt64ForKey:(NSString *)key defaultValue:(int64_t)defaultValue {
return m_mmkv->getInt64(key, defaultValue);
}
- (uint64_t)getUInt64ForKey:(NSString *)key {
return [self getUInt64ForKey:key defaultValue:0];
}
- (uint64_t)getUInt64ForKey:(NSString *)key defaultValue:(uint64_t)defaultValue {
return m_mmkv->getUInt64(key, defaultValue);
}
- (float)getFloatForKey:(NSString *)key {
return [self getFloatForKey:key defaultValue:0];
}
- (float)getFloatForKey:(NSString *)key defaultValue:(float)defaultValue {
return m_mmkv->getFloat(key, defaultValue);
}
- (double)getDoubleForKey:(NSString *)key {
return [self getDoubleForKey:key defaultValue:0];
}
- (double)getDoubleForKey:(NSString *)key defaultValue:(double)defaultValue {
return m_mmkv->getDouble(key, defaultValue);
}
- (nullable NSString *)getStringForKey:(NSString *)key {
return [self getStringForKey:key defaultValue:nil];
}
- (nullable NSString *)getStringForKey:(NSString *)key defaultValue:(nullable NSString *)defaultValue {
if (key.length <= 0) {
return defaultValue;
}
NSString *valueString = [self getObjectOfClass:NSString.class forKey:key];
if (!valueString) {
valueString = defaultValue;
}
return valueString;
}
- (nullable NSDate *)getDateForKey:(NSString *)key {
return [self getDateForKey:key defaultValue:nil];
}
- (nullable NSDate *)getDateForKey:(NSString *)key defaultValue:(nullable NSDate *)defaultValue {
if (key.length <= 0) {
return defaultValue;
}
NSDate *valueDate = [self getObjectOfClass:NSDate.class forKey:key];
if (!valueDate) {
valueDate = defaultValue;
}
return valueDate;
}
- (nullable NSData *)getDataForKey:(NSString *)key {
return [self getDataForKey:key defaultValue:nil];
}
- (nullable NSData *)getDataForKey:(NSString *)key defaultValue:(nullable NSData *)defaultValue {
if (key.length <= 0) {
return defaultValue;
}
NSData *valueData = [self getObjectOfClass:NSData.class forKey:key];
if (!valueData) {
valueData = defaultValue;
}
return valueData;
}
- (size_t)getValueSizeForKey:(NSString *)key {
return m_mmkv->getValueSize(key, false);
}
- (int32_t)writeValueForKey:(NSString *)key toBuffer:(NSMutableData *)buffer {
return m_mmkv->writeValueToBuffer(key, buffer.mutableBytes, static_cast<int32_t>(buffer.length));
}
#pragma mark - enumerate
- (BOOL)containsKey:(NSString *)key {
return m_mmkv->containsKey(key);
}
- (size_t)count {
return m_mmkv->count();
}
- (size_t)totalSize {
return m_mmkv->totalSize();
}
- (size_t)actualSize {
return m_mmkv->actualSize();
}
- (void)enumerateKeys:(void (^)(NSString *key, BOOL *stop))block {
m_mmkv->enumerateKeys(block);
}
- (NSArray *)allKeys {
return m_mmkv->allKeys();
}
- (void)removeValueForKey:(NSString *)key {
m_mmkv->removeValueForKey(key);
}
- (void)removeValuesForKeys:(NSArray *)arrKeys {
m_mmkv->removeValuesForKeys(arrKeys);
}
#pragma mark - Boring stuff
- (void)sync {
m_mmkv->sync(mmkv::MMKV_SYNC);
}
- (void)async {
m_mmkv->sync(mmkv::MMKV_ASYNC);
}
- (void)checkContentChanged {
m_mmkv->checkContentChanged();
}
+ (void)onAppTerminate {
SCOPED_LOCK(g_lock);
[g_instanceDic removeAllObjects];
mmkv::MMKV::onExit();
}
+ (NSString *)mmkvBasePath {
if (g_basePath.length > 0) {
return g_basePath;
}
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentPath = (NSString *) [paths firstObject];
if ([documentPath length] > 0) {
g_basePath = [documentPath stringByAppendingPathComponent:@"mmkv"];
return g_basePath;
} else {
return @"";
}
}
+ (void)setMMKVBasePath:(NSString *)basePath {
if (basePath.length > 0) {
g_basePath = basePath;
[MMKV initializeMMKV:basePath];
// still warn about it
g_hasCalledInitializeMMKV = NO;
MMKVInfo("set MMKV base path to: %@", g_basePath);
}
}
static NSString *md5(NSString *value) {
uint8_t md[MD5_DIGEST_LENGTH] = {};
char tmp[3] = {}, buf[33] = {};
auto data = [value dataUsingEncoding:NSUTF8StringEncoding];
openssl::MD5((uint8_t *) data.bytes, data.length, md);
for (auto ch : md) {
snprintf(tmp, sizeof(tmp), "%2.2x", ch);
strcat(buf, tmp);
}
return [NSString stringWithCString:buf encoding:NSASCIIStringEncoding];
}
+ (NSString *)mmapKeyWithMMapID:(NSString *)mmapID rootPath:(nullable NSString *)rootPath {
NSString *string = nil;
if ([rootPath length] > 0 && [rootPath isEqualToString:[MMKV mmkvBasePath]] == NO) {
string = md5([rootPath stringByAppendingPathComponent:mmapID]);
} else {
string = mmapID;
}
MMKVDebug("mmapKey: %@", string);
return string;
}
+ (BOOL)isFileValid:(NSString *)mmapID {
return [self isFileValid:mmapID rootPath:nil];
}
+ (BOOL)isFileValid:(NSString *)mmapID rootPath:(nullable NSString *)path {
if (mmapID.length > 0) {
if (path.length > 0) {
string rootPath(path.UTF8String);
return mmkv::MMKV::isFileValid(mmapID.UTF8String, &rootPath);
} else {
return mmkv::MMKV::isFileValid(mmapID.UTF8String, nullptr);
}
}
return NO;
}
+ (void)registerHandler:(id<MMKVHandler>)handler {
SCOPED_LOCK(g_lock);
g_callbackHandler = handler;
if ([g_callbackHandler respondsToSelector:@selector(mmkvLogWithLevel:file:line:func:message:)]) {
g_isLogRedirecting = true;
mmkv::MMKV::registerLogHandler(LogHandler);
}
if ([g_callbackHandler respondsToSelector:@selector(onMMKVCRCCheckFail:)] ||
[g_callbackHandler respondsToSelector:@selector(onMMKVFileLengthError:)]) {
mmkv::MMKV::registerErrorHandler(ErrorHandler);
}
if ([g_callbackHandler respondsToSelector:@selector(onMMKVContentChange:)]) {
mmkv::MMKV::registerContentChangeHandler(ContentChangeHandler);
}
}
+ (void)unregiserHandler {
SCOPED_LOCK(g_lock);
g_isLogRedirecting = false;
g_callbackHandler = nil;
mmkv::MMKV::unRegisterLogHandler();
mmkv::MMKV::unRegisterErrorHandler();
mmkv::MMKV::unRegisterContentChangeHandler();
}
+ (void)setLogLevel:(MMKVLogLevel)logLevel {
mmkv::MMKV::setLogLevel((mmkv::MMKVLogLevel) logLevel);
}
- (uint32_t)migrateFromUserDefaults:(NSUserDefaults *)userDaults {
NSDictionary *dic = [userDaults dictionaryRepresentation];
if (dic.count <= 0) {
MMKVInfo("migrate data fail, userDaults is nil or empty");
return 0;
}
__block uint32_t count = 0;
[dic enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL *_Nonnull stop) {
if ([key isKindOfClass:[NSString class]]) {
NSString *stringKey = key;
if ([MMKV tranlateData:obj key:stringKey kv:self]) {
count++;
}
} else {
MMKVWarning("unknown type of key:%@", key);
}
}];
return count;
}
+ (BOOL)tranlateData:(id)obj key:(NSString *)key kv:(MMKV *)kv {
if ([obj isKindOfClass:[NSString class]]) {
return [kv setString:obj forKey:key];
} else if ([obj isKindOfClass:[NSData class]]) {
return [kv setData:obj forKey:key];
} else if ([obj isKindOfClass:[NSDate class]]) {
return [kv setDate:obj forKey:key];
} else if ([obj isKindOfClass:[NSNumber class]]) {
NSNumber *num = obj;
CFNumberType numberType = CFNumberGetType((CFNumberRef) obj);
switch (numberType) {
case kCFNumberCharType:
case kCFNumberSInt8Type:
case kCFNumberSInt16Type:
case kCFNumberSInt32Type:
case kCFNumberIntType:
case kCFNumberShortType:
return [kv setInt32:num.intValue forKey:key];
case kCFNumberSInt64Type:
case kCFNumberLongType:
case kCFNumberNSIntegerType:
case kCFNumberLongLongType:
return [kv setInt64:num.longLongValue forKey:key];
case kCFNumberFloat32Type:
return [kv setFloat:num.floatValue forKey:key];
case kCFNumberFloat64Type:
case kCFNumberDoubleType:
return [kv setDouble:num.doubleValue forKey:key];
default:
MMKVWarning("unknown number type:%ld, key:%@", (long) numberType, key);
return NO;
}
} else if ([obj isKindOfClass:[NSArray class]] || [obj isKindOfClass:[NSDictionary class]]) {
return [kv setObject:obj forKey:key];
} else {
MMKVWarning("unknown type of key:%@", key);
}
return NO;
}
@end
#pragma makr - callbacks
static void LogHandler(mmkv::MMKVLogLevel level, const char *file, int line, const char *function, NSString *message) {
[g_callbackHandler mmkvLogWithLevel:(MMKVLogLevel) level file:file line:line func:function message:message];
}
static mmkv::MMKVRecoverStrategic ErrorHandler(const string &mmapID, mmkv::MMKVErrorType errorType) {
if (errorType == mmkv::MMKVCRCCheckFail) {
if ([g_callbackHandler respondsToSelector:@selector(onMMKVCRCCheckFail:)]) {
auto ret = [g_callbackHandler onMMKVCRCCheckFail:[NSString stringWithUTF8String:mmapID.c_str()]];
return (mmkv::MMKVRecoverStrategic) ret;
}
} else {
if ([g_callbackHandler respondsToSelector:@selector(onMMKVFileLengthError:)]) {
auto ret = [g_callbackHandler onMMKVFileLengthError:[NSString stringWithUTF8String:mmapID.c_str()]];
return (mmkv::MMKVRecoverStrategic) ret;
}
}
return mmkv::OnErrorDiscard;
}
static void ContentChangeHandler(const string &mmapID) {
if ([g_callbackHandler respondsToSelector:@selector(onMMKVContentChange:)]) {
[g_callbackHandler onMMKVContentChange:[NSString stringWithUTF8String:mmapID.c_str()]];
}
}

Some files were not shown because too many files have changed in this diff Show More