From 255ea84599240d9cf8914f215a64a2cf0bf99ca4 Mon Sep 17 00:00:00 2001 From: Djorkaeff Alexandre Date: Wed, 26 Jun 2019 16:50:03 -0300 Subject: [PATCH] [IMPROVEMENT] Share credentials with Rocket.Chat.iOS (#982) * :sparkles: Create user table * :sparkles: Introduce user table * :fire: Remove unused table * :heavy_plus_sign: Add userdefaults to storage data * :green_heart: Fix android build * :sparkles: Get credentials from iOS native client * :fire: Remove unused code * :rewind: Revert sign xcode * :bug: Fix first login-logout * :art: Use constants to UserDefaults Keys * :bug: Fix clear server-user-info on logout * :bug: Fix filter null value * :ambulance: Remove user object in logout * :sparkles: Fix get servers from native-client * :ambulance: Fix error on change server --- app/constants/userDefaults.js | 6 +++ app/lib/realm.js | 15 ++++++++ app/lib/rocketchat.js | 28 +++++++++++--- app/sagas/deepLinking.js | 6 +-- app/sagas/init.js | 44 +++++++++++++++++++--- app/sagas/login.js | 18 +++++++-- app/sagas/selectServer.js | 24 ++++++++---- app/views/RoomsListView/ServerDropdown.js | 7 ++-- app/views/RoomsListView/index.js | 8 ++-- app/views/SidebarView/index.js | 2 +- ios/RocketChatRN.xcodeproj/project.pbxproj | 30 +++++++++++++++ ios/RocketChatRN/RocketChatRN.entitlements | 4 ++ package.json | 1 + yarn.lock | 5 +++ 14 files changed, 166 insertions(+), 32 deletions(-) create mode 100644 app/constants/userDefaults.js diff --git a/app/constants/userDefaults.js b/app/constants/userDefaults.js new file mode 100644 index 000000000..e21b7b852 --- /dev/null +++ b/app/constants/userDefaults.js @@ -0,0 +1,6 @@ +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'; diff --git a/app/lib/realm.js b/app/lib/realm.js index f296752b8..93ebf00dc 100644 --- a/app/lib/realm.js +++ b/app/lib/realm.js @@ -4,6 +4,20 @@ import Realm from 'realm'; // Realm.clearTestState(); // AsyncStorage.clear(); +const userSchema = { + name: 'user', + primaryKey: 'id', + properties: { + id: 'string', + token: { type: 'string', optional: true }, + username: { type: 'string', optional: true }, + name: { type: 'string', optional: true }, + language: { type: 'string', optional: true }, + status: { type: 'string', optional: true }, + roles: { type: 'string[]', optional: true } + } +}; + const serversSchema = { name: 'servers', primaryKey: 'id', @@ -370,6 +384,7 @@ class DB { serversDB: new Realm({ path: 'default.realm', schema: [ + userSchema, serversSchema ], schemaVersion: 8, diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js index a0118f5e7..673c3358a 100644 --- a/app/lib/rocketchat.js +++ b/app/lib/rocketchat.js @@ -1,6 +1,7 @@ import { AsyncStorage, InteractionManager } from 'react-native'; import semver from 'semver'; import { Rocketchat as RocketchatClient } from '@rocket.chat/sdk'; +import RNUserDefaults from 'rn-user-defaults'; import reduxStore from './createStore'; import defaultSettings from '../constants/settings'; @@ -36,6 +37,7 @@ import sendMessage, { getMessage, sendMessageCall } from './methods/sendMessage' import { sendFileMessage, cancelUpload, isUploadActive } from './methods/sendFileMessage'; import { getDeviceToken } from '../notifications/push'; +import { SERVERS, SERVER_URL } from '../constants/userDefaults'; const TOKEN_KEY = 'reactnativemeteor_usertoken'; const SORT_PREFS_KEY = 'RC_SORT_PREFS_KEY'; @@ -58,9 +60,9 @@ const RocketChat = { }, async getUserToken() { try { - return await AsyncStorage.getItem(TOKEN_KEY); + return await RNUserDefaults.get(TOKEN_KEY); } catch (error) { - console.warn(`AsyncStorage error: ${ error.message }`); + console.warn(`RNUserDefaults error: ${ error.message }`); } }, async getServerInfo(server) { @@ -321,10 +323,26 @@ const RocketChat = { } this.sdk = null; + try { + const servers = await RNUserDefaults.objectForKey(SERVERS); + await RNUserDefaults.setObjectForKey(SERVERS, servers && servers.filter(srv => srv[SERVER_URL] !== server)); + } catch (error) { + console.log('logout_rn_user_defaults', error); + } + + const { serversDB } = database.databases; + + const userId = await RNUserDefaults.get(`${ TOKEN_KEY }-${ server }`); + + serversDB.write(() => { + const user = serversDB.objectForPrimaryKey('user', userId); + serversDB.delete(user); + }); + Promise.all([ - AsyncStorage.removeItem('currentServer'), - AsyncStorage.removeItem(TOKEN_KEY), - AsyncStorage.removeItem(`${ TOKEN_KEY }-${ server }`) + RNUserDefaults.clear('currentServer'), + RNUserDefaults.clear(TOKEN_KEY), + RNUserDefaults.clear(`${ TOKEN_KEY }-${ server }`) ]).catch(error => console.log(error)); try { diff --git a/app/sagas/deepLinking.js b/app/sagas/deepLinking.js index de7bed594..04eccfca9 100644 --- a/app/sagas/deepLinking.js +++ b/app/sagas/deepLinking.js @@ -1,8 +1,8 @@ -import { AsyncStorage } from 'react-native'; import { delay } from 'redux-saga'; import { takeLatest, take, select, put, all } from 'redux-saga/effects'; +import RNUserDefaults from 'rn-user-defaults'; import Navigation from '../lib/Navigation'; import * as types from '../actions/actionsTypes'; @@ -43,8 +43,8 @@ const handleOpen = function* handleOpen({ params }) { } const [server, user] = yield all([ - AsyncStorage.getItem('currentServer'), - AsyncStorage.getItem(`${ RocketChat.TOKEN_KEY }-${ host }`) + RNUserDefaults.get('currentServer'), + RNUserDefaults.get(`${ RocketChat.TOKEN_KEY }-${ host }`) ]); // TODO: needs better test diff --git a/app/sagas/init.js b/app/sagas/init.js index 4d92b3fa2..0420d58de 100644 --- a/app/sagas/init.js +++ b/app/sagas/init.js @@ -1,6 +1,6 @@ -import { AsyncStorage } from 'react-native'; import { put, takeLatest, all } from 'redux-saga/effects'; import SplashScreen from 'react-native-splash-screen'; +import RNUserDefaults from 'rn-user-defaults'; import * as actions from '../actions'; import { selectServerRequest } from '../actions/server'; @@ -11,14 +11,46 @@ import RocketChat from '../lib/rocketchat'; import log from '../utils/log'; import Navigation from '../lib/Navigation'; import database from '../lib/realm'; +import { + SERVERS, SERVER_ICON, SERVER_NAME, SERVER_URL, TOKEN, USER_ID +} from '../constants/userDefaults'; const restore = function* restore() { try { - const { token, server } = yield all({ - token: AsyncStorage.getItem(RocketChat.TOKEN_KEY), - server: AsyncStorage.getItem('currentServer') + yield RNUserDefaults.setName('group.ios.chat.rocket'); + + let { token, server } = yield all({ + token: RNUserDefaults.get(RocketChat.TOKEN_KEY), + server: RNUserDefaults.get('currentServer') }); + // get native credentials + const { serversDB } = database.databases; + const servers = yield RNUserDefaults.objectForKey(SERVERS); + if (servers) { + serversDB.write(() => { + servers.forEach(async(serverItem) => { + const serverInfo = { + id: serverItem[SERVER_URL], + name: serverItem[SERVER_NAME], + iconURL: serverItem[SERVER_ICON] + }; + try { + serversDB.create('servers', serverInfo, true); + await RNUserDefaults.set(`${ RocketChat.TOKEN_KEY }-${ serverInfo.id }`, serverItem[USER_ID]); + } catch (e) { + log('err_create_servers', e); + } + }); + }); + } + + // if not have current + if (servers.length !== 0 && (!token || !server)) { + server = servers[0][SERVER_URL]; + token = servers[0][TOKEN]; + } + const sortPreferences = yield RocketChat.getSortPreferences(); yield put(setAllPreferences(sortPreferences)); @@ -27,8 +59,8 @@ const restore = function* restore() { if (!token || !server) { yield all([ - AsyncStorage.removeItem(RocketChat.TOKEN_KEY), - AsyncStorage.removeItem('currentServer') + RNUserDefaults.clear(RocketChat.TOKEN_KEY), + RNUserDefaults.clear('currentServer') ]); yield put(actions.appStart('outside')); } else if (server) { diff --git a/app/sagas/login.js b/app/sagas/login.js index 66b4183ad..369f636ff 100644 --- a/app/sagas/login.js +++ b/app/sagas/login.js @@ -1,7 +1,7 @@ -import { AsyncStorage } from 'react-native'; import { put, call, takeLatest, select, take, fork, cancel } from 'redux-saga/effects'; +import RNUserDefaults from 'rn-user-defaults'; import * as types from '../actions/actionsTypes'; import { appStart } from '../actions'; @@ -60,7 +60,7 @@ const fetchUserPresence = function* fetchUserPresence() { const handleLoginSuccess = function* handleLoginSuccess({ user }) { try { const adding = yield select(state => state.server.adding); - yield AsyncStorage.setItem(RocketChat.TOKEN_KEY, user.token); + yield RNUserDefaults.set(RocketChat.TOKEN_KEY, user.token); const server = yield select(getServer); yield put(roomsRequest()); @@ -72,7 +72,17 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) { yield fork(fetchUserPresence); I18n.locale = user.language; - yield AsyncStorage.setItem(`${ RocketChat.TOKEN_KEY }-${ server }`, JSON.stringify(user)); + + const { serversDB } = database.databases; + serversDB.write(() => { + try { + serversDB.create('user', user, true); + } catch (e) { + log('err_set_user_token', e); + } + }); + + yield RNUserDefaults.set(`${ RocketChat.TOKEN_KEY }-${ server }`, user.id); yield put(setUser(user)); EventEmitter.emit('connected'); @@ -105,7 +115,7 @@ const handleLogout = function* handleLogout() { // see if there's other logged in servers and selects first one if (servers.length > 0) { const newServer = servers[0].id; - const token = yield AsyncStorage.getItem(`${ RocketChat.TOKEN_KEY }-${ newServer }`); + const token = yield RNUserDefaults.get(`${ RocketChat.TOKEN_KEY }-${ newServer }`); if (token) { return yield put(selectServerRequest(newServer)); } diff --git a/app/sagas/selectServer.js b/app/sagas/selectServer.js index b6f1efec5..f6793f998 100644 --- a/app/sagas/selectServer.js +++ b/app/sagas/selectServer.js @@ -1,7 +1,8 @@ import { put, take, takeLatest, fork, cancel, race } from 'redux-saga/effects'; -import { AsyncStorage, Alert } from 'react-native'; +import { Alert } from 'react-native'; +import RNUserDefaults from 'rn-user-defaults'; import Navigation from '../lib/Navigation'; import { SERVER } from '../actions/actionsTypes'; @@ -14,6 +15,7 @@ import RocketChat from '../lib/rocketchat'; import database from '../lib/realm'; import log from '../utils/log'; import I18n from '../i18n'; +import { SERVERS, TOKEN, SERVER_URL } from '../constants/userDefaults'; const getServerInfo = function* getServerInfo({ server, raiseError = true }) { try { @@ -38,13 +40,21 @@ const getServerInfo = function* getServerInfo({ server, raiseError = true }) { const handleSelectServer = function* handleSelectServer({ server, version, fetchVersion }) { try { - yield AsyncStorage.setItem('currentServer', server); - const userStringified = yield AsyncStorage.getItem(`${ RocketChat.TOKEN_KEY }-${ server }`); + const { serversDB } = database.databases; - if (userStringified) { - const user = JSON.parse(userStringified); - yield RocketChat.connect({ server, user }); - yield put(setUser(user)); + yield RNUserDefaults.set('currentServer', server); + const userId = yield RNUserDefaults.get(`${ RocketChat.TOKEN_KEY }-${ server }`); + const user = userId && serversDB.objectForPrimaryKey('user', userId); + + const servers = yield RNUserDefaults.objectForKey(SERVERS); + const userCredentials = servers && servers.find(srv => srv[SERVER_URL] === server); + const userLogin = userCredentials && { + token: userCredentials[TOKEN] + }; + + if (user || userLogin) { + yield RocketChat.connect({ server, user: user || userLogin }); + yield put(setUser(user || userLogin)); yield put(actions.appStart('inside')); } else { yield RocketChat.connect({ server }); diff --git a/app/views/RoomsListView/ServerDropdown.js b/app/views/RoomsListView/ServerDropdown.js index 4bc8db9d1..60e161d08 100644 --- a/app/views/RoomsListView/ServerDropdown.js +++ b/app/views/RoomsListView/ServerDropdown.js @@ -1,11 +1,12 @@ import React, { Component } from 'react'; import { - View, Text, Animated, Easing, TouchableWithoutFeedback, TouchableOpacity, FlatList, Image, AsyncStorage + View, Text, Animated, Easing, TouchableWithoutFeedback, TouchableOpacity, FlatList, Image } from 'react-native'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import equal from 'deep-equal'; import { withNavigation } from 'react-navigation'; +import RNUserDefaults from 'rn-user-defaults'; import { toggleServerDropdown as toggleServerDropdownAction } from '../../actions/rooms'; import { selectServerRequest as selectServerRequestAction } from '../../actions/server'; @@ -124,8 +125,8 @@ class ServerDropdown extends Component { this.close(); if (currentServer !== server) { - const token = await AsyncStorage.getItem(`${ RocketChat.TOKEN_KEY }-${ server }`); - if (!token) { + const userId = await RNUserDefaults.get(`${ RocketChat.TOKEN_KEY }-${ server }`); + if (!userId) { appStart(); this.newServerTimeout = setTimeout(() => { EventEmitter.emit('NewServer', { server }); diff --git a/app/views/RoomsListView/index.js b/app/views/RoomsListView/index.js index a8297a166..ee5aa0fcf 100644 --- a/app/views/RoomsListView/index.js +++ b/app/views/RoomsListView/index.js @@ -39,6 +39,7 @@ const keyExtractor = item => item.rid; @connect(state => ({ userId: state.login.user && state.login.user.id, + isAuthenticated: state.login.isAuthenticated, server: state.server.server, baseUrl: state.settings.baseUrl || state.server ? state.server.server : '', searchText: state.rooms.searchText, @@ -111,7 +112,8 @@ export default class RoomsListView extends React.Component { openSearchHeader: PropTypes.func, closeSearchHeader: PropTypes.func, appStart: PropTypes.func, - roomsRequest: PropTypes.func + roomsRequest: PropTypes.func, + isAuthenticated: PropTypes.bool } constructor(props) { @@ -187,7 +189,7 @@ export default class RoomsListView extends React.Component { componentDidUpdate(prevProps) { const { - sortBy, groupByType, showFavorites, showUnread, appState, roomsRequest + sortBy, groupByType, showFavorites, showUnread, appState, roomsRequest, isAuthenticated } = this.props; if (!( @@ -197,7 +199,7 @@ export default class RoomsListView extends React.Component { && (prevProps.showUnread === showUnread) )) { this.getSubscriptions(); - } else if (appState === 'foreground' && appState !== prevProps.appState) { + } else if (appState === 'foreground' && appState !== prevProps.appState && isAuthenticated) { roomsRequest(); } } diff --git a/app/views/SidebarView/index.js b/app/views/SidebarView/index.js index 49a0fa9e7..2249e8ad5 100644 --- a/app/views/SidebarView/index.js +++ b/app/views/SidebarView/index.js @@ -152,7 +152,7 @@ export default class Sidebar extends Component { const permissionsFiltered = database.objects('permissions') .filter(permission => permissions.includes(permission._id)); return permissionsFiltered.reduce((result, permission) => ( - result || permission.roles.some(r => roles.includes(r))), + result || permission.roles.some(r => roles.indexOf(r) !== -1)), false); } return false; diff --git a/ios/RocketChatRN.xcodeproj/project.pbxproj b/ios/RocketChatRN.xcodeproj/project.pbxproj index f879bc003..a9a733da8 100644 --- a/ios/RocketChatRN.xcodeproj/project.pbxproj +++ b/ios/RocketChatRN.xcodeproj/project.pbxproj @@ -20,6 +20,7 @@ 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; 146834051AC3E58100842450 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 146834041AC3E56700842450 /* libReact.a */; }; + 1E02221122B2F76B00001862 /* libRNUserDefaults.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1E02220D22B2F76400001862 /* libRNUserDefaults.a */; }; 24A2AEF2383D44B586D31C01 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 06BB44DD4855498082A744AD /* libz.tbd */; }; 38CEA0ED468E49CFABCD82FD /* libRNFirebase.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A36F9982B71E4662AA8DEB77 /* libRNFirebase.a */; }; 50046CB6BDA69B9232CF66D9 /* libPods-RocketChatRN.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C235DC7B31A4D1578EDEF219 /* libPods-RocketChatRN.a */; }; @@ -99,6 +100,13 @@ remoteGlobalIDString = 83CBBA2E1A601D0E00E9B192; remoteInfo = React; }; + 1E02220C22B2F76400001862 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 1E0221D522B2F76300001862 /* RNUserDefaults.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 134814201AA4EA6300B7C361; + remoteInfo = RNUserDefaults; + }; 3DAD3E831DF850E9000B6D8A /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */; @@ -452,6 +460,7 @@ 1845C223DA364898A8400573 /* FastImage.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = FastImage.xcodeproj; path = "../node_modules/react-native-fast-image/ios/FastImage.xcodeproj"; sourceTree = ""; }; 1A34D902CC074FF1BCC7DB48 /* libimageCropPicker.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libimageCropPicker.a; sourceTree = ""; }; 1D3BB00B9ABF44EA9BD71318 /* libSafariViewManager.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libSafariViewManager.a; sourceTree = ""; }; + 1E0221D522B2F76300001862 /* RNUserDefaults.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RNUserDefaults.xcodeproj; path = "../node_modules/rn-user-defaults/ios/RNUserDefaults.xcodeproj"; sourceTree = ""; }; 20CE3E407E0D4D9E8C9885F2 /* libRCTVideo.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRCTVideo.a; sourceTree = ""; }; 22A8B76C8EBA443BB97CE82D /* RNVectorIcons.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNVectorIcons.xcodeproj; path = "../node_modules/react-native-vector-icons/RNVectorIcons.xcodeproj"; sourceTree = ""; }; 22D3971EAF2E4660B4FAB3DD /* RNI18n.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNI18n.xcodeproj; path = "../node_modules/react-native-i18n/ios/RNI18n.xcodeproj"; sourceTree = ""; }; @@ -492,6 +501,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 1E02221122B2F76B00001862 /* libRNUserDefaults.a in Frameworks */, 7ACD4897222860DE00442C55 /* JavaScriptCore.framework in Frameworks */, 7A8DEB5A20ED0BEC00C5DCE4 /* libRNNotifications.a in Frameworks */, B8971BB2202A093B0000D245 /* libKeyboardTrackingView.a in Frameworks */, @@ -626,6 +636,14 @@ name = Products; sourceTree = ""; }; + 1E0221D622B2F76300001862 /* Products */ = { + isa = PBXGroup; + children = ( + 1E02220D22B2F76400001862 /* libRNUserDefaults.a */, + ); + name = Products; + sourceTree = ""; + }; 22CA7F59107E0C79C2506C7C /* Pods */ = { isa = PBXGroup; children = ( @@ -731,6 +749,7 @@ 832341AE1AAA6A7D00B99B32 /* Libraries */ = { isa = PBXGroup; children = ( + 1E0221D522B2F76300001862 /* RNUserDefaults.xcodeproj */, 7A8DEB1B20ED0BDE00C5DCE4 /* RNNotifications.xcodeproj */, B8971BAC202A091D0000D245 /* KeyboardTrackingView.xcodeproj */, 7A430E1620238C01008F55BC /* RCTCustomInputController.xcodeproj */, @@ -1016,6 +1035,10 @@ ProductGroup = 7A8DEB1C20ED0BDE00C5DCE4 /* Products */; ProjectRef = 7A8DEB1B20ED0BDE00C5DCE4 /* RNNotifications.xcodeproj */; }, + { + ProductGroup = 1E0221D622B2F76300001862 /* Products */; + ProjectRef = 1E0221D522B2F76300001862 /* RNUserDefaults.xcodeproj */; + }, { ProductGroup = B8E79A8A1F3CCC6C005B464F /* Products */; ProjectRef = 22A8B76C8EBA443BB97CE82D /* RNVectorIcons.xcodeproj */; @@ -1085,6 +1108,13 @@ remoteRef = 146834031AC3E56700842450 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; + 1E02220D22B2F76400001862 /* libRNUserDefaults.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRNUserDefaults.a; + remoteRef = 1E02220C22B2F76400001862 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; 3DAD3E841DF850E9000B6D8A /* libRCTImage-tvOS.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; diff --git a/ios/RocketChatRN/RocketChatRN.entitlements b/ios/RocketChatRN/RocketChatRN.entitlements index 1484e4f68..dce5d2dec 100644 --- a/ios/RocketChatRN/RocketChatRN.entitlements +++ b/ios/RocketChatRN/RocketChatRN.entitlements @@ -8,5 +8,9 @@ applinks:go.rocket.chat + com.apple.security.application-groups + + group.ios.chat.rocket + diff --git a/package.json b/package.json index befd08357..43d49492e 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,7 @@ "redux-saga": "^0.16.2", "remove-markdown": "^0.3.0", "rn-fetch-blob": "^0.10.15", + "rn-user-defaults": "^1.3.4", "semver": "6.0.0", "snyk": "^1.156.0", "strip-ansi": "5.2.0" diff --git a/yarn.lock b/yarn.lock index f418405c5..f077b08ae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12550,6 +12550,11 @@ rn-fetch-blob@^0.10.15: base-64 "0.1.0" glob "7.0.6" +rn-user-defaults@^1.3.4: + version "1.3.4" + resolved "https://registry.yarnpkg.com/rn-user-defaults/-/rn-user-defaults-1.3.4.tgz#1fbdd1bf29d9f853918dca5219e45db54d19fe37" + integrity sha512-CnzZbq3Q1VQUr6wKl9Z48eKmOzu+6dMcl2wN0ty+sBnpUyIqFvv3CMzYzb26v1qK8z7q/PcMr75shFHcrJH9WA== + rsvp@^3.3.3: version "3.6.2" resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-3.6.2.tgz#2e96491599a96cde1b515d5674a8f7a91452926a"