[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:
parent
6555687891
commit
e2f17a5a23
|
@ -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
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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';
|
|
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 });
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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(':', ''));
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 });
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 = () => {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -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|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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/).
|
|
|
@ -0,0 +1 @@
|
||||||
|
../../../MMKV/iOS/MMKV/MMKV/MMKV.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../MMKV/iOS/MMKV/MMKV/MMKVHandler.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../MMKVAppExtension/iOS/MMKV/MMKV/MMKV.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../MMKVAppExtension/iOS/MMKV/MMKV/MMKVHandler.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../MMKVCore/Core/aes/AESCrypt.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../MMKVCore/Core/crc32/Checksum.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../MMKVCore/Core/CodedInputData.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../MMKVCore/Core/CodedInputDataCrypt.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../MMKVCore/Core/CodedOutputData.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../MMKVCore/Core/InterProcessLock.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../MMKVCore/Core/KeyValueHolder.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../MMKVCore/Core/MMBuffer.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../MMKVCore/Core/MMKV.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../MMKVCore/Core/MMKVLog.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../MMKVCore/Core/MMKVMetaInfo.hpp
|
|
@ -0,0 +1 @@
|
||||||
|
../../../MMKVCore/Core/MMKVPredef.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../MMKVCore/Core/MMKV_IO.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../MMKVCore/Core/MMKV_OSX.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../MMKVCore/Core/MemoryFile.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../MMKVCore/Core/MiniPBCoder.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../MMKVCore/Core/PBEncodeItem.hpp
|
|
@ -0,0 +1 @@
|
||||||
|
../../../MMKVCore/Core/PBUtility.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../MMKVCore/Core/ScopedLock.hpp
|
|
@ -0,0 +1 @@
|
||||||
|
../../../MMKVCore/Core/ThreadLock.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../MMKVCore/Core/aes/openssl/openssl_aes.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../MMKVCore/Core/aes/openssl/openssl_aes_locl.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../MMKVCore/Core/aes/openssl/openssl_arm_arch.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../MMKVCore/Core/aes/openssl/openssl_md32_common.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../MMKVCore/Core/aes/openssl/openssl_md5.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../MMKVCore/Core/aes/openssl/openssl_md5_locl.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../MMKVCore/Core/aes/openssl/openssl_opensslconf.h
|
|
@ -1 +0,0 @@
|
||||||
../../../../../node_modules/rn-user-defaults/ios/RNUserDefaults.h
|
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../node_modules/react-native-mmkv-storage/ios/IDStore.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../node_modules/react-native-mmkv-storage/ios/MMKVStorage.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../node_modules/react-native-mmkv-storage/ios/SecureStorage.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../node_modules/react-native-mmkv-storage/ios/StorageGetters.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../node_modules/react-native-mmkv-storage/ios/StorageIndexer.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../node_modules/react-native-mmkv-storage/ios/StorageSetters.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../MMKV/iOS/MMKV/MMKV/MMKV.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../MMKV/iOS/MMKV/MMKV/MMKVHandler.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../MMKVAppExtension/iOS/MMKV/MMKV/MMKV.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../MMKVAppExtension/iOS/MMKV/MMKV/MMKVHandler.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../MMKVCore/Core/MMBuffer.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../MMKVCore/Core/MMKV.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../MMKVCore/Core/MMKVLog.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../MMKVCore/Core/MMKVPredef.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../MMKVCore/Core/PBUtility.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../MMKVCore/Core/ScopedLock.hpp
|
|
@ -0,0 +1 @@
|
||||||
|
../../../MMKVCore/Core/ThreadLock.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../MMKVCore/Core/aes/openssl/openssl_md5.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../MMKVCore/Core/aes/openssl/openssl_opensslconf.h
|
|
@ -1 +0,0 @@
|
||||||
../../../../../node_modules/rn-user-defaults/ios/RNUserDefaults.h
|
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../node_modules/react-native-mmkv-storage/ios/IDStore.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../node_modules/react-native-mmkv-storage/ios/MMKVStorage.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../node_modules/react-native-mmkv-storage/ios/SecureStorage.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../node_modules/react-native-mmkv-storage/ios/StorageGetters.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../node_modules/react-native-mmkv-storage/ios/StorageIndexer.h
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../node_modules/react-native-mmkv-storage/ios/StorageSetters.h
|
|
@ -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": [
|
|
||||||
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.
|
|
@ -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).
|
|
@ -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
|
|
@ -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 */
|
|
@ -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
Loading…
Reference in New Issue