mattermost-mobile/app/store/index.js
Chris Duarte 7fdb0c4d27 Create post timeout (#1242)
* Add timeout for api calls that take too long

This will allow any api call that takes too long to fail faster.

Specially for Client4.createPost.  If the api never returns the post is in a pending state and the user is not able to retry.

* Add canTimeout flag
2017-12-08 18:19:17 -03:00

218 lines
7.5 KiB
JavaScript

// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {batchActions} from 'redux-batched-actions';
import {AsyncStorage} from 'react-native';
import {createBlacklistFilter} from 'redux-persist-transform-filter';
import {createTransform, persistStore} from 'redux-persist';
import {ErrorTypes, GeneralTypes} from 'mattermost-redux/action_types';
import {General, RequestStatus} from 'mattermost-redux/constants';
import configureStore from 'mattermost-redux/store';
import EventEmitter from 'mattermost-redux/utils/event_emitter';
import {NavigationTypes, ViewTypes} from 'app/constants';
import appReducer from 'app/reducers';
import networkConnectionListener from 'app/utils/network';
import {createSentryMiddleware} from 'app/utils/sentry/middleware';
import {promiseTimeout} from 'app/utils/promise_timeout';
import {messageRetention} from './middleware';
import {transformSet} from './utils';
function getAppReducer() {
return require('../../app/reducers'); // eslint-disable-line global-require
}
const usersSetTransform = [
'profilesInChannel',
'profilesNotInChannel',
'profilesInTeam',
'profilesNotInTeam'
];
const channelSetTransform = [
'channelsInTeam'
];
const setTransforms = [
...usersSetTransform,
...channelSetTransform
];
export default function configureAppStore(initialState) {
const viewsBlackListFilter = createBlacklistFilter(
'views',
['login', 'root']
);
const typingBlackListFilter = createBlacklistFilter(
'entities',
['typing']
);
const channelViewBlackList = {loading: true, refreshing: true, loadingPosts: true, postVisibility: true, retryFailed: true};
const channelViewBlackListFilter = createTransform(
(inboundState) => {
const channel = {};
for (const channelKey of Object.keys(inboundState.channel)) {
if (!channelViewBlackList[channelKey]) {
channel[channelKey] = inboundState.channel[channelKey];
}
}
return {
...inboundState,
channel
};
},
null,
{whitelist: ['views']} // Only run this filter the views state (or any other entry that ends up being named views)
);
const setTransformer = createTransform(
(inboundState, key) => {
if (key === 'entities') {
const state = {...inboundState};
for (const prop in state) {
if (state.hasOwnProperty(prop)) {
state[prop] = transformSet(state[prop], setTransforms);
}
}
return state;
}
return inboundState;
},
(outboundState, key) => {
if (key === 'entities') {
const state = {...outboundState};
for (const prop in state) {
if (state.hasOwnProperty(prop)) {
state[prop] = transformSet(state[prop], setTransforms, false);
}
}
return state;
}
return outboundState;
}
);
const offlineOptions = {
effect: (effect, action) => {
if (typeof effect !== 'function') {
throw new Error('Offline Action: effect must be a function.');
} else if (!action.meta.offline.commit) {
throw new Error('Offline Action: commit action must be present.');
}
if (action.meta.offline.canTimeout) {
const defaultTimeout = 10000;
const timeout = action.meta.offline.timeout || defaultTimeout;
return promiseTimeout(effect(), timeout);
}
return effect();
},
detectNetwork: (callback) => networkConnectionListener(callback),
persist: (store, options) => {
const persistor = persistStore(store, {storage: AsyncStorage, ...options}, () => {
store.dispatch({
type: General.STORE_REHYDRATION_COMPLETE,
complete: true
});
});
let purging = false;
// check to see if the logout request was successful
store.subscribe(async () => {
const state = store.getState();
if ((state.requests.users.logout.status === RequestStatus.SUCCESS || state.requests.users.logout.status === RequestStatus.FAILURE) && !purging) {
purging = true;
await persistor.purge();
store.dispatch(batchActions([
{
type: General.OFFLINE_STORE_RESET,
data: initialState
},
{
type: ViewTypes.SERVER_URL_CHANGED,
serverUrl: state.entities.general.credentials.url || state.views.selectServer.serverUrl
},
{
type: GeneralTypes.RECEIVED_APP_DEVICE_TOKEN,
data: state.entities.general.deviceToken
}
]));
setTimeout(() => {
purging = false;
}, 500);
} else if (state.views.root.purge && !purging) {
purging = true;
await persistor.purge();
store.dispatch(batchActions([
{
type: General.OFFLINE_STORE_RESET,
data: initialState
},
{
type: ErrorTypes.RESTORE_ERRORS,
data: [...state.errors]
},
{
type: GeneralTypes.RECEIVED_APP_DEVICE_TOKEN,
data: state.entities.general.deviceToken
},
{
type: GeneralTypes.RECEIVED_APP_CREDENTIALS,
data: {
url: state.entities.general.credentials.url,
token: state.entities.general.credentials.token
}
},
{
type: ViewTypes.SERVER_URL_CHANGED,
serverUrl: state.entities.general.credentials.url || state.views.selectServer.serverUrl
}
], 'BATCH_FOR_RESTART'));
setTimeout(() => {
purging = false;
EventEmitter.emit(NavigationTypes.RESTART_APP);
}, 500);
}
});
return persistor;
},
persistOptions: {
autoRehydrate: {
log: false
},
blacklist: ['device', 'navigation', 'offline', 'requests'],
debounce: 500,
transforms: [
setTransformer,
viewsBlackListFilter,
typingBlackListFilter,
channelViewBlackListFilter
]
}
};
const additionalMiddleware = [createSentryMiddleware(), messageRetention];
return configureStore(initialState, appReducer, offlineOptions, getAppReducer, {
additionalMiddleware
});
}