[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:
Diego Mello 2021-03-05 13:10:21 -03:00 committed by GitHub
parent 40c075d748
commit cc8dc6a75a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 84 additions and 59 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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
}; };
yield goRoom({ item, isMasterDetail });
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 });
} 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;

View File

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

View File

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

View File

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

View File

@ -48,6 +48,9 @@ class ReplyNotification: RNNotificationEventHandler {
} }
} }
} }
} else {
let body = RNNotificationParser.parseNotificationResponse(response)
RNEventEmitter.sendEvent(RNNotificationOpened, body: body)
} }
} }
} }

View File

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

View File

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

View File

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