[FIX] Deep linking and other connectivity issues (#2894)
* Navigate from push notification only if necessary * Use JS SDK branch * Stop reconnecting if it's already connected * Fix RoomsListView forever loading after tapping push notification of another server * Execute fewer operations on app/index * Remove roomsRequest call from onForeground * Apply check and reopen * Stop opening in-app notification when the app is on backgorund * Connecting tweaks * Fix deep linking not working if the app is on background * Force reset yarn cache * Upgrade JS SDK * Remove listener on unmount * Fix resume on Android after back button is pressed * Fix local authentication resume * Fix back button android * Change JS SDK branch
This commit is contained in:
parent
40c075d748
commit
cc8dc6a75a
|
@ -29,7 +29,7 @@
|
||||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
|
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:launchMode="singleTop"
|
android:launchMode="singleTask"
|
||||||
android:windowSoftInputMode="adjustResize">
|
android:windowSoftInputMode="adjustResize">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
|
<action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
|
||||||
|
|
|
@ -11,8 +11,12 @@ import { getActiveRoute } from '../../utils/navigation';
|
||||||
|
|
||||||
export const INAPP_NOTIFICATION_EMITTER = 'NotificationInApp';
|
export const INAPP_NOTIFICATION_EMITTER = 'NotificationInApp';
|
||||||
|
|
||||||
const InAppNotification = memo(({ rooms }) => {
|
const InAppNotification = memo(({ rooms, appState }) => {
|
||||||
const show = (notification) => {
|
const show = (notification) => {
|
||||||
|
if (appState !== 'foreground') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const { payload } = notification;
|
const { payload } = notification;
|
||||||
const state = Navigation.navigationRef.current?.getRootState();
|
const state = Navigation.navigationRef.current?.getRootState();
|
||||||
const route = getActiveRoute(state);
|
const route = getActiveRoute(state);
|
||||||
|
@ -41,11 +45,13 @@ const InAppNotification = memo(({ rooms }) => {
|
||||||
}, (prevProps, nextProps) => dequal(prevProps.rooms, nextProps.rooms));
|
}, (prevProps, nextProps) => dequal(prevProps.rooms, nextProps.rooms));
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
rooms: state.room.rooms
|
rooms: state.room.rooms,
|
||||||
|
appState: state.app.ready && state.app.foreground ? 'foreground' : 'background'
|
||||||
});
|
});
|
||||||
|
|
||||||
InAppNotification.propTypes = {
|
InAppNotification.propTypes = {
|
||||||
rooms: PropTypes.array
|
rooms: PropTypes.array,
|
||||||
|
appState: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(mapStateToProps)(InAppNotification);
|
export default connect(mapStateToProps)(InAppNotification);
|
||||||
|
|
21
app/index.js
21
app/index.js
|
@ -112,16 +112,25 @@ export default class Root extends React.Component {
|
||||||
|
|
||||||
init = async() => {
|
init = async() => {
|
||||||
UserPreferences.getMapAsync(THEME_PREFERENCES_KEY).then(this.setTheme);
|
UserPreferences.getMapAsync(THEME_PREFERENCES_KEY).then(this.setTheme);
|
||||||
const [notification, deepLinking] = await Promise.all([initializePushNotifications(), Linking.getInitialURL()]);
|
|
||||||
const parsedDeepLinkingURL = parseDeepLinking(deepLinking);
|
|
||||||
store.dispatch(appInitLocalSettings());
|
store.dispatch(appInitLocalSettings());
|
||||||
|
|
||||||
|
// Open app from push notification
|
||||||
|
const notification = await initializePushNotifications();
|
||||||
if (notification) {
|
if (notification) {
|
||||||
onNotification(notification);
|
onNotification(notification);
|
||||||
} else if (parsedDeepLinkingURL) {
|
return;
|
||||||
store.dispatch(deepLinkingOpen(parsedDeepLinkingURL));
|
|
||||||
} else {
|
|
||||||
store.dispatch(appInit());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Open app from deep linking
|
||||||
|
const deepLinking = await Linking.getInitialURL();
|
||||||
|
const parsedDeepLinkingURL = parseDeepLinking(deepLinking);
|
||||||
|
if (parsedDeepLinkingURL) {
|
||||||
|
store.dispatch(deepLinkingOpen(parsedDeepLinkingURL));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open app from app icon
|
||||||
|
store.dispatch(appInit());
|
||||||
}
|
}
|
||||||
|
|
||||||
getMasterDetail = (width) => {
|
getMasterDetail = (width) => {
|
||||||
|
|
|
@ -407,7 +407,7 @@ export default function subscribeRooms() {
|
||||||
};
|
};
|
||||||
|
|
||||||
connectedListener = this.sdk.onStreamData('connected', handleConnection);
|
connectedListener = this.sdk.onStreamData('connected', handleConnection);
|
||||||
disconnectedListener = this.sdk.onStreamData('close', handleConnection);
|
// disconnectedListener = this.sdk.onStreamData('close', handleConnection);
|
||||||
streamListener = this.sdk.onStreamData('stream-notify-user', handleStreamMessageReceived);
|
streamListener = this.sdk.onStreamData('stream-notify-user', handleStreamMessageReceived);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -177,9 +177,16 @@ const RocketChat = {
|
||||||
}
|
}
|
||||||
this.controller = new AbortController();
|
this.controller = new AbortController();
|
||||||
},
|
},
|
||||||
|
checkAndReopen() {
|
||||||
|
return this?.sdk?.checkAndReopen();
|
||||||
|
},
|
||||||
connect({ server, user, logoutOnError = false }) {
|
connect({ server, user, logoutOnError = false }) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
if (!this.sdk || this.sdk.client.host !== server) {
|
if (this?.sdk?.client?.host === server) {
|
||||||
|
return resolve();
|
||||||
|
} else {
|
||||||
|
this.sdk?.disconnect?.();
|
||||||
|
this.sdk = null;
|
||||||
database.setActiveDB(server);
|
database.setActiveDB(server);
|
||||||
}
|
}
|
||||||
reduxStore.dispatch(connectRequest());
|
reduxStore.dispatch(connectRequest());
|
||||||
|
@ -208,11 +215,6 @@ const RocketChat = {
|
||||||
|
|
||||||
EventEmitter.emit('INQUIRY_UNSUBSCRIBE');
|
EventEmitter.emit('INQUIRY_UNSUBSCRIBE');
|
||||||
|
|
||||||
if (this.sdk) {
|
|
||||||
this.sdk.disconnect();
|
|
||||||
this.sdk = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.code) {
|
if (this.code) {
|
||||||
this.code = null;
|
this.code = null;
|
||||||
}
|
}
|
||||||
|
@ -240,6 +242,10 @@ const RocketChat = {
|
||||||
|
|
||||||
sdkConnect();
|
sdkConnect();
|
||||||
|
|
||||||
|
this.connectedListener = this.sdk.onStreamData('connecting', () => {
|
||||||
|
reduxStore.dispatch(connectRequest());
|
||||||
|
});
|
||||||
|
|
||||||
this.connectedListener = this.sdk.onStreamData('connected', () => {
|
this.connectedListener = this.sdk.onStreamData('connected', () => {
|
||||||
reduxStore.dispatch(connectSuccess());
|
reduxStore.dispatch(connectSuccess());
|
||||||
});
|
});
|
||||||
|
|
|
@ -31,6 +31,14 @@ const handleInviteLink = function* handleInviteLink({ params, requireLogin = fal
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const popToRoot = function popToRoot({ isMasterDetail }) {
|
||||||
|
if (isMasterDetail) {
|
||||||
|
Navigation.navigate('DrawerNavigator');
|
||||||
|
} else {
|
||||||
|
Navigation.navigate('RoomsListView');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const navigate = function* navigate({ params }) {
|
const navigate = function* navigate({ params }) {
|
||||||
yield put(appStart({ root: ROOT_INSIDE }));
|
yield put(appStart({ root: ROOT_INSIDE }));
|
||||||
if (params.path) {
|
if (params.path) {
|
||||||
|
@ -38,19 +46,28 @@ const navigate = function* navigate({ params }) {
|
||||||
if (type !== 'invite') {
|
if (type !== 'invite') {
|
||||||
const room = yield RocketChat.canOpenRoom(params);
|
const room = yield RocketChat.canOpenRoom(params);
|
||||||
if (room) {
|
if (room) {
|
||||||
const isMasterDetail = yield select(state => state.app.isMasterDetail);
|
|
||||||
if (isMasterDetail) {
|
|
||||||
Navigation.navigate('DrawerNavigator');
|
|
||||||
} else {
|
|
||||||
Navigation.navigate('RoomsListView');
|
|
||||||
}
|
|
||||||
const item = {
|
const item = {
|
||||||
name,
|
name,
|
||||||
t: roomTypes[type],
|
t: roomTypes[type],
|
||||||
roomUserId: RocketChat.getUidDirectMessage(room),
|
roomUserId: RocketChat.getUidDirectMessage(room),
|
||||||
...room
|
...room
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isMasterDetail = yield select(state => state.app.isMasterDetail);
|
||||||
|
const focusedRooms = yield select(state => state.room.rooms);
|
||||||
|
|
||||||
|
if (focusedRooms.includes(room.rid)) {
|
||||||
|
// if there's one room on the list or last room is the one
|
||||||
|
if (focusedRooms.length === 1 || focusedRooms[0] === room.rid) {
|
||||||
yield goRoom({ item, isMasterDetail });
|
yield goRoom({ item, isMasterDetail });
|
||||||
|
} else {
|
||||||
|
popToRoot({ isMasterDetail });
|
||||||
|
yield goRoom({ item, isMasterDetail });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
popToRoot({ isMasterDetail });
|
||||||
|
yield goRoom({ item, isMasterDetail });
|
||||||
|
}
|
||||||
|
|
||||||
if (params.isCall) {
|
if (params.isCall) {
|
||||||
RocketChat.callJitsi(item);
|
RocketChat.callJitsi(item);
|
||||||
|
@ -121,10 +138,10 @@ const handleOpen = function* handleOpen({ params }) {
|
||||||
} else {
|
} else {
|
||||||
// search if deep link's server already exists
|
// search if deep link's server already exists
|
||||||
try {
|
try {
|
||||||
const servers = yield serversCollection.find(host);
|
const hostServerRecord = yield serversCollection.find(host);
|
||||||
if (servers && user) {
|
if (hostServerRecord && user) {
|
||||||
yield localAuthenticate(host);
|
yield localAuthenticate(host);
|
||||||
yield put(selectServerRequest(host));
|
yield put(selectServerRequest(host, hostServerRecord.version, true, true));
|
||||||
yield take(types.LOGIN.SUCCESS);
|
yield take(types.LOGIN.SUCCESS);
|
||||||
yield navigate({ params });
|
yield navigate({ params });
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -12,13 +12,14 @@ const appHasComeBackToForeground = function* appHasComeBackToForeground() {
|
||||||
if (appRoot === ROOT_OUTSIDE) {
|
if (appRoot === ROOT_OUTSIDE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auth = yield select(state => state.login.isAuthenticated);
|
const login = yield select(state => state.login);
|
||||||
if (!auth) {
|
const server = yield select(state => state.server);
|
||||||
|
if (!login.isAuthenticated || login.isFetching || server.connecting || server.loading || server.changingServer) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const server = yield select(state => state.server.server);
|
yield localAuthenticate(server.server);
|
||||||
yield localAuthenticate(server);
|
RocketChat.checkAndReopen();
|
||||||
setBadgeCount();
|
setBadgeCount();
|
||||||
return yield RocketChat.setUserPresenceOnline();
|
return yield RocketChat.setUserPresenceOnline();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -31,14 +32,6 @@ const appHasComeBackToBackground = function* appHasComeBackToBackground() {
|
||||||
if (appRoot === ROOT_OUTSIDE) {
|
if (appRoot === ROOT_OUTSIDE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auth = yield select(state => state.login.isAuthenticated);
|
|
||||||
if (!auth) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const localAuthenticated = yield select(state => state.login.isLocalAuthenticated);
|
|
||||||
if (!localAuthenticated) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const server = yield select(state => state.server.server);
|
const server = yield select(state => state.server.server);
|
||||||
yield saveLastLocalAuthenticationSession(server);
|
yield saveLastLocalAuthenticationSession(server);
|
||||||
|
|
|
@ -102,9 +102,6 @@ export const localAuthenticate = async(server) => {
|
||||||
|
|
||||||
// if screen lock is enabled
|
// if screen lock is enabled
|
||||||
if (serverRecord?.autoLock) {
|
if (serverRecord?.autoLock) {
|
||||||
// set isLocalAuthenticated to false
|
|
||||||
store.dispatch(setLocalAuthenticated(false));
|
|
||||||
|
|
||||||
// Make sure splash screen has been hidden
|
// Make sure splash screen has been hidden
|
||||||
RNBootSplash.hide();
|
RNBootSplash.hide();
|
||||||
|
|
||||||
|
@ -118,6 +115,9 @@ export const localAuthenticate = async(server) => {
|
||||||
|
|
||||||
// if last authenticated session is older than configured auto lock time, authentication is required
|
// if last authenticated session is older than configured auto lock time, authentication is required
|
||||||
if (diffToLastSession >= serverRecord?.autoLockTime) {
|
if (diffToLastSession >= serverRecord?.autoLockTime) {
|
||||||
|
// set isLocalAuthenticated to false
|
||||||
|
store.dispatch(setLocalAuthenticated(false));
|
||||||
|
|
||||||
let hasBiometry = false;
|
let hasBiometry = false;
|
||||||
|
|
||||||
// if biometry is enabled on the app
|
// if biometry is enabled on the app
|
||||||
|
|
|
@ -89,7 +89,6 @@ const shouldUpdateProps = [
|
||||||
'showUnread',
|
'showUnread',
|
||||||
'useRealName',
|
'useRealName',
|
||||||
'StoreLastMessage',
|
'StoreLastMessage',
|
||||||
'appState',
|
|
||||||
'theme',
|
'theme',
|
||||||
'isMasterDetail',
|
'isMasterDetail',
|
||||||
'refreshing',
|
'refreshing',
|
||||||
|
@ -126,7 +125,6 @@ class RoomsListView extends React.Component {
|
||||||
showUnread: PropTypes.bool,
|
showUnread: PropTypes.bool,
|
||||||
refreshing: PropTypes.bool,
|
refreshing: PropTypes.bool,
|
||||||
StoreLastMessage: PropTypes.bool,
|
StoreLastMessage: PropTypes.bool,
|
||||||
appState: PropTypes.string,
|
|
||||||
theme: PropTypes.string,
|
theme: PropTypes.string,
|
||||||
toggleSortDropdown: PropTypes.func,
|
toggleSortDropdown: PropTypes.func,
|
||||||
openSearchHeader: PropTypes.func,
|
openSearchHeader: PropTypes.func,
|
||||||
|
@ -135,7 +133,6 @@ class RoomsListView extends React.Component {
|
||||||
roomsRequest: PropTypes.func,
|
roomsRequest: PropTypes.func,
|
||||||
closeServerDropdown: PropTypes.func,
|
closeServerDropdown: PropTypes.func,
|
||||||
useRealName: PropTypes.bool,
|
useRealName: PropTypes.bool,
|
||||||
connected: PropTypes.bool,
|
|
||||||
isMasterDetail: PropTypes.bool,
|
isMasterDetail: PropTypes.bool,
|
||||||
rooms: PropTypes.array,
|
rooms: PropTypes.array,
|
||||||
width: PropTypes.number,
|
width: PropTypes.number,
|
||||||
|
@ -276,9 +273,6 @@ class RoomsListView extends React.Component {
|
||||||
groupByType,
|
groupByType,
|
||||||
showFavorites,
|
showFavorites,
|
||||||
showUnread,
|
showUnread,
|
||||||
appState,
|
|
||||||
connected,
|
|
||||||
roomsRequest,
|
|
||||||
rooms,
|
rooms,
|
||||||
isMasterDetail,
|
isMasterDetail,
|
||||||
insets
|
insets
|
||||||
|
@ -294,12 +288,6 @@ class RoomsListView extends React.Component {
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
this.getSubscriptions();
|
this.getSubscriptions();
|
||||||
} else if (
|
|
||||||
appState === 'foreground'
|
|
||||||
&& appState !== prevProps.appState
|
|
||||||
&& connected
|
|
||||||
) {
|
|
||||||
roomsRequest();
|
|
||||||
}
|
}
|
||||||
// Update current item in case of another action triggers an update on rooms reducer
|
// Update current item in case of another action triggers an update on rooms reducer
|
||||||
if (isMasterDetail && item?.rid !== rooms[0] && !dequal(rooms, prevProps.rooms)) {
|
if (isMasterDetail && item?.rid !== rooms[0] && !dequal(rooms, prevProps.rooms)) {
|
||||||
|
@ -319,6 +307,9 @@ class RoomsListView extends React.Component {
|
||||||
if (this.unsubscribeBlur) {
|
if (this.unsubscribeBlur) {
|
||||||
this.unsubscribeBlur();
|
this.unsubscribeBlur();
|
||||||
}
|
}
|
||||||
|
if (this.backHandler && this.backHandler.remove) {
|
||||||
|
this.backHandler.remove();
|
||||||
|
}
|
||||||
if (isTablet) {
|
if (isTablet) {
|
||||||
EventEmitter.removeListener(KEY_COMMAND, this.handleCommands);
|
EventEmitter.removeListener(KEY_COMMAND, this.handleCommands);
|
||||||
}
|
}
|
||||||
|
@ -1018,7 +1009,6 @@ const mapStateToProps = state => ({
|
||||||
isMasterDetail: state.app.isMasterDetail,
|
isMasterDetail: state.app.isMasterDetail,
|
||||||
server: state.server.server,
|
server: state.server.server,
|
||||||
changingServer: state.server.changingServer,
|
changingServer: state.server.changingServer,
|
||||||
connected: state.server.connected,
|
|
||||||
searchText: state.rooms.searchText,
|
searchText: state.rooms.searchText,
|
||||||
loadingServer: state.server.loading,
|
loadingServer: state.server.loading,
|
||||||
showServerDropdown: state.rooms.showServerDropdown,
|
showServerDropdown: state.rooms.showServerDropdown,
|
||||||
|
@ -1029,7 +1019,6 @@ const mapStateToProps = state => ({
|
||||||
showFavorites: state.sortPreferences.showFavorites,
|
showFavorites: state.sortPreferences.showFavorites,
|
||||||
showUnread: state.sortPreferences.showUnread,
|
showUnread: state.sortPreferences.showUnread,
|
||||||
useRealName: state.settings.UI_Use_Real_Name,
|
useRealName: state.settings.UI_Use_Real_Name,
|
||||||
appState: state.app.ready && state.app.foreground ? 'foreground' : 'background',
|
|
||||||
StoreLastMessage: state.settings.Store_Last_Message,
|
StoreLastMessage: state.settings.Store_Last_Message,
|
||||||
rooms: state.room.rooms,
|
rooms: state.room.rooms,
|
||||||
queueSize: getInquiryQueueSelector(state).length,
|
queueSize: getInquiryQueueSelector(state).length,
|
||||||
|
|
|
@ -48,6 +48,9 @@ class ReplyNotification: RNNotificationEventHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
let body = RNNotificationParser.parseNotificationResponse(response)
|
||||||
|
RNEventEmitter.sendEvent(RNNotificationOpened, body: body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
#import <react-native-notifications/RNNotificationEventHandler.h>
|
#import <react-native-notifications/RNNotificationEventHandler.h>
|
||||||
#import <react-native-notifications/RNNotificationCenter.h>
|
#import <react-native-notifications/RNNotificationCenter.h>
|
||||||
#import <react-native-notifications/RCTConvert+RNNotifications.h>
|
#import <react-native-notifications/RCTConvert+RNNotifications.h>
|
||||||
|
#import <react-native-notifications/RNEventEmitter.h>
|
||||||
|
#import <react-native-notifications/RNNotificationParser.h>
|
||||||
#import <react-native-simple-crypto/Aes.h>
|
#import <react-native-simple-crypto/Aes.h>
|
||||||
#import <react-native-simple-crypto/Rsa.h>
|
#import <react-native-simple-crypto/Rsa.h>
|
||||||
#import <react-native-simple-crypto/Shared.h>
|
#import <react-native-simple-crypto/Shared.h>
|
||||||
|
|
|
@ -120,7 +120,7 @@
|
||||||
"reselect": "4.0.0",
|
"reselect": "4.0.0",
|
||||||
"rn-extensions-share": "RocketChat/rn-extensions-share",
|
"rn-extensions-share": "RocketChat/rn-extensions-share",
|
||||||
"rn-fetch-blob": "0.12.0",
|
"rn-fetch-blob": "0.12.0",
|
||||||
"rn-root-view": "^1.0.3",
|
"rn-root-view": "1.0.3",
|
||||||
"semver": "7.3.2",
|
"semver": "7.3.2",
|
||||||
"ua-parser-js": "^0.7.21",
|
"ua-parser-js": "^0.7.21",
|
||||||
"url-parse": "^1.4.7",
|
"url-parse": "^1.4.7",
|
||||||
|
|
|
@ -2274,7 +2274,7 @@
|
||||||
|
|
||||||
"@rocket.chat/sdk@RocketChat/Rocket.Chat.js.SDK#mobile":
|
"@rocket.chat/sdk@RocketChat/Rocket.Chat.js.SDK#mobile":
|
||||||
version "1.0.0-mobile"
|
version "1.0.0-mobile"
|
||||||
resolved "https://codeload.github.com/RocketChat/Rocket.Chat.js.SDK/tar.gz/0a97c818e60670d7660868ea107b96e5ebb631af"
|
resolved "https://codeload.github.com/RocketChat/Rocket.Chat.js.SDK/tar.gz/0241e2fc0c29827c51655f2d46d96e7a7720d2b6"
|
||||||
dependencies:
|
dependencies:
|
||||||
js-sha256 "^0.9.0"
|
js-sha256 "^0.9.0"
|
||||||
lru-cache "^4.1.1"
|
lru-cache "^4.1.1"
|
||||||
|
@ -13788,7 +13788,7 @@ rn-host-detect@1.2.0:
|
||||||
resolved "https://registry.yarnpkg.com/rn-host-detect/-/rn-host-detect-1.2.0.tgz#8b0396fc05631ec60c1cb8789e5070cdb04d0da0"
|
resolved "https://registry.yarnpkg.com/rn-host-detect/-/rn-host-detect-1.2.0.tgz#8b0396fc05631ec60c1cb8789e5070cdb04d0da0"
|
||||||
integrity sha512-btNg5kzHcjZZ7t7mvvV/4wNJ9e3MPgrWivkRgWURzXL0JJ0pwWlU4zrbmdlz3HHzHOxhBhHB4D+/dbMFfu4/4A==
|
integrity sha512-btNg5kzHcjZZ7t7mvvV/4wNJ9e3MPgrWivkRgWURzXL0JJ0pwWlU4zrbmdlz3HHzHOxhBhHB4D+/dbMFfu4/4A==
|
||||||
|
|
||||||
rn-root-view@^1.0.3:
|
rn-root-view@1.0.3:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/rn-root-view/-/rn-root-view-1.0.3.tgz#a2cddc717278cb2175fb29b7c006e407b7f0d0e2"
|
resolved "https://registry.yarnpkg.com/rn-root-view/-/rn-root-view-1.0.3.tgz#a2cddc717278cb2175fb29b7c006e407b7f0d0e2"
|
||||||
integrity sha512-BIKm8hY5q8+pxK9B5ugYjqutoI9xn2JfxIZKWoaFmAl1bOIM4oXjwFQrRM1e6lFgzz99MN6Mf2dK3Alsywnvvw==
|
integrity sha512-BIKm8hY5q8+pxK9B5ugYjqutoI9xn2JfxIZKWoaFmAl1bOIM4oXjwFQrRM1e6lFgzz99MN6Mf2dK3Alsywnvvw==
|
||||||
|
|
Loading…
Reference in New Issue