diff --git a/app/actions/actionsTypes.js b/app/actions/actionsTypes.js index 6e704861..45d2886f 100644 --- a/app/actions/actionsTypes.js +++ b/app/actions/actionsTypes.js @@ -58,6 +58,7 @@ export const SERVER = createRequestTypes('SERVER', [ ...defaultTypes, 'SELECT_SUCCESS', 'SELECT_REQUEST', + 'SELECT_FAILURE', 'INIT_ADD', 'FINISH_ADD' ]); diff --git a/app/actions/server.js b/app/actions/server.js index 3a383781..a5441c7b 100644 --- a/app/actions/server.js +++ b/app/actions/server.js @@ -17,6 +17,12 @@ export function selectServerSuccess(server, version) { }; } +export function selectServerFailure() { + return { + type: SERVER.SELECT_FAILURE + }; +} + export function serverRequest(server) { return { type: SERVER.REQUEST, diff --git a/app/lib/methods/getCustomEmojis.js b/app/lib/methods/getCustomEmojis.js index be2701f6..e2fa2790 100644 --- a/app/lib/methods/getCustomEmojis.js +++ b/app/lib/methods/getCustomEmojis.js @@ -23,57 +23,62 @@ const create = (customEmojis) => { }; -export default async function() { - try { - const serverVersion = reduxStore.getState().server.version; - const updatedSince = getUpdatedSince(); +export default function() { + return new Promise(async(resolve) => { + try { + const serverVersion = reduxStore.getState().server.version; + const updatedSince = getUpdatedSince(); - // if server version is lower than 0.75.0, fetches from old api - if (semver.lt(serverVersion, '0.75.0')) { - // RC 0.61.0 - const result = await this.sdk.get('emoji-custom'); + // if server version is lower than 0.75.0, fetches from old api + if (semver.lt(serverVersion, '0.75.0')) { + // RC 0.61.0 + const result = await this.sdk.get('emoji-custom'); - InteractionManager.runAfterInteractions(() => { - let { emojis } = result; - emojis = emojis.filter(emoji => !updatedSince || emoji._updatedAt > updatedSince); - database.write(() => { - create(emojis); + InteractionManager.runAfterInteractions(() => { + let { emojis } = result; + emojis = emojis.filter(emoji => !updatedSince || emoji._updatedAt > updatedSince); + database.write(() => { + create(emojis); + }); + return resolve(); }); - }); - } else { - const params = {}; - if (updatedSince) { - params.updatedSince = updatedSince; - } + } else { + const params = {}; + if (updatedSince) { + params.updatedSince = updatedSince; + } - // RC 0.75.0 - const result = await this.sdk.get('emoji-custom.list', params); + // RC 0.75.0 + const result = await this.sdk.get('emoji-custom.list', params); - if (!result.success) { - return; - } + if (!result.success) { + return resolve(); + } - InteractionManager.runAfterInteractions( - () => database.write(() => { - const { emojis } = result; - create(emojis.update); + InteractionManager.runAfterInteractions( + () => database.write(() => { + const { emojis } = result; + create(emojis.update); - if (emojis.delete && emojis.delete.length) { - emojis.delete.forEach((emoji) => { - try { - const emojiRecord = database.objectForPrimaryKey('customEmojis', emoji._id); - if (emojiRecord) { - database.delete(emojiRecord); + if (emojis.delete && emojis.delete.length) { + emojis.delete.forEach((emoji) => { + try { + const emojiRecord = database.objectForPrimaryKey('customEmojis', emoji._id); + if (emojiRecord) { + database.delete(emojiRecord); + } + } catch (e) { + log('err_get_emojis_delete', e); } - } catch (e) { - log('err_get_emojis_delete', e); - } - }); - } - }) - ); + }); + } + return resolve(); + }) + ); + } + } catch (e) { + log('err_get_custom_emojis', e); + return resolve(); } - } catch (e) { - log('err_get_custom_emojis', e); - } + }); } diff --git a/app/lib/methods/getPermissions.js b/app/lib/methods/getPermissions.js index c4d4a860..b6c9c1c5 100644 --- a/app/lib/methods/getPermissions.js +++ b/app/lib/methods/getPermissions.js @@ -22,55 +22,60 @@ const create = (permissions) => { } }; -export default async function() { - try { - const serverVersion = reduxStore.getState().server.version; +export default function() { + return new Promise(async(resolve) => { + try { + const serverVersion = reduxStore.getState().server.version; - // if server version is lower than 0.73.0, fetches from old api - if (semver.lt(serverVersion, '0.73.0')) { - // RC 0.66.0 - const result = await this.sdk.get('permissions.list'); - if (!result.success) { - return; - } - InteractionManager.runAfterInteractions(() => { - database.write(() => { - create(result.permissions); + // if server version is lower than 0.73.0, fetches from old api + if (semver.lt(serverVersion, '0.73.0')) { + // RC 0.66.0 + const result = await this.sdk.get('permissions.list'); + if (!result.success) { + return resolve(); + } + InteractionManager.runAfterInteractions(() => { + database.write(() => { + create(result.permissions); + }); + return resolve(); }); - }); - } else { - const params = {}; - const updatedSince = getUpdatedSince(); - if (updatedSince) { - params.updatedSince = updatedSince; - } - // RC 0.73.0 - const result = await this.sdk.get('permissions.listAll', params); + } else { + const params = {}; + const updatedSince = getUpdatedSince(); + if (updatedSince) { + params.updatedSince = updatedSince; + } + // RC 0.73.0 + const result = await this.sdk.get('permissions.listAll', params); - if (!result.success) { - return; - } + if (!result.success) { + return resolve(); + } - InteractionManager.runAfterInteractions( - () => database.write(() => { - create(result.update); + InteractionManager.runAfterInteractions( + () => database.write(() => { + create(result.update); - if (result.delete && result.delete.length) { - result.delete.forEach((p) => { - try { - const permission = database.objectForPrimaryKey('permissions', p._id); - if (permission) { - database.delete(permission); + if (result.delete && result.delete.length) { + result.delete.forEach((p) => { + try { + const permission = database.objectForPrimaryKey('permissions', p._id); + if (permission) { + database.delete(permission); + } + } catch (e) { + log('err_get_permissions_delete', e); } - } catch (e) { - log('err_get_permissions_delete', e); - } - }); - } - }) - ); + }); + } + return resolve(); + }) + ); + } + } catch (e) { + log('err_get_permissions', e); + return resolve(); } - } catch (e) { - log('err_get_permissions', e); - } + }); } diff --git a/app/lib/methods/getRoles.js b/app/lib/methods/getRoles.js index 6b7a06da..c668a55a 100644 --- a/app/lib/methods/getRoles.js +++ b/app/lib/methods/getRoles.js @@ -3,29 +3,33 @@ import { InteractionManager } from 'react-native'; import database from '../realm'; import log from '../../utils/log'; -export default async function() { - try { - // RC 0.70.0 - const result = await this.sdk.get('roles.list'); +export default function() { + return new Promise(async(resolve) => { + try { + // RC 0.70.0 + const result = await this.sdk.get('roles.list'); - if (!result.success) { - return; + if (!result.success) { + return resolve(); + } + + const { roles } = result; + + if (roles && roles.length) { + InteractionManager.runAfterInteractions(() => { + database.write(() => roles.forEach((role) => { + try { + database.create('roles', role, true); + } catch (e) { + log('err_get_roles_create', e); + } + })); + return resolve(); + }); + } + } catch (e) { + log('err_get_roles', e); + return resolve(); } - - const { roles } = result; - - if (roles && roles.length) { - InteractionManager.runAfterInteractions(() => { - database.write(() => roles.forEach((role) => { - try { - database.create('roles', role, true); - } catch (e) { - log('err_get_roles_create', e); - } - })); - }); - } - } catch (e) { - log('err_get_roles', e); - } + }); } diff --git a/app/lib/methods/getSlashCommands.js b/app/lib/methods/getSlashCommands.js index 401c1180..6c20624e 100644 --- a/app/lib/methods/getSlashCommands.js +++ b/app/lib/methods/getSlashCommands.js @@ -3,29 +3,34 @@ import { InteractionManager } from 'react-native'; import database from '../realm'; import log from '../../utils/log'; -export default async function() { - try { - // RC 0.60.2 - const result = await this.sdk.get('commands.list'); +export default function() { + return new Promise(async(resolve) => { + try { + // RC 0.60.2 + const result = await this.sdk.get('commands.list'); - if (!result.success) { - return log('getSlashCommand fetch', result); + if (!result.success) { + log('getSlashCommand fetch', result); + return resolve(); + } + + const { commands } = result; + + if (commands && commands.length) { + InteractionManager.runAfterInteractions(() => { + database.write(() => commands.forEach((command) => { + try { + database.create('slashCommand', command, true); + } catch (e) { + log('get_slash_command', e); + } + })); + return resolve(); + }); + } + } catch (e) { + log('err_get_slash_command', e); + return resolve(); } - - const { commands } = result; - - if (commands && commands.length) { - InteractionManager.runAfterInteractions(() => { - database.write(() => commands.forEach((command) => { - try { - database.create('slashCommand', command, true); - } catch (e) { - log('get_slash_command', e); - } - })); - }); - } - } catch (e) { - log('err_get_slash_command', e); - } + }); } diff --git a/app/lib/methods/subscriptions/rooms.js b/app/lib/methods/subscriptions/rooms.js index 50a35c55..5b23fbda 100644 --- a/app/lib/methods/subscriptions/rooms.js +++ b/app/lib/methods/subscriptions/rooms.js @@ -13,6 +13,7 @@ const removeListener = listener => listener.stop(); let connectedListener; let disconnectedListener; let streamListener; +let subServer; export default async function subscribeRooms() { let timer = null; @@ -41,6 +42,10 @@ export default async function subscribeRooms() { }; const handleStreamMessageReceived = protectedFunction((ddpMessage) => { + // check if the server from variable is the same as the js sdk client + if (this.sdk && this.sdk.client && this.sdk.client.host !== subServer) { + return; + } if (ddpMessage.msg === 'added') { return; } @@ -149,6 +154,8 @@ export default async function subscribeRooms() { streamListener = this.sdk.onStreamData('stream-notify-user', handleStreamMessageReceived); try { + // set the server that started this task + subServer = this.sdk.client.host; await this.sdk.subscribeNotifyUser(); } catch (e) { log('err_subscribe_rooms', e); diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js index 156e34b7..21ad4b1e 100644 --- a/app/lib/rocketchat.js +++ b/app/lib/rocketchat.js @@ -8,7 +8,6 @@ import messagesStatus from '../constants/messagesStatus'; import database from './realm'; import log from '../utils/log'; import { isIOS, getBundleId } from '../utils/deviceInfo'; -import EventEmitter from '../utils/events'; import { setUser, setLoginServices, loginRequest, loginFailure, logout @@ -24,7 +23,7 @@ import getSettings from './methods/getSettings'; import getRooms from './methods/getRooms'; import getPermissions from './methods/getPermissions'; -import getCustomEmoji from './methods/getCustomEmojis'; +import getCustomEmojis from './methods/getCustomEmojis'; import getSlashCommands from './methods/getSlashCommands'; import getRoles from './methods/getRoles'; import canOpenRoom from './methods/canOpenRoom'; @@ -37,7 +36,6 @@ import sendMessage, { getMessage, sendMessageCall } from './methods/sendMessage' import { sendFileMessage, cancelUpload, isUploadActive } from './methods/sendFileMessage'; import { getDeviceToken } from '../notifications/push'; -import { roomsRequest } from '../actions/rooms'; const TOKEN_KEY = 'reactnativemeteor_usertoken'; const SORT_PREFS_KEY = 'RC_SORT_PREFS_KEY'; @@ -141,23 +139,6 @@ const RocketChat = { }; } }, - async loginSuccess({ user }) { - EventEmitter.emit('connected'); - reduxStore.dispatch(setUser(user)); - reduxStore.dispatch(roomsRequest()); - - if (this.roomsSub) { - this.roomsSub.stop(); - } - this.roomsSub = await this.subscribeRooms(); - - this.getPermissions(); - this.getCustomEmoji(); - this.getRoles(); - this.getSlashCommands(); - this.registerPushToken().catch(e => console.log(e)); - this.getUserPresence(); - }, connect({ server, user }) { return new Promise((resolve) => { database.setActiveDB(server); @@ -167,6 +148,10 @@ const RocketChat = { clearTimeout(this.connectTimeout); } + if (this.roomsSub) { + this.roomsSub.stop(); + } + if (this.sdk) { this.sdk.disconnect(); this.sdk = null; @@ -195,10 +180,10 @@ const RocketChat = { this.sdk.onStreamData('connected', () => { reduxStore.dispatch(connectSuccess()); - const { isAuthenticated } = reduxStore.getState().login; - if (isAuthenticated) { - this.getUserPresence(); - } + // const { isAuthenticated } = reduxStore.getState().login; + // if (isAuthenticated) { + // this.getUserPresence(); + // } }); this.sdk.onStreamData('close', () => { @@ -349,7 +334,7 @@ const RocketChat = { } }, registerPushToken() { - return new Promise((resolve) => { + return new Promise(async(resolve) => { const token = getDeviceToken(); if (token) { const type = isIOS ? 'apn' : 'gcm'; @@ -358,8 +343,12 @@ const RocketChat = { type, appName: getBundleId }; - // RC 0.60.0 - return this.sdk.post('push.token', data); + try { + // RC 0.60.0 + await this.sdk.post('push.token', data); + } catch (error) { + console.log(error); + } } return resolve(); }); @@ -468,7 +457,7 @@ const RocketChat = { isUploadActive, getSettings, getPermissions, - getCustomEmoji, + getCustomEmojis, getSlashCommands, getRoles, parseSettings: settings => settings.reduce((ret, item) => { @@ -824,41 +813,45 @@ const RocketChat = { command, params, roomId, previewItem }); }, - async getUserPresence() { - const serverVersion = reduxStore.getState().server.version; + getUserPresence() { + return new Promise(async(resolve) => { + const serverVersion = reduxStore.getState().server.version; - // if server is lower than 1.1.0 - if (semver.lt(semver.coerce(serverVersion), '1.1.0')) { - if (this.activeUsersSubTimeout) { - clearTimeout(this.activeUsersSubTimeout); - this.activeUsersSubTimeout = false; - } - this.activeUsersSubTimeout = setTimeout(() => { - this.sdk.subscribe('activeUsers'); - }, 5000); - } else { - const params = {}; - if (this.lastUserPresenceFetch) { - params.from = this.lastUserPresenceFetch.toISOString(); - } + // if server is lower than 1.1.0 + if (semver.lt(semver.coerce(serverVersion), '1.1.0')) { + if (this.activeUsersSubTimeout) { + clearTimeout(this.activeUsersSubTimeout); + this.activeUsersSubTimeout = false; + } + this.activeUsersSubTimeout = setTimeout(() => { + this.sdk.subscribe('activeUsers'); + }, 5000); + return resolve(); + } else { + const params = {}; + // if (this.lastUserPresenceFetch) { + // params.from = this.lastUserPresenceFetch.toISOString(); + // } - // RC 1.1.0 - const result = await this.sdk.get('users.presence', params); - if (result.success) { - this.lastUserPresenceFetch = new Date(); - database.memoryDatabase.write(() => { - result.users.forEach((item) => { - try { - item.id = item._id; - database.memoryDatabase.create('activeUsers', item, true); - } catch (error) { - console.log(error); - } + // RC 1.1.0 + const result = await this.sdk.get('users.presence', params); + if (result.success) { + // this.lastUserPresenceFetch = new Date(); + database.memoryDatabase.write(() => { + result.users.forEach((item) => { + try { + item.id = item._id; + database.memoryDatabase.create('activeUsers', item, true); + } catch (error) { + console.log(error); + } + }); }); - }); - this.sdk.subscribe('stream-notify-logged', 'user-status'); + this.sdk.subscribe('stream-notify-logged', 'user-status'); + return resolve(); + } } - } + }); }, getDirectory({ query, count, offset, sort diff --git a/app/reducers/server.js b/app/reducers/server.js index a2a04bd9..7eda3767 100644 --- a/app/reducers/server.js +++ b/app/reducers/server.js @@ -44,6 +44,13 @@ export default function server(state = initialState, action) { connected: true, loading: false }; + case SERVER.SELECT_FAILURE: + return { + ...state, + connecting: false, + connected: false, + loading: false + }; case SERVER.INIT_ADD: return { ...state, diff --git a/app/sagas/login.js b/app/sagas/login.js index 28c72321..66b4183a 100644 --- a/app/sagas/login.js +++ b/app/sagas/login.js @@ -1,16 +1,18 @@ import { AsyncStorage } from 'react-native'; import { - put, call, takeLatest, select + put, call, takeLatest, select, take, fork, cancel } from 'redux-saga/effects'; import * as types from '../actions/actionsTypes'; import { appStart } from '../actions'; import { serverFinishAdd, selectServerRequest } from '../actions/server'; -import { loginFailure, loginSuccess } from '../actions/login'; +import { loginFailure, loginSuccess, setUser } from '../actions/login'; +import { roomsRequest } from '../actions/rooms'; import RocketChat from '../lib/rocketchat'; import log from '../utils/log'; import I18n from '../i18n'; import database from '../lib/realm'; +import EventEmitter from '../utils/events'; const getServer = state => state.server.server; const loginWithPasswordCall = args => RocketChat.loginWithPassword(args); @@ -31,27 +33,59 @@ const handleLoginRequest = function* handleLoginRequest({ credentials }) { } }; -const handleLoginSuccess = function* handleLoginSuccess({ user }) { - const adding = yield select(state => state.server.adding); - yield AsyncStorage.setItem(RocketChat.TOKEN_KEY, user.token); +const fetchPermissions = function* fetchPermissions() { + yield RocketChat.getPermissions(); +}; - const server = yield select(getServer); +const fetchCustomEmojis = function* fetchCustomEmojis() { + yield RocketChat.getCustomEmojis(); +}; + +const fetchRoles = function* fetchRoles() { + yield RocketChat.getRoles(); +}; + +const fetchSlashCommands = function* fetchSlashCommands() { + yield RocketChat.getSlashCommands(); +}; + +const registerPushToken = function* registerPushToken() { + yield RocketChat.registerPushToken(); +}; + +const fetchUserPresence = function* fetchUserPresence() { + yield RocketChat.getUserPresence(); +}; + +const handleLoginSuccess = function* handleLoginSuccess({ user }) { try { - RocketChat.loginSuccess({ user }); + const adding = yield select(state => state.server.adding); + yield AsyncStorage.setItem(RocketChat.TOKEN_KEY, user.token); + + const server = yield select(getServer); + yield put(roomsRequest()); + yield fork(fetchPermissions); + yield fork(fetchCustomEmojis); + yield fork(fetchRoles); + yield fork(fetchSlashCommands); + yield fork(registerPushToken); + yield fork(fetchUserPresence); + I18n.locale = user.language; yield AsyncStorage.setItem(`${ RocketChat.TOKEN_KEY }-${ server }`, JSON.stringify(user)); - } catch (error) { - console.log('loginSuccess saga -> error', error); - } + yield put(setUser(user)); + EventEmitter.emit('connected'); - if (!user.username) { - RocketChat.loginSuccess({ user }); - yield put(appStart('setUsername')); - } else if (adding) { - yield put(serverFinishAdd()); - yield put(appStart('inside')); - } else { - yield put(appStart('inside')); + if (!user.username) { + yield put(appStart('setUsername')); + } else if (adding) { + yield put(serverFinishAdd()); + yield put(appStart('inside')); + } else { + yield put(appStart('inside')); + } + } catch (e) { + log('err_handle_login_success', e); } }; @@ -93,8 +127,14 @@ const handleSetUser = function handleSetUser({ user }) { const root = function* root() { yield takeLatest(types.LOGIN.REQUEST, handleLoginRequest); - yield takeLatest(types.LOGIN.SUCCESS, handleLoginSuccess); yield takeLatest(types.LOGOUT, handleLogout); yield takeLatest(types.USER.SET, handleSetUser); + + while (true) { + const params = yield take(types.LOGIN.SUCCESS); + const loginSuccessTask = yield fork(handleLoginSuccess, params); + yield take(types.SERVER.SELECT_REQUEST); + yield cancel(loginSuccessTask); + } }; export default root; diff --git a/app/sagas/rooms.js b/app/sagas/rooms.js index ad5d862f..bc900f2d 100644 --- a/app/sagas/rooms.js +++ b/app/sagas/rooms.js @@ -1,6 +1,7 @@ import { - put, takeLatest, select + put, select, race, take, fork, cancel, takeLatest } from 'redux-saga/effects'; +import { BACKGROUND } from 'redux-enhancer-react-native-appstate'; import * as types from '../actions/actionsTypes'; import { roomsSuccess, roomsFailure } from '../actions/rooms'; @@ -9,8 +10,18 @@ import log from '../utils/log'; import mergeSubscriptionsRooms from '../lib/methods/helpers/mergeSubscriptionsRooms'; import RocketChat from '../lib/rocketchat'; +let roomsSub; + +const removeSub = function removeSub() { + if (roomsSub && roomsSub.stop) { + roomsSub.stop(); + } +}; + const handleRoomsRequest = function* handleRoomsRequest() { try { + removeSub(); + roomsSub = yield RocketChat.subscribeRooms(); const newRoomsUpdatedAt = new Date(); const server = yield select(state => state.server.server); const [serverRecord] = database.databases.serversDB.objects('servers').filtered('id = $0', server); @@ -42,7 +53,21 @@ const handleRoomsRequest = function* handleRoomsRequest() { } }; +const handleLogout = function handleLogout() { + removeSub(); +}; + const root = function* root() { - yield takeLatest(types.ROOMS.REQUEST, handleRoomsRequest); + yield takeLatest(types.LOGOUT, handleLogout); + while (true) { + const params = yield take(types.ROOMS.REQUEST); + const roomsRequestTask = yield fork(handleRoomsRequest, params); + yield race({ + serverReq: take(types.SERVER.SELECT_REQUEST), + background: take(BACKGROUND), + logout: take(types.LOGOUT) + }); + yield cancel(roomsRequestTask); + } }; export default root; diff --git a/app/sagas/selectServer.js b/app/sagas/selectServer.js index 85910390..b6f1efec 100644 --- a/app/sagas/selectServer.js +++ b/app/sagas/selectServer.js @@ -1,10 +1,14 @@ -import { put, takeLatest } from 'redux-saga/effects'; +import { + put, take, takeLatest, fork, cancel, race +} from 'redux-saga/effects'; import { AsyncStorage, Alert } from 'react-native'; import Navigation from '../lib/Navigation'; import { SERVER } from '../actions/actionsTypes'; import * as actions from '../actions'; -import { serverFailure, selectServerRequest, selectServerSuccess } from '../actions/server'; +import { + serverFailure, selectServerRequest, selectServerSuccess, selectServerFailure +} from '../actions/server'; import { setUser } from '../actions/login'; import RocketChat from '../lib/rocketchat'; import database from '../lib/realm'; @@ -58,6 +62,7 @@ const handleSelectServer = function* handleSelectServer({ server, version, fetch // Return server version even when offline yield put(selectServerSuccess(server, (serverInfo && serverInfo.version) || version)); } catch (e) { + yield put(selectServerFailure()); log('err_select_server', e); } }; @@ -81,7 +86,17 @@ const handleServerRequest = function* handleServerRequest({ server }) { }; const root = function* root() { - yield takeLatest(SERVER.SELECT_REQUEST, handleSelectServer); 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); + } }; export default root; diff --git a/app/views/RoomsListView/index.js b/app/views/RoomsListView/index.js index f29d6234..a8297a16 100644 --- a/app/views/RoomsListView/index.js +++ b/app/views/RoomsListView/index.js @@ -29,6 +29,7 @@ import RoomsListHeaderView from './Header'; import { DrawerButton, CustomHeaderButtons, Item } from '../../containers/HeaderButton'; import StatusBar from '../../containers/StatusBar'; import ListHeader from './ListHeader'; +import { selectServerRequest as selectServerRequestAction } from '../../actions/server'; const SCROLL_OFFSET = 56; @@ -56,7 +57,8 @@ const keyExtractor = item => item.rid; openSearchHeader: () => dispatch(openSearchHeaderAction()), closeSearchHeader: () => dispatch(closeSearchHeaderAction()), appStart: () => dispatch(appStartAction()), - roomsRequest: () => dispatch(roomsRequestAction()) + roomsRequest: () => dispatch(roomsRequestAction()), + selectServerRequest: server => dispatch(selectServerRequestAction(server)) })) export default class RoomsListView extends React.Component { static navigationOptions = ({ navigation }) => {