* Some extra work on voice channels interface * Fixing some TODOs * Improving styling of call in channel * Improve calls monitoring * Replacing some of the fontawesome icons with the compass ones * Improving the layout * Migrating to webrtc2 for unified plan * Add screen on and off behavior * Adding incall manager plugin * Moving everything into the products/calls folder * Make products modules routes relatives * Make products modules routes @mmproducts * Removing initiator parameter * Removing trickle parameter * Simplifying code * Removing underscore from private variables * Removing underscore from private things * More simplifications * More simplifications * More simplifications * Changing sha sum for mmjstool * Fixing typo * Migrating simple-peer to typescript * Migrating simple-peer to typescript * Improving the size of the screen share * Adding feature flag to disable the calls feature in mobile * Fixing some tests * Removing obsolte tests * Added call ended support for the post messages * Fixing some warnings in the tests * Adding JoinCall tests * Adding CallMessage tests * Adding CurrentCall unit tests * Adding CallAvatar unit tests * Adding FloatingCallContainer unit tests * Adding StartCall unit tests * Adding EnableDisableCalls unit tests * Adding CallDuration tests * Improving CallDuration tests * Adding CallScreen unit tests * Adding CallOtherActions screen tests * Fixing some dark theme styles * Fixing tests * More robustness around connecting/disconnecting * Adding FormattedRelativeTime tests * Adding tests for ChannelItem * Adding tests for ChannelInfo * Adding selectors tests * Adding reducers unit tests * Adding actions tests * Removing most of the TODOs * Removing another TODO * Updating tests snapshots * Removing the last TODO * Fixed a small problem on pressing while a call is ongoing * Remove all the inlined functions * Replacing usage of isLandscape selector with useWindowDimensions * Removed unnecesary makeStyleSheetFromTheme * Removing unneded properties from call_duration * Fixing possible null channels return from getChannel selector * Moving other inlined functions to its own constant * Simplifiying enable/disable calls component * Improving the behavior when you are in the call of the current channel * Adding missing translation strings * Simplified a bit the EnableDisableCalls component * Moving other inlined functions to its own constant * Updating snapshots * Improving usage of makeStyleSheetFromTheme * Moving data reformating from the rest client to the redux action * Adding calls to the blocklist to the redux-persist * Fixing tests * Updating snapshots * Update file icon name to the last compass icons version * Fix loading state * Only show the call connected if the websocket gets connected * Taking into consideration the indicator bar to position the calls new bars * Making the MoreMessagesButton component aware of calls components * Updating snapshots * Fixing tests * Updating snapshot * Fixing different use cases for start call channel menu * Fixing tests * Ask for confirmation to start a call when you are already in another call * Update app/products/calls/components/floating_call_container.tsx Co-authored-by: Elias Nahum <nahumhbl@gmail.com> * Memoizing userIds in join call * Applying suggestion around combine the blocklist for calls with the one for typing * Adding explicit types to the rest client * Removing unneeded permission * Making updateIntervalInSeconds prop optional in FormattedRelativeTime * Making updateIntervalInSeconds prop optional in CallDuration Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
265 lines
8.9 KiB
TypeScript
265 lines
8.9 KiB
TypeScript
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
|
|
import DeviceInfo from 'react-native-device-info';
|
|
import * as redux from 'redux';
|
|
import {createPersistoid, createTransform, persistReducer, persistStore, Persistor, PersistConfig} from 'redux-persist';
|
|
import {createBlacklistFilter} from 'redux-persist-transform-filter';
|
|
|
|
import {General} from '@mm-redux/constants';
|
|
import serviceReducer from '@mm-redux/reducers';
|
|
import {GenericAction} from '@mm-redux/types/actions';
|
|
import {GlobalState} from '@mm-redux/types/store';
|
|
import initialState from '@store/initial_state';
|
|
|
|
import appReducer from 'app/reducers';
|
|
|
|
import {createReducer, getStoredState} from './helpers';
|
|
import {createMiddlewares} from './middlewares';
|
|
import Store from './store';
|
|
import {transformSet, serialize} from './utils';
|
|
|
|
/**
|
|
* Configures and constructs the redux store. Accepts the following parameters:
|
|
* preloadedState - Any preloaded state to be applied to the store after it is initially configured.
|
|
* appReducer - An object containing any app-specific reducer functions that the client needs.
|
|
* persistConfig - Configuration for redux-persist.
|
|
* getAppReducer - A function that returns the appReducer as defined above. Only used in development to enable hot reloading.
|
|
* clientOptions - An object containing additional options used when configuring the redux store. The following options are available:
|
|
* additionalMiddleware - func | array - Allows for single or multiple additional middleware functions to be passed in from the client side.
|
|
* enableBuffer - bool - default = true - If true, the store will buffer all actions until offline state rehydration occurs.
|
|
* enableThunk - bool - default = true - If true, include the thunk middleware automatically. If false, thunk must be provided as part of additionalMiddleware.
|
|
*/
|
|
|
|
export type ReduxStore = {
|
|
store: redux.Store;
|
|
persistor: Persistor;
|
|
}
|
|
|
|
type ClientOptions = {
|
|
enableBuffer: boolean;
|
|
enableThunk: boolean;
|
|
}
|
|
|
|
type V4Store = {
|
|
storeKeys: Array<string>;
|
|
restoredState: any;
|
|
}
|
|
|
|
const usersSetTransform = [
|
|
'profilesInChannel',
|
|
'profilesNotInChannel',
|
|
'profilesInTeam',
|
|
'profilesNotInTeam',
|
|
];
|
|
|
|
const channelSetTransform = [
|
|
'channelsInTeam',
|
|
];
|
|
|
|
const rolesSetTransform = [
|
|
'pending',
|
|
];
|
|
|
|
const setTransforms = [
|
|
...usersSetTransform,
|
|
...channelSetTransform,
|
|
...rolesSetTransform,
|
|
];
|
|
|
|
const viewsBlackListFilter = createBlacklistFilter(
|
|
'views',
|
|
['extension', 'root'],
|
|
);
|
|
|
|
const entitiesBlackListFilter = createBlacklistFilter(
|
|
'entities',
|
|
['typing', 'calls'],
|
|
);
|
|
|
|
const channelViewBlackList: any = {loading: true, refreshing: true, loadingPosts: true, retryFailed: true, loadMorePostsVisible: true};
|
|
const channelViewBlackListFilter = createTransform(
|
|
(inboundState: any) => {
|
|
const channel: any = {};
|
|
const keys: Array<string> = inboundState.channel ? Object.keys(inboundState.channel) : [];
|
|
|
|
for (const channelKey of keys) {
|
|
if (!channelViewBlackList[channelKey]) {
|
|
channel[channelKey] = inboundState.channel[channelKey];
|
|
}
|
|
}
|
|
|
|
return {
|
|
...inboundState,
|
|
channel,
|
|
};
|
|
},
|
|
null,
|
|
{whitelist: ['views']}, // Only run this filter on the views state (or any other entry that ends up being named views)
|
|
);
|
|
|
|
const emojiBlackList: any = {nonExistentEmoji: true};
|
|
const emojiBlackListFilter = createTransform(
|
|
(inboundState: any) => {
|
|
const emojis: any = {};
|
|
const keys: Array<string> = inboundState.emojis ? Object.keys(inboundState.emojis) : [];
|
|
|
|
for (const emojiKey of keys) {
|
|
if (!emojiBlackList[emojiKey]) {
|
|
emojis[emojiKey] = inboundState.emojis[emojiKey];
|
|
}
|
|
}
|
|
|
|
return {
|
|
...inboundState,
|
|
emojis,
|
|
};
|
|
},
|
|
null,
|
|
{whitelist: ['entities']}, // Only run this filter on the entities state (or any other entry that ends up being named entities)
|
|
);
|
|
|
|
const setTransformer = createTransform(
|
|
(inboundState: any, key: string) => {
|
|
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: any, key: string) => {
|
|
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;
|
|
},
|
|
);
|
|
|
|
/**
|
|
* Function to configure the redux store with persistence
|
|
* @param storage the storage engine to use
|
|
* @param preloadedState (optional) the initial state to use (applies to tests)
|
|
* @param optionalConfig (optional) persist configuration (applies to tests)
|
|
* @param optionalOptions (optional) middleware configuration (applies to tests)
|
|
*/
|
|
export default function configureStore(storage: any, preloadedState: any = {}, optionalConfig: any = {}, optionalOptions = {}): ReduxStore {
|
|
const defaultOptions: ClientOptions = {
|
|
enableBuffer: true,
|
|
enableThunk: true,
|
|
};
|
|
|
|
const defaultConfig: PersistConfig<GlobalState> = {
|
|
key: 'root',
|
|
storage,
|
|
serialize,
|
|
deserialize: false,
|
|
blacklist: ['device', 'navigation', 'requests', '_persist'],
|
|
transforms: [
|
|
setTransformer,
|
|
viewsBlackListFilter,
|
|
entitiesBlackListFilter,
|
|
channelViewBlackListFilter,
|
|
emojiBlackListFilter,
|
|
],
|
|
throttle: 100,
|
|
timeout: 60000,
|
|
};
|
|
|
|
const persistConfig: PersistConfig<GlobalState> = Object.assign({}, defaultConfig, optionalConfig);
|
|
const baseState: any = Object.assign({}, initialState, preloadedState);
|
|
const baseReducer: any = createReducer(serviceReducer as any, appReducer as any);
|
|
const rootReducer: any = (state: GlobalState, action: GenericAction) => {
|
|
if (action.type === General.OFFLINE_STORE_PURGE) {
|
|
// eslint-disable-next-line no-underscore-dangle
|
|
if (action.data?._persist) {
|
|
// eslint-disable-next-line no-underscore-dangle
|
|
delete action?.data?._persist;
|
|
}
|
|
return baseReducer(action.data, action as any);
|
|
}
|
|
return baseReducer(state as any, action as any);
|
|
};
|
|
const persistedReducer = persistReducer({...persistConfig}, rootReducer);
|
|
const options: ClientOptions = Object.assign({}, defaultOptions, optionalOptions);
|
|
|
|
const store = redux.createStore(
|
|
persistedReducer,
|
|
baseState,
|
|
redux.compose(
|
|
redux.applyMiddleware(
|
|
...createMiddlewares(options),
|
|
),
|
|
),
|
|
);
|
|
|
|
const persistor = persistStore(store, null);
|
|
|
|
getStoredState().then(({storeKeys, restoredState}: V4Store) => {
|
|
if (Object.keys(restoredState).length) {
|
|
const {app} = restoredState;
|
|
app.previousVersion = app.version;
|
|
app.build = DeviceInfo.getBuildNumber();
|
|
app.version = DeviceInfo.getVersion();
|
|
|
|
const state = {
|
|
...restoredState,
|
|
app: {
|
|
...app,
|
|
},
|
|
views: {
|
|
...restoredState.views,
|
|
root: {
|
|
hydrationComplete: false,
|
|
},
|
|
},
|
|
_persist: persistor.getState(),
|
|
};
|
|
|
|
store.dispatch({
|
|
type: General.OFFLINE_STORE_PURGE,
|
|
data: state,
|
|
});
|
|
|
|
console.log('HYDRATED FROM v4', storeKeys); // eslint-disable-line no-console
|
|
const persistoid = createPersistoid(persistConfig);
|
|
store.subscribe(() => {
|
|
persistoid.update(store.getState());
|
|
});
|
|
store.dispatch({type: General.REHYDRATED});
|
|
AsyncStorage.multiRemove(storeKeys);
|
|
} else if (store.getState()._persist?.rehydrated) { // eslint-disable-line no-underscore-dangle
|
|
store.dispatch({type: General.REHYDRATED});
|
|
} else {
|
|
let executed = false;
|
|
const unsubscribe = store.subscribe(() => {
|
|
if (store.getState()._persist?.rehydrated && !executed) { // eslint-disable-line no-underscore-dangle
|
|
unsubscribe();
|
|
executed = true;
|
|
store.dispatch({type: General.REHYDRATED});
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
Store.redux = store;
|
|
Store.persistor = persistor;
|
|
|
|
return {store, persistor};
|
|
}
|