Reauthenticate from stored token when app is reloaded (#137)
* Reauthenticate from stored token when app is reloaded * Address feedback
This commit is contained in:
parent
bd3bd45c36
commit
d2f2678880
25 changed files with 356 additions and 42 deletions
52
app/actions/storage/index.js
Normal file
52
app/actions/storage/index.js
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {AsyncStorage} from 'react-native';
|
||||
import {GeneralTypes, UsersTypes} from 'service/constants';
|
||||
import Client from 'service/client';
|
||||
|
||||
export function loadStorage() {
|
||||
return async (dispatch, getState) => {
|
||||
try {
|
||||
const data = JSON.parse(await AsyncStorage.getItem('storage'));
|
||||
dispatch({type: GeneralTypes.RECEIVED_APP_CREDENTIALS, data}, getState);
|
||||
} catch (error) {
|
||||
// Error loading data
|
||||
dispatch({type: GeneralTypes.REMOVED_APP_CREDENTIALS, error}, getState);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function saveStorage() {
|
||||
return async (dispatch, getState) => {
|
||||
try {
|
||||
const data = {
|
||||
token: Client.getToken(),
|
||||
url: Client.getUrl()
|
||||
};
|
||||
|
||||
await AsyncStorage.setItem('storage', JSON.stringify(data));
|
||||
dispatch({type: GeneralTypes.RECEIVED_APP_CREDENTIALS, data}, getState);
|
||||
} catch (error) {
|
||||
// Error saving data
|
||||
dispatch({type: GeneralTypes.REMOVED_APP_CREDENTIALS, error}, getState);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function removeStorage() {
|
||||
return async (dispatch, getState) => {
|
||||
try {
|
||||
await AsyncStorage.removeItem('storage');
|
||||
} catch (error) {
|
||||
// Error removing data
|
||||
}
|
||||
dispatch({type: UsersTypes.RESET_LOGOUT_STATE}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
loadStorage,
|
||||
saveStorage,
|
||||
removeStorage
|
||||
};
|
||||
|
|
@ -1,9 +1,14 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {batchActions} from 'redux-batched-actions';
|
||||
import {NavigationTypes} from 'app/constants';
|
||||
import Routes from 'app/navigation/routes';
|
||||
|
||||
import Client from 'service/client';
|
||||
import {forceLogoutIfNecessary} from 'service/actions/helpers';
|
||||
import {PreferencesTypes, TeamsTypes, UsersTypes} from 'service/constants';
|
||||
|
||||
export function goToSelectServer() {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
|
|
@ -13,3 +18,85 @@ export function goToSelectServer() {
|
|||
}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function setStoreFromLocalData(data) {
|
||||
return async (dispatch, getState) => {
|
||||
Client.setToken(data.token);
|
||||
Client.setUrl(data.url);
|
||||
|
||||
let user;
|
||||
dispatch({type: UsersTypes.LOGIN_REQUEST}, getState);
|
||||
try {
|
||||
user = await Client.getMe();
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch);
|
||||
dispatch({type: UsersTypes.LOGIN_FAILURE, error}, getState);
|
||||
return;
|
||||
}
|
||||
|
||||
let preferences;
|
||||
dispatch({type: PreferencesTypes.MY_PREFERENCES_REQUEST}, getState);
|
||||
try {
|
||||
preferences = await Client.getMyPreferences();
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch);
|
||||
dispatch({type: PreferencesTypes.MY_PREFERENCES_FAILURE, error}, getState);
|
||||
return;
|
||||
}
|
||||
|
||||
let teams;
|
||||
dispatch({type: TeamsTypes.FETCH_TEAMS_REQUEST}, getState);
|
||||
try {
|
||||
teams = await Client.getAllTeams();
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch);
|
||||
dispatch({type: TeamsTypes.FETCH_TEAMS_FAILURE, error}, getState);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: uncomment and sorround with try/catch when PLT-4167 is merged
|
||||
// const teamMembers = Client.getMyTeamMembers();
|
||||
const teamMembers = Object.keys(teams).map((key) => {
|
||||
return {
|
||||
team_id: key,
|
||||
user_id: user.id
|
||||
};
|
||||
});
|
||||
|
||||
dispatch(batchActions([
|
||||
{
|
||||
type: UsersTypes.RECEIVED_ME,
|
||||
data: user
|
||||
},
|
||||
{
|
||||
type: UsersTypes.LOGIN_SUCCESS
|
||||
},
|
||||
{
|
||||
type: PreferencesTypes.RECEIVED_PREFERENCES,
|
||||
data: preferences
|
||||
},
|
||||
{
|
||||
type: PreferencesTypes.MY_PREFERENCES_SUCCESS
|
||||
},
|
||||
{
|
||||
type: TeamsTypes.RECEIVED_MY_TEAM_MEMBERS,
|
||||
data: teamMembers
|
||||
},
|
||||
{
|
||||
type: TeamsTypes.MY_TEAM_MEMBERS_SUCCESS
|
||||
},
|
||||
{
|
||||
type: TeamsTypes.RECEIVED_ALL_TEAMS,
|
||||
data: teams
|
||||
},
|
||||
{
|
||||
type: TeamsTypes.FETCH_TEAMS_SUCCESS
|
||||
}
|
||||
]), getState);
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
goToSelectServer,
|
||||
setStoreFromLocalData
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,9 +2,11 @@
|
|||
// See License.txt for license information.
|
||||
|
||||
import NavigationTypes from './navigation';
|
||||
import StorageTypes from './storage';
|
||||
import ViewTypes from './view';
|
||||
|
||||
export {
|
||||
NavigationTypes,
|
||||
StorageTypes,
|
||||
ViewTypes
|
||||
};
|
||||
|
|
|
|||
15
app/constants/storage.js
Normal file
15
app/constants/storage.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import keymirror from 'keymirror';
|
||||
|
||||
const StorageTypes = keymirror({
|
||||
SAVE_TO_STORAGE: null,
|
||||
SAVE_TO_STORAGE_ERROR: null,
|
||||
LOAD_FROM_STORAGE: null,
|
||||
LOAD_FROM_STORAGE_ERROR: null,
|
||||
REMOVE_FROM_STORAGE: null,
|
||||
REMOVE_FROM_STORAGE_ERROR: null
|
||||
});
|
||||
|
||||
export default StorageTypes;
|
||||
|
|
@ -2,16 +2,39 @@
|
|||
// See License.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import {AppState} from 'react-native';
|
||||
import {getTranslations} from 'service/i18n';
|
||||
import {IntlProvider} from 'react-intl';
|
||||
|
||||
export default class RootLayout extends React.Component {
|
||||
static propTypes = {
|
||||
children: React.PropTypes.node,
|
||||
locale: React.PropTypes.string.isRequired
|
||||
locale: React.PropTypes.string.isRequired,
|
||||
actions: React.PropTypes.shape({
|
||||
setAppState: React.PropTypes.func
|
||||
}).isRequired
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.handleAppStateChange = this.handleAppStateChange.bind(this);
|
||||
|
||||
this.props.actions.setAppState(AppState.currentState === 'active');
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
AppState.addEventListener('change', this.handleAppStateChange);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
AppState.removeEventListener('change', this.handleAppStateChange);
|
||||
}
|
||||
|
||||
handleAppStateChange(appState) {
|
||||
this.props.actions.setAppState(appState === 'active');
|
||||
}
|
||||
|
||||
render() {
|
||||
const locale = this.props.locale;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
import Config from 'config/index';
|
||||
|
||||
import Config from 'config';
|
||||
|
||||
import {setAppState} from 'service/actions/general';
|
||||
import RootLayout from './root_layout';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
|
|
@ -22,4 +23,12 @@ function mapStateToProps(state, ownProps) {
|
|||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(RootLayout);
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
setAppState
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(RootLayout);
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ class Router extends React.Component {
|
|||
actions: React.PropTypes.shape({
|
||||
goBack: React.PropTypes.func
|
||||
}).isRequired
|
||||
}
|
||||
};
|
||||
|
||||
renderTransition = (transitionProps) => {
|
||||
let title;
|
||||
|
|
@ -61,7 +61,7 @@ class Router extends React.Component {
|
|||
{title}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
renderTitle = ({scene}) => {
|
||||
const title = scene.route.title;
|
||||
|
|
@ -78,20 +78,20 @@ class Router extends React.Component {
|
|||
/>
|
||||
</NavigationExperimental.Header.Title>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
renderScene = ({scene}) => {
|
||||
const SceneComponent = getComponentForScene(scene.route.key);
|
||||
|
||||
return <SceneComponent {...scene.route.props}/>;
|
||||
}
|
||||
};
|
||||
|
||||
configureTransition = () => {
|
||||
return {
|
||||
duration: 500,
|
||||
easing: Easing.inOut(Easing.ease)
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
render = () => {
|
||||
return (
|
||||
|
|
@ -102,7 +102,7 @@ class Router extends React.Component {
|
|||
configureTransition={this.configureTransition}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function mapStateToProps(state) {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import views from './views';
|
||||
import navigation from './navigation';
|
||||
import views from './views';
|
||||
|
||||
export default {
|
||||
views,
|
||||
navigation
|
||||
navigation,
|
||||
views
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import {combineReducers} from 'redux';
|
||||
|
||||
import Config from 'config';
|
||||
import Config from 'config/index';
|
||||
import {UsersTypes} from 'service/constants';
|
||||
|
||||
function locale(state = Config.DefaultLocale, action) {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import {combineReducers} from 'redux';
|
||||
|
||||
import Config from 'config';
|
||||
import Config from 'config/index';
|
||||
|
||||
import {ViewTypes} from 'app/constants';
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import {StatusBar, Text, TouchableHighlight, View} from 'react-native';
|
|||
export default class Channel extends React.Component {
|
||||
static propTypes = {
|
||||
actions: React.PropTypes.object.isRequired,
|
||||
currentTeam: React.PropTypes.object.isRequired,
|
||||
currentTeam: React.PropTypes.object,
|
||||
currentChannel: React.PropTypes.object,
|
||||
channels: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
|
||||
theme: React.PropTypes.object.isRequired
|
||||
|
|
@ -36,26 +36,26 @@ export default class Channel extends React.Component {
|
|||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.currentTeam.id !== nextProps.currentTeam.id) {
|
||||
if (this.props.currentTeam && nextProps.currentTeam && this.props.currentTeam.id !== nextProps.currentTeam.id) {
|
||||
this.props.actions.fetchMyChannelsAndMembers(nextProps.currentTeam.id);
|
||||
}
|
||||
}
|
||||
|
||||
openLeftSidebar = () => {
|
||||
this.setState({leftSidebarOpen: true});
|
||||
}
|
||||
};
|
||||
|
||||
closeLeftSidebar = () => {
|
||||
this.setState({leftSidebarOpen: false});
|
||||
}
|
||||
};
|
||||
|
||||
openRightSidebar = () => {
|
||||
this.setState({rightSidebarOpen: true});
|
||||
}
|
||||
};
|
||||
|
||||
closeRightSidebar = () => {
|
||||
this.setState({rightSidebarOpen: false});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ class Login extends Component {
|
|||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.loginRequest.status === RequestStatus.STARTED && nextProps.loginRequest.status === RequestStatus.SUCCESS) {
|
||||
this.props.actions.goToSelectTeam();
|
||||
this.props.actions.saveStorage().then(this.props.actions.goToSelectTeam);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import {connect} from 'react-redux';
|
|||
|
||||
import {getClientConfig, getLicenseConfig} from 'service/actions/general';
|
||||
import * as LoginActions from 'app/actions/views/login';
|
||||
import * as StorageActions from 'app/actions/storage';
|
||||
import {goToSelectTeam} from 'app/actions/navigation';
|
||||
import {login} from 'service/actions/users';
|
||||
|
||||
|
|
@ -26,6 +27,7 @@ function mapDispatchToProps(dispatch) {
|
|||
return {
|
||||
actions: bindActionCreators({
|
||||
...LoginActions,
|
||||
...StorageActions,
|
||||
login,
|
||||
getClientConfig,
|
||||
getLicenseConfig,
|
||||
|
|
|
|||
|
|
@ -2,19 +2,47 @@
|
|||
// See License.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import Loading from 'app/components/loading';
|
||||
|
||||
import {RequestStatus} from 'service/constants';
|
||||
|
||||
export default class Root extends React.Component {
|
||||
static propTypes = {
|
||||
actions: React.PropTypes.object.isRequired
|
||||
credentials: React.PropTypes.object,
|
||||
logoutRequest: React.PropTypes.object,
|
||||
actions: React.PropTypes.shape({
|
||||
goToSelectServer: React.PropTypes.func,
|
||||
goToSelectTeam: React.PropTypes.func,
|
||||
loadStorage: React.PropTypes.func,
|
||||
removeStorage: React.PropTypes.func,
|
||||
setStoreFromLocalData: React.PropTypes.func,
|
||||
resetLogout: React.PropTypes.func
|
||||
}).isRequired
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
// Any initialization logic for navigation, setting up the client, etc should go here
|
||||
|
||||
this.props.actions.goToSelectServer();
|
||||
if (this.props.logoutRequest.status === RequestStatus.SUCCESS) {
|
||||
this.props.actions.removeStorage().then(() => {
|
||||
setTimeout(this.loadStoreAndScene, 1000);
|
||||
});
|
||||
} else {
|
||||
this.loadStoreAndScene();
|
||||
}
|
||||
}
|
||||
|
||||
loadStoreAndScene = () => {
|
||||
this.props.actions.loadStorage().then(() => {
|
||||
if (this.props.credentials.token && this.props.credentials.url) {
|
||||
this.props.actions.setStoreFromLocalData(this.props.credentials).then(this.props.actions.goToSelectTeam);
|
||||
} else {
|
||||
this.props.actions.goToSelectServer();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
return null;
|
||||
return <Loading/>;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,18 +4,30 @@
|
|||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {goToSelectServer} from 'app/actions/views/root';
|
||||
import {loadStorage, removeStorage} from 'app/actions/storage';
|
||||
import {goToSelectServer, setStoreFromLocalData} from 'app/actions/views/root';
|
||||
import {goToSelectTeam} from 'app/actions/navigation';
|
||||
import {resetLogout} from 'service/actions/users';
|
||||
|
||||
import Root from './root';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
return ownProps;
|
||||
return {
|
||||
...ownProps,
|
||||
credentials: state.entities.general.credentials,
|
||||
logoutRequest: state.requests.users.logout
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
goToSelectServer
|
||||
goToSelectServer,
|
||||
goToSelectTeam,
|
||||
loadStorage,
|
||||
removeStorage,
|
||||
setStoreFromLocalData,
|
||||
resetLogout
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,3 +42,17 @@ export function logClientError(message, level = 'ERROR') {
|
|||
level
|
||||
);
|
||||
}
|
||||
|
||||
export function setAppState(state) {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({type: GeneralTypes.RECEIVED_APP_STATE, data: state}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
getPing,
|
||||
getClientConfig,
|
||||
getLicenseConfig,
|
||||
logClientError,
|
||||
setAppState
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ const HTTP_UNAUTHORIZED = 401;
|
|||
|
||||
export async function forceLogoutIfNecessary(err, dispatch) {
|
||||
if (err.status_code === HTTP_UNAUTHORIZED && err.url.indexOf('/login') === -1) {
|
||||
dispatch({type: UsersTypes.LOGOUT_REQUEST});
|
||||
await Client.logout();
|
||||
dispatch({type: UsersTypes.LOGOUT_SUCCESS});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,10 +22,22 @@ export default class Client {
|
|||
};
|
||||
}
|
||||
|
||||
getUrl() {
|
||||
return this.url;
|
||||
}
|
||||
|
||||
setUrl(url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
getToken() {
|
||||
return this.token;
|
||||
}
|
||||
|
||||
setToken(token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
getBaseRoute() {
|
||||
return `${this.url}${this.urlVersion}`;
|
||||
}
|
||||
|
|
@ -192,6 +204,13 @@ export default class Client {
|
|||
|
||||
if (response.headers.has(HEADER_TOKEN)) {
|
||||
this.token = response.headers.get(HEADER_TOKEN);
|
||||
} else {
|
||||
// weird case where fetch does not parse the header correctly
|
||||
const parseHeader = response.headers.get(HEADER_CONTENT_TYPE).split('\n');
|
||||
const filter = parseHeader.filter((h) => h.indexOf('Token:') > -1);
|
||||
if (filter.length) {
|
||||
this.token = filter[0].replace('Token: ', '');
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
|
|
@ -242,6 +261,13 @@ export default class Client {
|
|||
);
|
||||
};
|
||||
|
||||
getMe = async () => {
|
||||
return this.doFetch(
|
||||
`${this.getUsersRoute()}/me`,
|
||||
{method: 'get'}
|
||||
);
|
||||
};
|
||||
|
||||
getProfiles = async (offset, limit) => {
|
||||
return this.doFetch(
|
||||
`${this.getUsersRoute()}/${offset}/${limit}`,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,10 @@
|
|||
import keymirror from 'keymirror';
|
||||
|
||||
const GeneralTypes = keymirror({
|
||||
RECEIVED_APP_STATE: null,
|
||||
RECEIVED_APP_CREDENTIALS: null,
|
||||
REMOVED_APP_CREDENTIALS: null,
|
||||
|
||||
PING_REQUEST: null,
|
||||
PING_SUCCESS: null,
|
||||
PING_FAILURE: null,
|
||||
|
|
|
|||
|
|
@ -54,7 +54,8 @@ const UserTypes = keymirror({
|
|||
RECEIVED_SESSIONS: null,
|
||||
RECEIVED_REVOKED_SESSION: null,
|
||||
RECEIVED_AUDITS: null,
|
||||
RECEIVED_STATUSES: null
|
||||
RECEIVED_STATUSES: null,
|
||||
RESET_LOGOUT_STATE: null
|
||||
});
|
||||
|
||||
export default UserTypes;
|
||||
|
|
|
|||
|
|
@ -26,7 +26,31 @@ function license(state = {}, action) {
|
|||
}
|
||||
}
|
||||
|
||||
function appState(state = false, action) {
|
||||
switch (action.type) {
|
||||
case GeneralTypes.RECEIVED_APP_STATE:
|
||||
return action.data;
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
function credentials(state = {}, action) {
|
||||
switch (action.type) {
|
||||
case GeneralTypes.RECEIVED_APP_CREDENTIALS:
|
||||
return Object.assign({}, state, action.data);
|
||||
|
||||
case UsersTypes.LOGOUT_SUCCESS:
|
||||
return {};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export default combineReducers({
|
||||
appState,
|
||||
credentials,
|
||||
config,
|
||||
license
|
||||
});
|
||||
|
|
|
|||
|
|
@ -23,13 +23,18 @@ export function handleRequest(REQUEST, SUCCESS, FAILURE, state, action) {
|
|||
status: RequestStatus.SUCCESS,
|
||||
error: null
|
||||
};
|
||||
case FAILURE:
|
||||
case FAILURE: {
|
||||
let error = action.error;
|
||||
if (error instanceof Error) {
|
||||
error = error.toString();
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
status: RequestStatus.FAILURE,
|
||||
error: action.error
|
||||
error
|
||||
};
|
||||
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,13 +26,22 @@ function login(state = initialRequestState(), action) {
|
|||
}
|
||||
|
||||
function logout(state = initialRequestState(), action) {
|
||||
return handleRequest(
|
||||
UsersTypes.LOGOUT_REQUEST,
|
||||
UsersTypes.LOGOUT_SUCCESS,
|
||||
UsersTypes.LOGOUT_FAILURE,
|
||||
state,
|
||||
action
|
||||
);
|
||||
switch (action.type) {
|
||||
case UsersTypes.LOGOUT_REQUEST:
|
||||
return {...state, status: RequestStatus.STARTED};
|
||||
|
||||
case UsersTypes.LOGOUT_SUCCESS:
|
||||
return {...state, status: RequestStatus.SUCCESS, error: null};
|
||||
|
||||
case UsersTypes.LOGOUT_FAILURE:
|
||||
return {...state, status: RequestStatus.FAILURE, error: action.error};
|
||||
|
||||
case UsersTypes.RESET_LOGOUT_STATE:
|
||||
return initialRequestState();
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
function getProfiles(state = initialRequestState(), action) {
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ describe('Actions.Channels', () => {
|
|||
await getProfiles(0)(store.dispatch, store.getState);
|
||||
Actions.createDirectChannel(TestHelper.basicUser.id, user.id)(store.dispatch, store.getState);
|
||||
});
|
||||
});
|
||||
}).timeout(3000);
|
||||
|
||||
it('updateChannel', (done) => {
|
||||
TestHelper.initBasic(Client).then(() => {
|
||||
|
|
|
|||
|
|
@ -317,5 +317,5 @@ describe('Actions.Teams', () => {
|
|||
|
||||
Actions.addUserToTeam(TestHelper.basicTeam.id, user.id)(store.dispatch, store.getState);
|
||||
});
|
||||
});
|
||||
}).timeout(3000);
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue