diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js index 5bef3ad8..80fd2972 100644 --- a/app/lib/rocketchat.js +++ b/app/lib/rocketchat.js @@ -47,6 +47,7 @@ import { getDeviceToken } from '../notifications/push'; import { setActiveUsers } from '../actions/activeUsers'; import I18n from '../i18n'; import { twoFactor } from '../utils/twoFactor'; +import { selectServerFailure } from '../actions/server'; import { useSsl } from '../utils/url'; const TOKEN_KEY = 'reactnativemeteor_usertoken'; @@ -142,6 +143,10 @@ const RocketChat = { } return result; } catch (e) { + if (e.message === 'Aborted') { + reduxStore.dispatch(selectServerFailure()); + throw e; + } log(e); } return { @@ -155,6 +160,16 @@ const RocketChat = { stopListener(listener) { return listener && listener.stop(); }, + // Abort all requests and create a new AbortController + abort() { + if (this.controller) { + this.controller.abort(); + if (this.sdk) { + this.sdk.abort(); + } + } + this.controller = new AbortController(); + }, connect({ server, user, logoutOnError = false }) { return new Promise((resolve) => { if (!this.sdk || this.sdk.client.host !== server) { @@ -201,17 +216,21 @@ const RocketChat = { const sdkConnect = () => this.sdk.connect() .then(() => { - if (user && user.token) { + const { server: currentServer } = reduxStore.getState().server; + if (user && user.token && server === currentServer) { reduxStore.dispatch(loginRequest({ resume: user.token }, logoutOnError)); } }) .catch((err) => { console.log('connect error', err); - // when `connect` raises an error, we try again in 10 seconds - this.connectTimeout = setTimeout(() => { - sdkConnect(); - }, 10000); + const { server: currentServer } = reduxStore.getState().server; + if (server === currentServer) { + // when `connect` raises an error, we try again in 10 seconds + this.connectTimeout = setTimeout(() => { + sdkConnect(); + }, 10000); + } }); sdkConnect(); diff --git a/app/sagas/selectServer.js b/app/sagas/selectServer.js index ed26c454..7596e05d 100644 --- a/app/sagas/selectServer.js +++ b/app/sagas/selectServer.js @@ -1,6 +1,4 @@ -import { - put, take, takeLatest, fork, cancel, race -} from 'redux-saga/effects'; +import { put, takeLatest } from 'redux-saga/effects'; import { Alert } from 'react-native'; import RNUserDefaults from 'rn-user-defaults'; import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord'; @@ -98,6 +96,9 @@ const handleSelectServer = function* handleSelectServer({ server, version, fetch const basicAuth = yield RNUserDefaults.get(`${ BASIC_AUTH_KEY }-${ server }`); setBasicAuth(basicAuth); + // Check for running requests and abort them before connecting to the server + RocketChat.abort(); + if (user) { yield put(clearSettings()); yield RocketChat.connect({ server, user, logoutOnError: true }); @@ -152,16 +153,6 @@ const handleServerRequest = function* handleServerRequest({ server, certificate const root = function* root() { yield takeLatest(SERVER.REQUEST, handleServerRequest); - - while (true) { - const params = yield take(SERVER.SELECT_REQUEST); - const selectServerTask = yield fork(handleSelectServer, params); - yield race({ - request: take(SERVER.SELECT_REQUEST), - success: take(SERVER.SELECT_SUCCESS), - failure: take(SERVER.SELECT_FAILURE) - }); - yield cancel(selectServerTask); - } + yield takeLatest(SERVER.SELECT_REQUEST, handleSelectServer); }; export default root; diff --git a/app/utils/fetch.js b/app/utils/fetch.js index 7b0fae13..a804e0a1 100644 --- a/app/utils/fetch.js +++ b/app/utils/fetch.js @@ -1,6 +1,7 @@ import { Platform } from 'react-native'; import DeviceInfo from 'react-native-device-info'; import { settings as RocketChatSettings } from '@rocket.chat/sdk'; +import RocketChat from '../lib/rocketchat'; // this form is required by Rocket.Chat's parser in "app/statistics/server/lib/UAParserCustom.js" export const headers = { @@ -25,5 +26,9 @@ export default (url, options = {}) => { if (options && options.headers) { customOptions = { ...customOptions, headers: { ...options.headers, ...customOptions.headers } }; } + if (RocketChat.controller) { + const { signal } = RocketChat.controller; + customOptions = { ...customOptions, signal }; + } return fetch(url, customOptions); }; diff --git a/app/views/RoomsListView/Header/Header.ios.js b/app/views/RoomsListView/Header/Header.ios.js index 31f887cf..35a8a65d 100644 --- a/app/views/RoomsListView/Header/Header.ios.js +++ b/app/views/RoomsListView/Header/Header.ios.js @@ -56,7 +56,7 @@ const Header = React.memo(({ onPress={onPress} testID='rooms-list-header-server-dropdown-button' style={styles.container} - disabled={connecting || isFetching} + // disabled={connecting || isFetching} > diff --git a/package.json b/package.json index c431048d..d1719e29 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,7 @@ "react-native-responsive-ui": "^1.1.1", "react-native-screens": "^2.0.0-alpha.3", "react-native-scrollable-tab-view": "^1.0.0", - "react-native-slowlog": "^1.0.2", + "react-native-slowlog": "1.0.2", "react-native-unimodules": "0.5.3", "react-native-vector-icons": "6.6.0", "react-native-webview": "7.5.1", diff --git a/patches/@rocket.chat+sdk+1.0.0-dj.15.patch b/patches/@rocket.chat+sdk+1.0.0-dj.15.patch new file mode 100644 index 00000000..cf698fd9 --- /dev/null +++ b/patches/@rocket.chat+sdk+1.0.0-dj.15.patch @@ -0,0 +1,83 @@ +diff --git a/node_modules/@rocket.chat/sdk/lib/api/api.ts b/node_modules/@rocket.chat/sdk/lib/api/api.ts +index 17c2c2b..cb094e8 100644 +--- a/node_modules/@rocket.chat/sdk/lib/api/api.ts ++++ b/node_modules/@rocket.chat/sdk/lib/api/api.ts +@@ -117,24 +117,31 @@ class Client implements IClient { + JSON.stringify(data) + } + ++ getSignal (options?: any): AbortSignal { ++ return options && options.signal; ++ } ++ + get (url: string, data: any, options?: any): Promise { + return fetch(`${this.host}/api/v1/${encodeURI(url)}?${this.getParams(data)}`, { + method: 'GET', +- headers: this.getHeaders(options) ++ headers: this.getHeaders(options), ++ signal: this.getSignal(options) + }).then(this.handle) + } + post (url: string, data: any, options?: any): Promise { + return fetch(`${this.host}/api/v1/${encodeURI(url)}`, { + method: 'POST', + body: this.getBody(data), +- headers: this.getHeaders(options) ++ headers: this.getHeaders(options), ++ signal: this.getSignal(options) + }).then(this.handle) + } + put (url: string, data: any, options?: any): Promise { + return fetch(`${this.host}/api/v1/${encodeURI(url)}`, { + method: 'PUT', + body: this.getBody(data), +- headers: this.getHeaders(options) ++ headers: this.getHeaders(options), ++ signal: this.getSignal(options) + }).then(this.handle) + } + +@@ -142,7 +149,8 @@ class Client implements IClient { + return fetch(`${this.host}/api/v1/${encodeURI(url)}`, { + method: 'DELETE', + body: this.getBody(data), +- headers: this.getHeaders(options) ++ headers: this.getHeaders(options), ++ signal: this.getSignal(options) + }).then(this.handle) + } + private async handle (r: any) { +@@ -176,11 +184,13 @@ export default class Api extends EventEmitter { + authToken: string, + result: ILoginResultAPI + } | null = null ++ controller: AbortController + + constructor ({ client, host, logger = Logger }: any) { + super() + this.client = client || new Client({ host } as any) + this.logger = Logger ++ this.controller = new AbortController(); + } + + get username () { +@@ -212,6 +222,10 @@ export default class Api extends EventEmitter { + if (auth && !this.loggedIn()) { + throw new Error('') + } ++ ++ const { signal } = this.controller; ++ options = { ...options, signal }; ++ + let result + switch (method) { + case 'GET': result = await this.client.get(endpoint, data, options); break +@@ -242,6 +256,8 @@ export default class Api extends EventEmitter { + /** Do a DELETE request to an API endpoint. */ + del: IAPIRequest = (endpoint, data, auth, ignore, options = {}) => this.request('DELETE', endpoint, data, auth, ignore, options) + ++ abort = (): void => this.controller.abort() ++ + /** Check result data for success, allowing override to ignore some errors */ + success (result: any, ignore?: RegExp) { + return ( diff --git a/yarn.lock b/yarn.lock index 6341ed12..deea80c0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10072,7 +10072,7 @@ react-native-scrollable-tab-view@^1.0.0: prop-types "^15.6.0" react-timer-mixin "^0.13.3" -react-native-slowlog@^1.0.2: +react-native-slowlog@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/react-native-slowlog/-/react-native-slowlog-1.0.2.tgz#5520979e3ef9d5273495d431ff3be34f02e35c89" integrity sha1-VSCXnj751Sc0ldQx/zvjTwLjXIk=