MM-23848 consolidate store, upgrade mmkv and fix logout (#4145)

* MM-23848 consolidate store, upgrade mmkv and fix logout

* Feedback review

* Add store.ts to modulesPath
This commit is contained in:
Elias Nahum 2020-04-16 09:02:47 -04:00 committed by GitHub
parent b025ed0f01
commit fda4948c8c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
60 changed files with 1057 additions and 1527 deletions

View file

@ -7,13 +7,13 @@ import merge from 'deepmerge';
import {getTheme} from '@mm-redux/selectors/entities/preferences';
import store from 'app/store';
import EphemeralStore from 'app/store/ephemeral_store';
import EphemeralStore from '@store/ephemeral_store';
import Store from '@store/store';
const CHANNEL_SCREEN = 'Channel';
function getThemeFromState() {
const state = store.getState();
const state = Store.redux?.getState() || {};
return getTheme(state);
}

View file

@ -3,21 +3,27 @@
import {Platform} from 'react-native';
import {Navigation} from 'react-native-navigation';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import merge from 'deepmerge';
import * as NavigationActions from '@actions/navigation';
import Preferences from '@mm-redux/constants/preferences';
import EphemeralStore from '@store/ephemeral_store';
import intitialState from '@store/initial_state';
import Store from '@store/store';
import EphemeralStore from 'app/store/ephemeral_store';
import * as NavigationActions from 'app/actions/navigation';
jest.unmock('app/actions/navigation');
jest.mock('app/store/ephemeral_store', () => ({
jest.unmock('@actions/navigation');
jest.mock('@store/ephemeral_store', () => ({
getNavigationTopComponentId: jest.fn(),
clearNavigationComponents: jest.fn(),
}));
describe('app/actions/navigation', () => {
const mockStore = configureMockStore([thunk]);
const store = mockStore(intitialState);
Store.redux = store;
describe('@actions/navigation', () => {
const topComponentId = 'top-component-id';
const name = 'name';
const title = 'title';

View file

@ -4,20 +4,20 @@
import configureStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import initialState from 'app/initial_state';
import {ChannelTypes} from '@mm-redux/action_types';
import testHelper from 'test/test_helper';
import {ViewTypes} from 'app/constants';
import * as ChannelActions from 'app/actions/views/channel';
import * as ChannelActions from '@actions/views/channel';
import {ViewTypes} from '@constants';
import {ChannelTypes} from '@mm-redux/action_types';
import postReducer from '@mm-redux/reducers/entities/posts';
import initialState from '@store/initial_state';
const {
handleSelectChannel,
handleSelectChannelByName,
loadPostsIfNecessaryWithRetry,
} = ChannelActions;
import postReducer from '@mm-redux/reducers/entities/posts';
const MOCK_CHANNEL_MARK_AS_READ = 'MOCK_CHANNEL_MARK_AS_READ';
const MOCK_CHANNEL_MARK_AS_VIEWED = 'MOCK_CHANNEL_MARK_AS_VIEWED';

View file

@ -10,8 +10,8 @@ import {PostTypes, UserTypes} from '@mm-redux/action_types';
import * as PostSelectors from '@mm-redux/selectors/entities/posts';
import * as ChannelUtils from '@mm-redux/utils/channel_utils';
import {ViewTypes} from 'app/constants';
import initialState from 'app/initial_state';
import {ViewTypes} from '@constants';
import initialState from '@store/initial_state';
import {loadUnreadChannelPosts} from '@actions/views/post';

View file

@ -13,11 +13,10 @@ import {General} from '@mm-redux/constants';
import EventEmitter from '@mm-redux/utils/event_emitter';
import {NavigationTypes, ViewTypes} from '@constants';
import initialState from 'app/initial_state';
import {persistor} from 'app/store';
import EphemeralStore from 'app/store/ephemeral_store';
import {getStateForReset} from 'app/store/utils';
import {recordTime} from 'app/utils/segment';
import EphemeralStore from '@store/ephemeral_store';
import initialState from '@store/initial_state';
import {getStateForReset} from '@store/utils';
import {recordTime} from '@utils/segment';
import {markChannelViewedAndRead} from './channel';
@ -144,13 +143,11 @@ export function handleSelectTeamAndChannel(teamId, channelId) {
export function purgeOfflineStore() {
return (dispatch, getState) => {
const currentState = getState();
persistor.purge();
dispatch({
type: General.OFFLINE_STORE_PURGE,
state: getStateForReset(initialState, currentState),
});
persistor.flush();
persistor.persist();
EventEmitter.emit(NavigationTypes.RESTART_APP);
};

View file

@ -21,10 +21,10 @@ import EventEmitter from '@mm-redux/utils/event_emitter';
import * as Actions from '@actions/websocket';
import {WebsocketEvents} from '@constants';
import initial_state from '@store/initial_state';
import TestHelper from 'test/test_helper';
import configureStore from 'test/test_store';
import initial_state from 'app/initial_state';
global.WebSocket = MockWebSocket;

View file

@ -64,7 +64,6 @@ export const removeAppCredentials = async () => {
KeyChain.resetInternetCredentials(url);
}
KeyChain.resetGenericPassword();
EphemeralStore.currentServerUrl = null;
AsyncStorage.removeItem(CURRENT_SERVER);
};
@ -90,9 +89,7 @@ async function getCredentialsFromGenericKeyChain() {
Client4.setToken(token);
await setCSRFFromCookie(url);
// Migration: remove the generic credentials and add a server specific one
setAppCredentials(deviceToken, currentUserId, token, url);
KeyChain.resetGenericPassword();
return {
username: usernameParsed,

View file

@ -3,12 +3,14 @@
import {Alert, Platform} from 'react-native';
import {setServerUrl} from 'app/actions/views/select_server';
import {getTranslations} from 'app/i18n';
import {setServerUrl} from '@actions/views/select_server';
import {getTranslations} from '@i18n';
import {getCurrentLocale} from '@selectors/i18n';
import Store from '@store/store';
import {t} from '@utils/i18n';
import mattermostBucket from 'app/mattermost_bucket';
import mattermostManaged from 'app/mattermost_managed';
import {getCurrentLocale} from 'app/selectors/i18n';
import {t} from 'app/utils/i18n';
import {getAppCredentials} from './credentials';
@ -29,11 +31,11 @@ class EMMProvider {
this.emmServerUrl = null;
}
checkIfDeviceIsTrusted = (store) => {
checkIfDeviceIsTrusted = () => {
const isTrusted = mattermostManaged.isTrustedDevice();
if (!isTrusted) {
const state = store.getState();
const state = Store.redux.getState();
const locale = getCurrentLocale(state);
const translations = getTranslations(locale);
Alert.alert(
@ -51,10 +53,10 @@ class EMMProvider {
}
};
handleAuthentication = async (store, prompt = true) => {
handleAuthentication = async (prompt = true) => {
this.performingAuthentication = true;
const isSecured = await mattermostManaged.isDeviceSecure();
const state = store.getState();
const state = Store.redux.getState();
const locale = getCurrentLocale(state);
const translations = getTranslations(locale);
@ -83,12 +85,12 @@ class EMMProvider {
return true;
};
handleManagedConfig = async (store) => {
handleManagedConfig = async () => {
if (this.performingAuthentication) {
return true;
}
const {dispatch} = store;
const {dispatch} = Store.redux;
if (LocalConfig.AutoSelectServerUrl) {
dispatch(setServerUrl(LocalConfig.DefaultServerUrl));

View file

@ -29,11 +29,12 @@ import {NavigationTypes, ViewTypes} from '@constants';
import {getTranslations, resetMomentLocale} from '@i18n';
import PushNotifications from 'app/push_notifications';
import {getCurrentLocale} from '@selectors/i18n';
import initialState from '@store/initial_state';
import Store from '@store/store';
import {t} from '@utils/i18n';
import {deleteFileCache} from '@utils/file';
import {getDeviceTimezoneAsync} from '@utils/timezone';
import initialState from 'app/initial_state';
import mattermostBucket from 'app/mattermost_bucket';
import mattermostManaged from 'app/mattermost_managed';
import LocalConfig from 'assets/config';
@ -66,7 +67,7 @@ class GlobalEventHandler {
// Once the app becomes active we check if the device needs to have a passcode set
const prompt = emmProvider.inBackgroundSince && authExpired; // if more than 5 minutes have passed prompt for passcode
await emmProvider.handleAuthentication(this.store, prompt);
await emmProvider.handleAuthentication(prompt);
}
emmProvider.inBackgroundSince = null; /* eslint-disable-line require-atomic-updates */
@ -75,7 +76,7 @@ class GlobalEventHandler {
appInactive = () => {
this.turnOffInAppNotificationHandling();
const {dispatch} = this.store;
const {dispatch} = Store.redux;
// When the app is sent to the background we set the time when that happens
// and perform a data clean up to improve on performance
@ -85,7 +86,6 @@ class GlobalEventHandler {
};
configure = (opts) => {
this.store = opts.store;
this.launchApp = opts.launchApp;
// onAppStateChange may be called by the AppState listener before we
@ -107,7 +107,7 @@ class GlobalEventHandler {
}
this.JavascriptAndNativeErrorHandler = require('app/utils/error_handling').default;
this.JavascriptAndNativeErrorHandler.initializeErrorHandling(this.store);
this.JavascriptAndNativeErrorHandler.initializeErrorHandling(Store.redux);
mattermostManaged.addEventListener('managedConfigDidChange', this.onManagedConfigurationChange);
};
@ -126,8 +126,8 @@ class GlobalEventHandler {
const isActive = appState === 'active';
const isBackground = appState === 'background';
if (this.store) {
this.store.dispatch(setAppState(isActive));
if (Store.redux) {
Store.redux.dispatch(setAppState(isActive));
if (isActive && (!emmProvider.enabled || emmProvider.previousAppState === 'background')) {
this.appActive();
@ -142,12 +142,12 @@ class GlobalEventHandler {
onDeepLink = (event) => {
const {url} = event;
if (url) {
this.store.dispatch(setDeepLinkURL(url));
Store.redux.dispatch(setDeepLinkURL(url));
}
};
onManagedConfigurationChange = () => {
emmProvider.handleManagedConfig(this.store, true);
emmProvider.handleManagedConfig(true);
};
onServerConfigChanged = (config) => {
@ -155,9 +155,9 @@ class GlobalEventHandler {
};
onLogout = async () => {
this.store.dispatch(closeWebSocket(false));
this.store.dispatch(setServerVersion(''));
this.resetState();
Store.redux.dispatch(closeWebSocket(false));
Store.redux.dispatch(setServerVersion(''));
await this.resetState();
removeAppCredentials();
deleteFileCache();
resetMomentLocale();
@ -190,8 +190,8 @@ class GlobalEventHandler {
};
onOrientationChange = (dimensions) => {
if (this.store) {
const {dispatch, getState} = this.store;
if (Store.redux) {
const {dispatch, getState} = Store.redux;
const deviceState = getState().device;
if (DeviceInfo.isTablet()) {
@ -215,7 +215,7 @@ class GlobalEventHandler {
};
onRestartApp = async () => {
const {dispatch, getState} = this.store;
const {dispatch, getState} = Store.redux;
const state = getState();
const {currentUserId} = state.entities.users;
const user = getUser(state, currentUserId);
@ -236,7 +236,7 @@ class GlobalEventHandler {
};
onServerVersionChanged = async (serverVersion) => {
const {dispatch, getState} = this.store;
const {dispatch, getState} = Store.redux;
const state = getState();
const match = serverVersion && serverVersion.match(/^[0-9]*.[0-9]*.[0-9]*(-[a-zA-Z0-9.-]*)?/g);
const version = match && match[0];
@ -263,17 +263,17 @@ class GlobalEventHandler {
};
onStatusBarHeightChange = (nextStatusBarHeight) => {
this.store.dispatch(setStatusBarHeight(nextStatusBarHeight));
Store.redux.dispatch(setStatusBarHeight(nextStatusBarHeight));
};
onSwitchToDefaultChannel = (teamId) => {
this.store.dispatch(selectDefaultChannel(teamId));
Store.redux.dispatch(selectDefaultChannel(teamId));
};
resetState = async () => {
try {
await AsyncStorage.clear();
const state = this.store.getState();
const state = Store.redux.getState();
const newState = {
...initialState,
entities: {
@ -294,19 +294,23 @@ class GlobalEventHandler {
serverUrl: state.views.selectServer.serverUrl,
},
},
_persist: {
rehydrated: true,
},
};
this.store.dispatch({
return Store.redux.dispatch({
type: General.OFFLINE_STORE_PURGE,
state: newState,
});
} catch (e) {
// clear error
return e;
}
}
serverUpgradeNeeded = async () => {
const {dispatch} = this.store;
const {dispatch} = Store.redux;
dispatch(setServerVersion(''));
Client4.serverVersion = '';
@ -328,7 +332,7 @@ class GlobalEventHandler {
handleInAppNotification = (notification) => {
const {data} = notification;
const {getState} = this.store;
const {getState} = Store.redux;
const state = getState();
const currentChannelId = getCurrentChannelId(state);
@ -344,7 +348,7 @@ class GlobalEventHandler {
};
setUserTimezone = async () => {
const {dispatch, getState} = this.store;
const {dispatch, getState} = Store.redux;
const state = getState();
const currentUserId = getCurrentUserId(state);

View file

@ -6,10 +6,11 @@ import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import semver from 'semver/preload';
import intitialState from 'app/initial_state';
import PushNotification from 'app/push_notifications';
import mattermostBucket from 'app/mattermost_bucket';
import * as I18n from 'app/i18n';
import * as I18n from '@i18n';
import Store from '@store/store';
import intitialState from '@store/initial_state';
import {MinServerVersion} from 'assets/config';
@ -51,7 +52,7 @@ jest.mock('app/actions/views/root', () => ({
const mockStore = configureMockStore([thunk]);
const store = mockStore(intitialState);
GlobalEventHandler.store = store;
Store.redux = store;
// TODO: Add Android test as part of https://mattermost.atlassian.net/browse/MM-17110
describe('GlobalEventHandler', () => {
@ -71,15 +72,16 @@ describe('GlobalEventHandler', () => {
it('should call onAppStateChange after configuration', () => {
const onAppStateChange = jest.spyOn(GlobalEventHandler, 'onAppStateChange');
GlobalEventHandler.configure({store});
expect(GlobalEventHandler.store).not.toBeNull();
Store.redux = store;
GlobalEventHandler.configure({launchApp: jest.fn()});
expect(Store.redux).not.toBeNull();
expect(onAppStateChange).toHaveBeenCalledWith('active');
});
it('should handle onAppStateChange to active if the store set', () => {
const appActive = jest.spyOn(GlobalEventHandler, 'appActive');
const appInactive = jest.spyOn(GlobalEventHandler, 'appInactive');
expect(GlobalEventHandler.store).not.toBeNull();
expect(Store.redux).not.toBeNull();
GlobalEventHandler.onAppStateChange('active');
expect(appActive).toHaveBeenCalled();
@ -89,7 +91,7 @@ describe('GlobalEventHandler', () => {
it('should handle onAppStateChange to background if the store set', () => {
const appActive = jest.spyOn(GlobalEventHandler, 'appActive');
const appInactive = jest.spyOn(GlobalEventHandler, 'appInactive');
expect(GlobalEventHandler.store).not.toBeNull();
expect(Store.redux).not.toBeNull();
GlobalEventHandler.onAppStateChange('background');
expect(appActive).not.toHaveBeenCalled();
@ -99,7 +101,7 @@ describe('GlobalEventHandler', () => {
it('should not handle onAppStateChange if the store is not set', () => {
const appActive = jest.spyOn(GlobalEventHandler, 'appActive');
const appInactive = jest.spyOn(GlobalEventHandler, 'appInactive');
GlobalEventHandler.store = null;
Store.redux = null;
GlobalEventHandler.onAppStateChange('active');
expect(appActive).not.toHaveBeenCalled();
@ -114,8 +116,9 @@ describe('GlobalEventHandler', () => {
const onAppStateChange = jest.spyOn(GlobalEventHandler, 'onAppStateChange');
const setUserTimezone = jest.spyOn(GlobalEventHandler, 'setUserTimezone');
GlobalEventHandler.configure({store});
expect(GlobalEventHandler.store).not.toBeNull();
Store.redux = store;
GlobalEventHandler.configure({launchApp: jest.fn()});
expect(Store.redux).not.toBeNull();
expect(onAppStateChange).toHaveBeenCalledWith('active');
expect(setUserTimezone).toHaveBeenCalledTimes(1);
});
@ -127,7 +130,7 @@ describe('GlobalEventHandler', () => {
const minVersion = semver.parse(MinServerVersion);
const currentUserId = 'current-user-id';
GlobalEventHandler.store.getState = jest.fn().mockReturnValue({
Store.redux.getState = jest.fn().mockReturnValue({
entities: {
users: {
currentUserId,
@ -137,9 +140,9 @@ describe('GlobalEventHandler', () => {
},
},
});
GlobalEventHandler.store.dispatch = jest.fn().mockReturnValue({});
Store.redux.dispatch = jest.fn().mockReturnValue({});
const dispatch = jest.spyOn(GlobalEventHandler.store, 'dispatch');
const dispatch = jest.spyOn(Store.redux, 'dispatch');
const configureAnalytics = jest.spyOn(GlobalEventHandler, 'configureAnalytics');
const alert = jest.spyOn(Alert, 'alert');

View file

@ -7,34 +7,38 @@ import {Provider} from 'react-redux';
import EventEmitter from '@mm-redux/utils/event_emitter';
import {loadMe} from 'app/actions/views/user';
import {resetToChannel, resetToSelectServer} from 'app/actions/navigation';
import {setDeepLinkURL} from 'app/actions/views/root';
import {NavigationTypes} from 'app/constants';
import {getAppCredentials} from 'app/init/credentials';
import emmProvider from 'app/init/emm_provider';
import 'app/init/device';
import 'app/init/fetch';
import globalEventHandler from 'app/init/global_event_handler';
import {registerScreens} from 'app/screens';
import store, {persistor} from 'app/store';
import {waitForHydration} from 'app/store/utils';
import EphemeralStore from 'app/store/ephemeral_store';
import {resetToChannel, resetToSelectServer} from '@actions/navigation';
import {setDeepLinkURL} from '@actions/views/root';
import {loadMe, logout} from '@actions/views/user';
import telemetry from 'app/telemetry';
import {validatePreviousVersion} from 'app/utils/general';
import pushNotificationsUtils from 'app/utils/push_notifications';
import {NavigationTypes} from '@constants';
import {getAppCredentials} from '@init/credentials';
import emmProvider from '@init/emm_provider';
import '@init/device';
import '@init/fetch';
import globalEventHandler from '@init/global_event_handler';
import {registerScreens} from '@screens';
import configureStore from '@store';
import EphemeralStore from '@store/ephemeral_store';
import getStorage from '@store/mmkv_adapter';
import Store from '@store/store';
import {waitForHydration} from '@store/utils';
import {validatePreviousVersion} from '@utils/general';
import pushNotificationsUtils from '@utils/push_notifications';
const init = async () => {
const credentials = await getAppCredentials();
const dt = Date.now();
const MMKVStorage = await getStorage();
const {store} = configureStore(MMKVStorage);
if (EphemeralStore.appStarted) {
launchApp(credentials);
return;
}
pushNotificationsUtils.configure(store);
pushNotificationsUtils.configure();
globalEventHandler.configure({
store,
launchApp,
});
@ -52,9 +56,9 @@ const launchApp = (credentials) => {
]);
if (credentials) {
waitForHydration(store, async () => {
if (validatePreviousVersion(store, EphemeralStore.prevAppVersion)) {
store.dispatch(loadMe());
waitForHydration(Store.redux, async () => {
if (validatePreviousVersion(Store.redux, EphemeralStore.prevAppVersion)) {
Store.redux.dispatch(loadMe());
resetToChannel({skipMetrics: true});
}
});
@ -67,13 +71,13 @@ const launchApp = (credentials) => {
Linking.getInitialURL().then((url) => {
if (url) {
store.dispatch(setDeepLinkURL(url));
Store.redux.dispatch(setDeepLinkURL(url));
}
});
};
const launchAppAndAuthenticateIfNeeded = async (credentials) => {
await emmProvider.handleManagedConfig(store);
await emmProvider.handleManagedConfig();
await launchApp(credentials);
if (emmProvider.enabled) {
@ -82,7 +86,7 @@ const launchAppAndAuthenticateIfNeeded = async (credentials) => {
}
if (emmProvider.inAppPinCode) {
await emmProvider.handleAuthentication(store);
await emmProvider.handleAuthentication();
}
}
};

View file

@ -15,7 +15,7 @@ import configureStore from 'test/test_store';
import deepFreeze from '@mm-redux/utils/deep_freeze';
import EventEmitter from '@mm-redux/utils/event_emitter';
import initialState from '@mm-redux/store/initial_state';
import initialState from '@store/initial_state';
const OK_RESPONSE = {status: 'OK'};
const UNAUTHORIZED = {status_code: 401};

View file

@ -7,8 +7,7 @@ import * as client from './client';
import * as constants from './constants';
import * as reducers from './reducers';
import * as selectors from './selectors';
import store from './store';
import * as types from './types';
import * as utils from './utils';
export {action_types, actions, client, constants, reducers, selectors, store, types, utils};
export {action_types, actions, client, constants, reducers, selectors, types, utils};

View file

@ -1,108 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import * as redux from 'redux';
import {persistReducer, persistStore, Persistor, createPersistoid} from 'redux-persist';
import AsyncStorage from '@react-native-community/async-storage';
import {General} from '@mm-redux/constants';
import serviceReducer from '@mm-redux/reducers';
import {createReducer, getStoredState} from './helpers';
import initialState from './initial_state';
import {createMiddleware} from './middleware';
import reducerRegistry from './reducer_registry';
/**
* 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.
*/
type ReduxStore = {
store: redux.Store;
persistor: Persistor;
}
type ClientOptions = {
additionalMiddleware: [];
enableBuffer: boolean;
enableThunk: boolean;
enhancers: [];
}
type V4Store = {
storeKeys: Array<string>;
restoredState: any;
}
const defaultClientOptions: ClientOptions = {
additionalMiddleware: [],
enableBuffer: true,
enableThunk: true,
enhancers: [],
};
export default function configureStore(preloadedState: any, appReducer: any, persistConfig: any, clientOptions: ClientOptions): ReduxStore {
const baseState = Object.assign({}, initialState, preloadedState);
const rootReducer = createReducer(serviceReducer as any, appReducer);
const persistedReducer = persistReducer({...persistConfig}, rootReducer);
const options = Object.assign({}, defaultClientOptions, clientOptions);
const store = redux.createStore(
persistedReducer,
baseState,
redux.compose(
redux.applyMiddleware(
...createMiddleware(options),
),
...options.enhancers,
),
);
reducerRegistry.setChangeListener((reducers: any) => {
const reducer = persistReducer(persistConfig, createReducer(baseState, reducers));
store.replaceReducer(reducer);
});
const persistor = persistStore(store, null);
getStoredState().then(({storeKeys, restoredState}: V4Store) => {
if (Object.keys(restoredState).length) {
const state = {
...restoredState,
views: {
...restoredState.views,
root: {
hydrationComplete: true,
},
},
_persist: persistor.getState(),
};
store.dispatch({
type: General.OFFLINE_STORE_PURGE,
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 {
store.dispatch({type: General.REHYDRATED});
}
});
return {store, persistor};
}

View file

@ -1,266 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {GlobalState} from '@mm-redux/types/store';
const state: GlobalState = {
entities: {
general: {
appState: false,
credentials: {},
config: {},
dataRetentionPolicy: {},
deviceToken: '',
license: {},
serverVersion: '',
timezones: [],
},
users: {
currentUserId: '',
isManualStatus: {},
mySessions: [],
myAudits: [],
profiles: {},
profilesInTeam: {},
profilesNotInTeam: {},
profilesWithoutTeam: new Set(),
profilesInChannel: {},
profilesNotInChannel: {},
statuses: {},
stats: {},
},
teams: {
currentTeamId: '',
teams: {},
myMembers: {},
membersInTeam: {},
stats: {},
groupsAssociatedToTeam: {},
totalCount: 0,
},
channels: {
currentChannelId: '',
channels: {},
channelsInTeam: {},
myMembers: {},
membersInChannel: {},
stats: {},
groupsAssociatedToChannel: {},
totalCount: 0,
manuallyUnread: {},
channelModerations: {},
},
posts: {
expandedURLs: {},
posts: {},
postsInChannel: {},
postsInThread: {},
pendingPostIds: [],
reactions: {},
openGraph: {},
selectedPostId: '',
currentFocusedPostId: '',
messagesHistory: {
messages: [],
index: {
post: -1,
comment: -1,
},
},
},
preferences: {
myPreferences: {},
},
bots: {
accounts: {},
},
jobs: {
jobs: {},
jobsByTypeList: {},
},
integrations: {
incomingHooks: {},
outgoingHooks: {},
oauthApps: {},
systemCommands: {},
commands: {},
},
files: {
files: {},
fileIdsByPostId: {},
},
emojis: {
customEmoji: {},
nonExistentEmoji: new Set(),
},
search: {
results: [],
current: {},
recent: {},
matches: {},
flagged: [],
pinned: {},
isSearchingTerm: false,
isSearchGettingMore: false,
},
typing: {},
roles: {
roles: {},
pending: new Set(),
},
gifs: {
categories: {
tagsList: [],
tagsDict: {},
},
cache: {
gifs: {},
updating: false,
},
search: {
searchText: '',
searchBarText: '',
resultsByTerm: {},
scrollPosition: 0,
priorLocation: null,
},
},
schemes: {
schemes: {},
},
groups: {
groups: {},
syncables: {},
members: {},
},
channelCategories: {
byId: {},
orderByTeam: {},
},
},
errors: [],
requests: {
channels: {
getAllChannels: {
status: 'not_started',
error: null,
},
getChannels: {
status: 'not_started',
error: null,
},
myChannels: {
status: 'not_started',
error: null,
},
createChannel: {
status: 'not_started',
error: null,
},
updateChannel: {
status: 'not_started',
error: null,
},
},
general: {
websocket: {
status: 'not_started',
error: null,
},
},
posts: {
createPost: {
status: 'not_started',
error: null,
},
editPost: {
status: 'not_started',
error: null,
},
getPostThread: {
status: 'not_started',
error: null,
},
},
teams: {
getMyTeams: {
status: 'not_started',
error: null,
},
getTeams: {
status: 'not_started',
error: null,
},
joinTeam: {
status: 'not_started',
error: null,
},
},
users: {
checkMfa: {
status: 'not_started',
error: null,
},
login: {
status: 'not_started',
error: null,
},
autocompleteUsers: {
status: 'not_started',
error: null,
},
updateMe: {
status: 'not_started',
error: null,
},
},
files: {
uploadFiles: {
status: 'not_started',
error: null,
},
},
roles: {
getRolesByNames: {
status: 'not_started',
error: null,
},
getRoleByName: {
status: 'not_started',
error: null,
},
getRole: {
status: 'not_started',
error: null,
},
editRole: {
status: 'not_started',
error: null,
},
},
jobs: {
createJob: {
status: 'not_started',
error: null,
},
getJob: {
status: 'not_started',
error: null,
},
getJobs: {
status: 'not_started',
error: null,
},
cancelJob: {
status: 'not_started',
error: null,
},
},
},
websocket: {
connected: false,
lastConnectAt: 0,
lastDisconnectAt: 0,
},
};
export default state;

View file

@ -1,34 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import thunk, {ThunkMiddleware} from 'redux-thunk';
import createActionBuffer from 'redux-action-buffer';
import {PERSIST} from 'redux-persist';
import {General} from '@mm-redux/constants';
export function createMiddleware(clientOptions: any): ThunkMiddleware[] {
const {
additionalMiddleware,
enableBuffer,
enableThunk,
} = clientOptions;
const middleware: ThunkMiddleware[] = [];
if (enableThunk) {
middleware.push(thunk);
}
if (additionalMiddleware) {
if (typeof additionalMiddleware === 'function') {
middleware.push(additionalMiddleware);
} else {
middleware.push(...additionalMiddleware);
}
}
if (enableBuffer) {
middleware.push(createActionBuffer({breaker: General.REHYDRATED, passthrough: [PERSIST]}));
}
return middleware;
}

View file

@ -1,142 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/* eslint-disable no-console */
import MMKV from 'react-native-mmkv-storage';
type ReadOnlyArrayString = ReadonlyArray<string>;
type MultiGetCallbackFunction = (
errors: ReadonlyArray<Error> | null | undefined,
result: ReadonlyArray<ReadOnlyArrayString> | null | undefined,
) => void;
type MultiRequest = {
keys: ReadonlyArray<string>;
callback: MultiGetCallbackFunction | null | undefined;
keyIndex: number;
resolve: (
result?: Promise<ReadonlyArray<ReadOnlyArrayString> | null | undefined>,
) => void | null | undefined;
reject: (error?: any) => void | null | undefined;
};
function checkValidInput(usedKey: string, value?: any) {
const isValuePassed = arguments.length > 1;
if (typeof usedKey !== 'string') {
console.warn(
`[MMKVStorageAdapter] Using ${typeof usedKey} type is not suppported. This can lead to unexpected behavior/errors. Use string instead.\nKey passed: ${usedKey}\n`,
);
}
if (isValuePassed && typeof value !== 'string') {
if (value == null) {
throw new Error(
`[MMKVStorageAdapter] Passing null/undefined as value is not supported. If you want to remove value, Use .remove method instead.\nPassed value: ${value}\nPassed key: ${usedKey}\n`,
);
} else {
console.warn(
`[MMKVStorageAdapter] The value for key "${usedKey}" is not a string. This can lead to unexpected behavior/errors. Consider stringifying it.\nPassed value: ${value}\nPassed key: ${usedKey}\n`,
);
}
}
}
const MMKVStorageAdapter = {
_getRequests: [] as Array<MultiRequest>,
_getKeys: [] as Array<string>,
_immediate: null as number | null | undefined,
getItem: (
key: string,
callback?: (
error: Error | null | undefined,
result: string | null,
) => void | null | undefined,
): Promise<string | null> => {
return new Promise((resolve, reject) => {
checkValidInput(key);
MMKV.getStringAsync(key).then((result: string) => {
if (callback) {
callback(null, result);
}
resolve(result);
}).catch((error) => {
if (callback) {
callback(null, error);
}
reject(error);
});
});
},
setItem: (
key: string,
value: string,
callback?: (
error: Error | null | undefined
) => void | null | undefined,
): Promise<null> => {
return new Promise((resolve, reject) => {
checkValidInput(key, value);
MMKV.setStringAsync(key, value).then(() => {
if (callback) {
callback(null);
}
resolve(null);
}).catch((error) => {
if (callback) {
callback(error);
}
reject(error);
});
});
},
removeItem: (
key: string,
callback?: (
error: Error | null | undefined
) => void | null | undefined,
): Promise<null> => {
checkValidInput(key);
if (callback) {
callback(null);
}
return MMKV.removeItem(key);
},
clear: (callback?: (error: Error | null | undefined) => void | null | undefined): Promise<null> => {
if (callback) {
callback(null);
}
return MMKV.clearStore();
},
getAllKeys: (
callback?: (
error: Error | null | undefined,
keys: ReadOnlyArrayString | null | undefined
) => void,
): Promise<ReadOnlyArrayString> => {
return new Promise((resolve, reject) => {
MMKV.getKeysAsync().then((keys: Array<string>) => {
if (callback) {
callback(null, keys);
}
resolve(keys);
}).catch((error) => {
if (callback) {
callback(error, null);
}
reject(error);
});
});
},
};
export default MMKVStorageAdapter;

View file

@ -1,33 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import assert from 'assert';
import reducerRegistry from '@mm-redux/store/reducer_registry';
import configureStore from 'test/test_store';
describe('ReducerRegistry', () => {
let store;
function testReducer() {
return 'teststate';
}
beforeEach(async () => {
store = await configureStore();
});
it('register reducer', () => {
reducerRegistry.register('testReducer', testReducer);
assert.equal(store.getState().testReducer, 'teststate');
});
it('get reducers', () => {
reducerRegistry.register('testReducer', testReducer);
const reducers = reducerRegistry.getReducers();
assert.ok(reducers.testReducer);
assert.ok(reducers.entities);
assert.ok(reducers.requests);
assert.ok(reducers.errors);
});
});

View file

@ -1,33 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Reducer} from '@mm-redux/types/actions';
import {Dictionary} from '@mm-redux/types/utilities';
// Based on http://nicolasgallagher.com/redux-modules-and-code-splitting/
export class ReducerRegistry {
emitChange?: Function;
reducers: Dictionary<Reducer> = {};
setReducers = (reducers: Dictionary<Reducer>) => {
this.reducers = reducers;
}
getReducers = () => {
return {...this.reducers};
}
register = (name: string, reducer: Reducer) => {
this.reducers = {...this.reducers, [name]: reducer};
if (this.emitChange) {
this.emitChange(this.getReducers());
}
}
setChangeListener = (listener: Function) => {
this.emitChange = listener;
}
}
const reducerRegistry = new ReducerRegistry();
export default reducerRegistry;

View file

@ -2,6 +2,5 @@
// See LICENSE.txt for license information.
declare module 'gfycat-sdk';
declare module 'remote-redux-devtools';
declare module 'redux-persist';
declare module 'redux-persist/constants';
declare module 'redux-action-buffer';
declare module 'redux-action-buffer';
declare module 'redux-reset';

View file

@ -4,7 +4,7 @@
import {AppState, NativeModules} from 'react-native';
import {NotificationsAndroid, PendingNotifications} from 'react-native-notifications';
import ephemeralStore from 'app/store/ephemeral_store';
import EphemeralStore from '@store/ephemeral_store';
const {NotificationPreferences} = NativeModules;
@ -65,7 +65,7 @@ class PushNotification {
if (notification) {
const data = notification.getData();
if (data) {
ephemeralStore.appStartedFromPushNotification = true;
EphemeralStore.appStartedFromPushNotification = true;
this.handleNotification(data, true);
}
}

View file

@ -10,11 +10,12 @@ import NotificationsIOS, {
DEVICE_NOTIFICATION_OPENED_EVENT,
} from 'react-native-notifications';
import {getBadgeCount} from 'app/selectors/views';
import EphemeralStore from 'app/store/ephemeral_store';
import {getCurrentLocale} from 'app/selectors/i18n';
import {getLocalizedMessage} from 'app/i18n';
import {t} from 'app/utils/i18n';
import {getLocalizedMessage} from '@i18n';
import {getCurrentLocale} from '@selectors/i18n';
import {getBadgeCount} from '@selectors/views';
import EphemeralStore from '@store/ephemeral_store';
import Store from '@store/store';
import {t} from '@utils/i18n';
const CATEGORY = 'CAN_REPLY';
const REPLY_ACTION = 'REPLY_ACTION';
@ -24,7 +25,6 @@ class PushNotification {
this.deviceNotification = null;
this.onRegister = null;
this.onNotification = null;
this.reduxStore = null;
NotificationsIOS.addEventListener(DEVICE_REMOTE_NOTIFICATIONS_REGISTERED_EVENT, this.onRemoteNotificationsRegistered);
NotificationsIOS.addEventListener(DEVICE_NOTIFICATION_RECEIVED_FOREGROUND_EVENT, this.onNotificationReceivedForeground);
@ -46,7 +46,6 @@ class PushNotification {
};
configure(options) {
this.reduxStore = options.reduxStore;
this.onRegister = options.onRegister;
this.onNotification = options.onNotification;
@ -75,7 +74,7 @@ class PushNotification {
}
createReplyCategory = () => {
const {getState} = this.reduxStore;
const {getState} = Store.redux;
const state = getState();
const locale = getCurrentLocale(state);
@ -198,8 +197,8 @@ class PushNotification {
const ids = [];
let badgeCount = notifications.length;
if (this.reduxStore) {
const totalMentions = getBadgeCount(this.reduxStore.getState());
if (Store.redux) {
const totalMentions = getBadgeCount(Store.redux.getState());
if (totalMentions > -1) {
badgeCount = totalMentions;
}

View file

@ -3,7 +3,8 @@
import NotificationsIOS from 'react-native-notifications';
import * as ViewSelectors from 'app/selectors/views';
import * as ViewSelectors from '@selectors/views';
import Store from '@store/store';
import PushNotification from './push_notifications.ios';
@ -95,7 +96,7 @@ describe('PushNotification', () => {
});
it('clearChannelNotifications should set app badge number from to delivered notification count when redux store is not set', () => {
PushNotification.reduxStore = null;
Store.redux = null;
const setApplicationIconBadgeNumber = jest.spyOn(PushNotification, 'setApplicationIconBadgeNumber');
const deliveredNotifications = [{identifier: 1}, {identifier: 2}];
NotificationsIOS.setDeliveredNotifications(deliveredNotifications);
@ -105,7 +106,7 @@ describe('PushNotification', () => {
});
it('clearChannelNotifications should set app badge number from redux store when set', () => {
PushNotification.reduxStore = {
Store.redux = {
getState: jest.fn(),
};
const setApplicationIconBadgeNumber = jest.spyOn(PushNotification, 'setApplicationIconBadgeNumber');

View file

@ -12,7 +12,9 @@ function locale(state = defaultLocale, action) {
switch (action.type) {
case UserTypes.RECEIVED_ME: {
const data = action.data || action.payload;
return data.locale;
if (data?.locale) {
return data.locale;
}
}
}

View file

@ -19,6 +19,7 @@ function deepLinkURL(state = '', action) {
function hydrationComplete(state = false, action) {
switch (action.type) {
case General.REHYDRATED:
case General.STORE_REHYDRATION_COMPLETE:
return true;
default:

View file

@ -40,7 +40,7 @@ export const getThreadDraft = createSelector(
);
export function getProfileImageUri(state) {
return state.views.user.profileImageUri;
return state.views?.user?.profileImageUri;
}
export const getBadgeCount = createSelector(

View file

@ -5,12 +5,12 @@ import {ViewTypes} from 'app/constants';
class EphemeralStore {
constructor() {
this.allNavigationComponentIds = [];
this.appStarted = false;
this.appStartedFromPushNotification = false;
this.deviceToken = null;
this.navigationComponentIdStack = [];
this.allNavigationComponentIds = [];
this.currentServerUrl = null;
this.navigationComponentIdStack = [];
this.safeAreaInsets = {
[ViewTypes.PORTRAIT]: null,
[ViewTypes.LANDSCAPE]: null,

View file

@ -1,22 +1,22 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {combineReducers} from 'redux';
import reducerRegistry from './reducer_registry';
import {enableBatching, Reducer} from '@mm-redux/types/actions';
import AsyncStorage from '@react-native-community/async-storage';
import {combineReducers} from 'redux';
import {enableBatching, Reducer} from '@mm-redux/types/actions';
const KEY_PREFIX = 'reduxPersist:';
/* eslint-disable no-console */
export function createReducer(...reducers: Reducer[]) {
reducerRegistry.setReducers(Object.assign({}, ...reducers));
const baseReducer = combineReducers(reducerRegistry.getReducers());
const reducerRegistry = Object.assign({}, ...reducers);
const baseReducer = combineReducers(reducerRegistry);
return enableBatching(baseReducer);
}
const KEY_PREFIX = 'reduxPersist:';
export async function getStoredState() {
const restoredState: Record<string, any> = {};
let storeKeys: Array<string> = [];

View file

@ -1,10 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import initialState from 'app/initial_state';
import configureAppStore from './store';
const {store, persistor} = configureAppStore(initialState);
export {persistor};
export default store;

242
app/store/index.ts Normal file
View file

@ -0,0 +1,242 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import AsyncStorage from '@react-native-community/async-storage';
import * as redux from 'redux';
import {createPersistoid, createTransform, persistReducer, persistStore, Persistor, PersistConfig} from 'redux-persist';
import {createBlacklistFilter} from 'redux-persist-transform-filter';
import reduxReset from 'redux-reset';
import {General} from '@mm-redux/constants';
import serviceReducer from '@mm-redux/reducers';
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} 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.
*/
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 typingBlackListFilter = createBlacklistFilter(
'entities',
['typing'],
);
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: {}, optionalOptions = {}): ReduxStore {
const defaultOptions: ClientOptions = {
enableBuffer: true,
enableThunk: true,
};
const defaultConfig: PersistConfig<GlobalState> = {
key: 'root',
storage,
serialize: (state: GlobalState) => ({...state}),
deserialize: false,
blacklist: ['device', 'navigation', 'requests', '_persist'],
transforms: [
setTransformer,
viewsBlackListFilter,
typingBlackListFilter,
channelViewBlackListFilter,
emojiBlackListFilter,
],
throttle: 100,
};
const persistConfig: PersistConfig<GlobalState> = Object.assign({}, defaultConfig, optionalConfig);
const baseState: any = Object.assign({}, initialState, preloadedState);
const rootReducer: any = createReducer(serviceReducer as any, appReducer 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),
),
reduxReset(General.OFFLINE_STORE_PURGE),
),
);
const persistor = persistStore(store, null);
getStoredState().then(({storeKeys, restoredState}: V4Store) => {
if (Object.keys(restoredState).length) {
const state = {
...restoredState,
views: {
...restoredState.views,
root: {
hydrationComplete: true,
},
},
_persist: persistor.getState(),
};
store.dispatch({
type: General.OFFLINE_STORE_PURGE,
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 {
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};
}

View file

@ -1,173 +1,11 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Platform} from 'react-native';
import DeviceInfo from 'react-native-device-info';
import {REHYDRATE} from 'redux-persist';
import semver from 'semver/preload';
import {ViewTypes} from 'app/constants';
import initialState from 'app/initial_state';
import EphemeralStore from 'app/store/ephemeral_store';
import {throttle} from 'app/utils/general';
import initialState from '@store/initial_state';
import mattermostBucket from 'app/mattermost_bucket';
import {
captureException,
LOGGER_JAVASCRIPT_WARNING,
} from 'app/utils/sentry';
const SAVE_STATE_ACTIONS = [
'CONNECTION_CHANGED',
'DATA_CLEANUP',
'LOGIN',
'Offline/STATUS_CHANGED',
'persist/REHYDRATE',
'RECEIVED_APP_STATE',
'WEBSOCKET_CLOSED',
'WEBSOCKET_SUCCESS',
];
export const middlewares = () => {
const middlewareFunctions = [
messageRetention,
];
if (Platform.OS === 'ios') {
middlewareFunctions.push(saveShareExtensionState);
}
return middlewareFunctions;
};
// This middleware stores key parts of state entities into a file (in the App Group container) on certain actions.
// iOS only. Allows the share extension to work, without having access available to the redux store object.
// Remove this middleware if/when state is moved to a persisted solution.
function saveShareExtensionState(store) {
return (next) => (action) => {
if (SAVE_STATE_ACTIONS.includes(action.type)) {
throttle(saveStateToFile(store));
}
return next(action);
};
}
async function saveStateToFile(store) {
const state = store.getState();
if (state.entities) {
const channelsInTeam = {...state.entities.channels.channelsInTeam};
Object.keys(channelsInTeam).forEach((teamId) => {
channelsInTeam[teamId] = Array.from(channelsInTeam[teamId]);
});
const profilesInChannel = {...state.entities.users.profilesInChannel};
Object.keys(profilesInChannel).forEach((channelId) => {
profilesInChannel[channelId] = Array.from(profilesInChannel[channelId]);
});
let url;
if (state.entities.users.currentUserId) {
url = state.entities.general.credentials.url || state.views.selectServer.serverUrl;
}
const entities = {
...state.entities,
general: {
...state.entities.general,
credentials: {
url,
},
},
channels: {
...state.entities.channels,
channelsInTeam,
},
users: {
...state.entities.users,
profilesInChannel,
profilesNotInTeam: [],
profilesWithoutTeam: [],
profilesNotInChannel: [],
},
};
mattermostBucket.writeToFile('entities', JSON.stringify(entities));
}
}
function messageRetention(store) {
return (next) => (action) => {
if (action.type === REHYDRATE) {
if (!action.payload) {
// On first run payload is not set (when installed)
const version = DeviceInfo.getVersion();
const major = semver.major(version);
const minor = semver.minor(version);
const patch = semver.patch(version);
const prevAppVersion = `${major}.${parseInt(minor, 10) - 1}.${patch}`;
EphemeralStore.prevAppVersion = prevAppVersion;
action.payload = {
app: {
build: DeviceInfo.getBuildNumber(),
version,
},
views: {
root: {
hydrationComplete: true,
},
},
};
}
const {app} = action.payload;
const {entities, views} = action.payload;
if (!EphemeralStore.prevAppVersion) {
EphemeralStore.prevAppVersion = app?.version;
}
if (!entities || !views) {
return next(action);
}
// When a new version of the app has been detected
if (!app || !app.version || app.version !== DeviceInfo.getVersion() || app.build !== DeviceInfo.getBuildNumber()) {
action.payload = resetStateForNewVersion(action.payload);
return next(action);
}
// Keep only the last 60 messages for the last 5 viewed channels in each team
// and apply data retention on those posts if applies
try {
action.payload = cleanUpState(action.payload);
} catch (e) {
// Sometimes, the payload is incomplete so log the error to Sentry and skip the cleanup
console.warn(e); // eslint-disable-line no-console
captureException(e, LOGGER_JAVASCRIPT_WARNING, store);
}
return next(action);
} else if (action.type === ViewTypes.DATA_CLEANUP) {
action.payload = cleanUpState(action.payload, true);
return next(action);
}
/* Uncomment the following lines to log the actions being dispatched */
// if (action.type === 'BATCHING_REDUCER.BATCH') {
// action.payload.forEach((p) => {
// console.log('BATCHED ACTIONS', p.type);
// });
// } else {
// console.log('ACTION', action.type);
// }
return next(action);
};
}
function resetStateForNewVersion(payload) {
export function resetStateForNewVersion(payload) {
const lastChannelForTeam = getLastChannelForTeam(payload);
let general = initialState.entities.general;
@ -259,6 +97,9 @@ function resetStateForNewVersion(payload) {
}
const nextState = {
_persist: {
rehydrated: true,
},
app: {
build: DeviceInfo.getBuildNumber(),
version: DeviceInfo.getVersion(),
@ -299,7 +140,7 @@ function resetStateForNewVersion(payload) {
return nextState;
}
function getLastChannelForTeam(payload) {
export function getLastChannelForTeam(payload) {
if (payload?.views?.team?.lastChannelForTeam) {
const lastChannelForTeam = {...payload.views.team.lastChannelForTeam};
const convertLastChannelForTeam = Object.values(lastChannelForTeam).some((value) => !Array.isArray(value));
@ -448,9 +289,6 @@ export function cleanUpState(payload, keepCurrent = false) {
...resetPayload.views.channel,
...payload.views.channel,
},
root: {
hydrationComplete: true,
},
},
websocket: {
lastConnectAt: payload.websocket?.lastConnectAt,
@ -540,4 +378,4 @@ function removePendingPost(pendingPostIds, id) {
if (pendingIndex !== -1) {
pendingPostIds.splice(pendingIndex, 1);
}
}
}

View file

@ -0,0 +1,35 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Platform} from 'react-native';
import {PERSIST, REHYDRATE} from 'redux-persist';
import {ThunkMiddleware} from 'redux-thunk';
import createActionBuffer from 'redux-action-buffer';
import messageRetention from './message_retention';
import createSentryMiddleware from './sentry';
import thunk from './thunk';
export function createMiddlewares(clientOptions: any): ThunkMiddleware[] {
const {
enableBuffer,
enableThunk,
} = clientOptions;
const middleware: ThunkMiddleware[] = [];
if (enableThunk) {
middleware.push(thunk);
}
middleware.push(createSentryMiddleware(), messageRetention);
if (Platform.OS === 'ios') {
const iosExtension = require('./ios_extension').default;
middleware.push(iosExtension);
}
if (enableBuffer) {
middleware.push(createActionBuffer({breaker: REHYDRATE, passthrough: [PERSIST]}));
}
return middleware;
}

View file

@ -0,0 +1,72 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import mattermostBucket from 'app/mattermost_bucket';
import {throttle} from '@utils/general';
const SAVE_STATE_ACTIONS = [
'CONNECTION_CHANGED',
'DATA_CLEANUP',
'LOGIN',
'Offline/STATUS_CHANGED',
'persist/REHYDRATE',
'RECEIVED_APP_STATE',
'WEBSOCKET_CLOSED',
'WEBSOCKET_SUCCESS',
];
// This middleware stores key parts of state entities into a file (in the App Group container) on certain actions.
// iOS only. Allows the share extension to work, without having access available to the redux store object.
// Remove this middleware if/when state is moved to a persisted solution.
export default function saveShareExtensionState(store) {
return (next) => (action) => {
if (SAVE_STATE_ACTIONS.includes(action.type)) {
throttle(saveStateToFile(store));
}
return next(action);
};
}
async function saveStateToFile(store) {
const state = store.getState();
if (state.entities) {
const channelsInTeam = {...state.entities.channels.channelsInTeam};
Object.keys(channelsInTeam).forEach((teamId) => {
channelsInTeam[teamId] = Array.from(channelsInTeam[teamId]);
});
const profilesInChannel = {...state.entities.users.profilesInChannel};
Object.keys(profilesInChannel).forEach((channelId) => {
profilesInChannel[channelId] = Array.from(profilesInChannel[channelId]);
});
let url;
if (state.entities.users.currentUserId) {
url = state.entities.general.credentials.url || state.views.selectServer.serverUrl;
}
const entities = {
...state.entities,
general: {
...state.entities.general,
credentials: {
url,
},
},
channels: {
...state.entities.channels,
channelsInTeam,
},
users: {
...state.entities.users,
profilesInChannel,
profilesNotInTeam: [],
profilesWithoutTeam: [],
profilesNotInChannel: [],
},
};
mattermostBucket.writeToFile('entities', JSON.stringify(entities));
}
}

View file

@ -0,0 +1,84 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import DeviceInfo from 'react-native-device-info';
import {REHYDRATE} from 'redux-persist';
import semver from 'semver/preload';
import {ViewTypes} from '@constants';
import {General} from '@mm-redux/constants';
import EphemeralStore from '@store/ephemeral_store';
import {
captureException,
LOGGER_JAVASCRIPT_WARNING,
} from '@utils/sentry';
import {cleanUpState, resetStateForNewVersion} from './helpers';
export default function messageRetention(store) {
return (next) => (action) => {
if (action.type === REHYDRATE) {
if (!action.payload) {
// On first run payload is not set (when installed)
const version = DeviceInfo.getVersion();
const major = semver.major(version);
const minor = semver.minor(version);
const patch = semver.patch(version);
const prevAppVersion = `${major}.${parseInt(minor, 10) - 1}.${patch}`;
EphemeralStore.prevAppVersion = prevAppVersion;
action.payload = {
app: {
build: DeviceInfo.getBuildNumber(),
version,
},
_persist: {
rehydrated: true,
},
};
}
const {app} = action.payload;
const {entities, views} = action.payload;
if (!EphemeralStore.prevAppVersion) {
EphemeralStore.prevAppVersion = app?.version;
}
if (!entities || !views) {
return next(action);
}
// When a new version of the app has been detected
if (!app || !app.version || app.version !== DeviceInfo.getVersion() || app.build !== DeviceInfo.getBuildNumber()) {
action.payload = resetStateForNewVersion(action.payload);
return next(action);
}
// Keep only the last 60 messages for the last 5 viewed channels in each team
// and apply data retention on those posts if applies
try {
action.payload = cleanUpState(action.payload);
} catch (e) {
// Sometimes, the payload is incomplete so log the error to Sentry and skip the cleanup
console.warn(e); // eslint-disable-line no-console
captureException(e, LOGGER_JAVASCRIPT_WARNING, store);
}
return next(action);
} else if (action.type === ViewTypes.DATA_CLEANUP) {
action.payload = cleanUpState(action.payload, true);
return next(action);
}
/* Uncomment the following lines to log the actions being dispatched */
// if (action.type === 'BATCHING_REDUCER.BATCH') {
// action.payload.forEach((p) => {
// console.log('BATCHED ACTIONS', p.type);
// });
// } else {
// console.log('ACTION', action.type);
// }
return next(action);
};
}

View file

@ -5,17 +5,15 @@
import assert from 'assert';
import {ViewTypes} from 'app/constants';
import {ViewTypes} from '@constants';
import {
cleanUpPostsInChannel,
cleanUpState,
getAllFromPostsInChannel,
middlewares,
} from 'app/store/middleware';
} from './helpers';
import messageRetention from './message_retention';
describe('messageRetention', () => {
const messageRetention = middlewares()[0];
describe('should chain the same incoming action type', () => {
const actions = [
{

View file

@ -6,7 +6,7 @@ import {BATCH} from 'redux-batched-actions';
export const BREADCRUMB_REDUX_ACTION = 'redux-action';
let Sentry;
export function createSentryMiddleware() {
export default function createSentryMiddleware() {
if (!Sentry) {
Sentry = require('@sentry/react-native');
}

View file

@ -0,0 +1,39 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {
captureMessage,
cleanUrlForLogging,
LOGGER_JAVASCRIPT_WARNING,
} from 'app/utils/sentry';
// Creates middleware that mimics thunk while catching network errors thrown by Client4 that haven't
// been otherwise handled.
export default (store) => (next) => (action) => {
if (typeof action === 'function') {
const result = action(store.dispatch, store.getState);
if (result instanceof Promise) {
return result.catch((error) => {
if (error.url) {
// This is a connection error from app/mm-redux. This should've been handled
// within the action itself, so we'll log to Sentry enough to identify where
// that handling is missing.
captureMessage(
`Caught Client4 error "${error.message}" from "${cleanUrlForLogging(error.url)}"`,
LOGGER_JAVASCRIPT_WARNING,
store,
);
return {error};
}
throw error;
});
}
return result;
}
return next(action);
};

98
app/store/mmkv_adapter.ts Normal file
View file

@ -0,0 +1,98 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/* eslint-disable no-console */
import MMKVStorage from 'react-native-mmkv-storage';
function checkValidInput(usedKey: string, value?: any) {
const isValuePassed = arguments.length > 1;
if (typeof usedKey !== 'string') {
console.warn(
`[MMKVStorageAdapter] Using ${typeof usedKey} type is not suppported. This can lead to unexpected behavior/errors. Use string instead.\nKey passed: ${usedKey}\n`,
);
}
if (isValuePassed && typeof value !== 'object') {
if (value == null) {
throw new Error(
`[MMKVStorageAdapter] Passing null/undefined as value is not supported. If you want to remove value, Use .remove method instead.\nPassed value: ${value}\nPassed key: ${usedKey}\n`,
);
} else {
console.warn(
`[MMKVStorageAdapter] The value for key "${usedKey}" is not a object. This can lead to unexpected behavior/errors. Consider JSON.parse it.\nPassed value: ${value}\nPassed key: ${usedKey}\n`,
);
}
}
}
export default async function getStorage(identifier = 'default') {
const MMKV = await new MMKVStorage.Loader().
withInstanceID(identifier).
setProcessingMode(MMKVStorage.MODES.MULTI_PROCESS).
withEncryption().
initialize();
return {
getItem: (
key: string,
callback?: (
error: Error | null | undefined,
result: object | null,
) => void | null | undefined,
): Promise<object | null> => {
return new Promise((resolve, reject) => {
checkValidInput(key);
MMKV.getMapAsync(key).then((result: object) => {
if (callback) {
callback(null, result);
}
resolve(result);
}).catch((error: Error) => {
if (callback) {
callback(null, error);
}
reject(error);
});
});
},
setItem: (
key: string,
value: object,
callback?: (
error: Error | null | undefined
) => void | null | undefined,
): Promise<null> => {
return new Promise((resolve, reject) => {
checkValidInput(key, value);
MMKV.setMapAsync(key, value).then(() => {
if (callback) {
callback(null);
}
resolve(null);
}).catch((error: Error) => {
if (callback) {
callback(error);
}
reject(error);
});
});
},
removeItem: (
key: string,
callback?: (
error: Error | null | undefined
) => void | null | undefined,
): Promise<boolean> => {
checkValidInput(key);
if (callback) {
callback(null);
}
return MMKV.removeItem(key);
},
};
}

View file

@ -1,148 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {createBlacklistFilter} from 'redux-persist-transform-filter';
import {createTransform} from 'redux-persist';
import reduxReset from 'redux-reset';
import {General} from '@mm-redux/constants';
import configureStore from '@mm-redux/store';
import MMKVStorageAdapter from '@mm-redux/store/mmkv_adapter';
import appReducer from 'app/reducers';
import {createSentryMiddleware} from 'app/utils/sentry/middleware';
import {middlewares} from './middleware';
import {createThunkMiddleware} from './thunk';
import {transformSet} from './utils';
const usersSetTransform = [
'profilesInChannel',
'profilesNotInChannel',
'profilesInTeam',
'profilesNotInTeam',
];
const channelSetTransform = [
'channelsInTeam',
];
const rolesSetTransform = [
'pending',
];
const setTransforms = [
...usersSetTransform,
...channelSetTransform,
...rolesSetTransform,
];
const viewsBlackListFilter = createBlacklistFilter(
'views',
['extension', 'root'],
);
const typingBlackListFilter = createBlacklistFilter(
'entities',
['typing'],
);
const channelViewBlackList = {loading: true, refreshing: true, loadingPosts: true, retryFailed: true, loadMorePostsVisible: true};
const channelViewBlackListFilter = createTransform(
(inboundState) => {
const channel = {};
const keys = 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 = {nonExistentEmoji: true};
const emojiBlackListFilter = createTransform(
(inboundState) => {
const emojis = {};
const keys = 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, 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 persistConfig = {
key: 'root',
storage: MMKVStorageAdapter,
blacklist: ['device', 'navigation', 'offline', 'requests'],
transforms: [
setTransformer,
viewsBlackListFilter,
typingBlackListFilter,
channelViewBlackListFilter,
emojiBlackListFilter,
],
};
export default function configureAppStore(initialState) {
const clientOptions = {
additionalMiddleware: [
createThunkMiddleware(),
createSentryMiddleware(),
...middlewares(),
],
enableThunk: false, // We override the default thunk middleware
enhancers: [reduxReset(General.OFFLINE_STORE_PURGE)],
};
return configureStore(initialState, appReducer, persistConfig, clientOptions);
}

17
app/store/store.ts Normal file
View file

@ -0,0 +1,17 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import * as redux from 'redux';
import {Persistor} from 'redux-persist';
class Store {
redux: redux.Store | null;
persistor: Persistor | null;
constructor() {
this.redux = null;
this.persistor = null;
}
}
export default new Store();

View file

@ -1,41 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {
captureMessage,
cleanUrlForLogging,
LOGGER_JAVASCRIPT_WARNING,
} from 'app/utils/sentry';
// Creates middleware that mimics thunk while catching network errors thrown by Client4 that haven't
// been otherwise handled.
export function createThunkMiddleware() {
return (store) => (next) => (action) => {
if (typeof action === 'function') {
const result = action(store.dispatch, store.getState);
if (result instanceof Promise) {
return result.catch((error) => {
if (error.url) {
// This is a connection error from app/mm-redux. This should've been handled
// within the action itself, so we'll log to Sentry enough to identify where
// that handling is missing.
captureMessage(
`Caught Client4 error "${error.message}" from "${cleanUrlForLogging(error.url)}"`,
LOGGER_JAVASCRIPT_WARNING,
store,
);
return {error};
}
throw error;
});
}
return result;
}
return next(action);
};
}

View file

@ -45,14 +45,21 @@ export function transformSet(incoming, setTransforms, toStorage = true) {
export function waitForHydration(store, callback) {
let executed = false; // this is to prevent a race condition when subcription runs before unsubscribed
if (store.getState().views?.root?.hydrationComplete && !executed) {
let state = store.getState();
let root = state.views?.root;
let persist = state._persist; //eslint-disable-line no-underscore-dangle
if (root?.hydrationComplete && !executed) {
if (callback && typeof callback === 'function') {
executed = true;
callback();
}
} else {
const subscription = () => {
if (store.getState().views?.root?.hydrationComplete && !executed) {
state = store.getState();
root = state.views?.root;
persist = state._persist; //eslint-disable-line no-underscore-dangle
if (root?.hydrationComplete && !executed) {
unsubscribeFromStore();
if (callback && typeof callback === 'function') {
executed = true;
@ -94,6 +101,9 @@ export function getStateForReset(initialState, currentState) {
hydrationComplete: true,
},
},
_persist: {
rehydrated: true,
},
});
return resetState;

View file

@ -1,8 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import initialState from 'app/initial_state';
import {getStateForReset} from 'app/store/utils';
import initialState from '@store/initial_state';
import {getStateForReset} from '@store/utils';
/*
const {currentUserId} = currentState.entities.users;

View file

@ -3,7 +3,7 @@
import LocalConfig from 'assets/config'; // eslint-disable-line
import store from 'app/store';
import Store from '@store/store';
import {
saveToTelemetryServer,
@ -32,7 +32,7 @@ class Telemetry {
}
canSendTelemetry() {
const {config} = store.getState().entities.general;
const {config} = Store.redux.getState().entities.general;
return Boolean(!__DEV__ && config.EnableDiagnostics === 'true' && LocalConfig.TelemetryEnabled);
}
@ -144,7 +144,7 @@ class Telemetry {
});
});
const {config} = store.getState().entities.general;
const {config} = Store.redux.getState().entities.general;
const deviceInfo = getDeviceInfo();
deviceInfo.server_version = config.Version;

View file

@ -17,14 +17,16 @@ import {
loadFromPushNotification,
} from '@actions/views/root';
import {NavigationTypes, ViewTypes} from 'app/constants';
import {getLocalizedMessage} from 'app/i18n';
import {NavigationTypes, ViewTypes} from '@constants';
import {getLocalizedMessage} from '@i18n';
import {getCurrentLocale} from '@selectors/i18n';
import EphemeralStore from '@store/ephemeral_store';
import Store from '@store/store';
import {waitForHydration} from '@store/utils';
import {t} from '@utils/i18n';
import {getCurrentServerUrl, getAppCredentials} from 'app/init/credentials';
import PushNotifications from 'app/push_notifications';
import {getCurrentLocale} from 'app/selectors/i18n';
import EphemeralStore from 'app/store/ephemeral_store';
import {waitForHydration} from 'app/store/utils';
import {t} from 'app/utils/i18n';
class PushNotificationUtils {
constructor() {
@ -32,11 +34,8 @@ class PushNotificationUtils {
this.replyNotificationData = null;
}
configure = (store) => {
this.store = store;
configure = () => {
PushNotifications.configure({
reduxStore: store,
onRegister: this.onRegisterDevice,
onNotification: this.onPushNotification,
onReply: this.onPushNotificationReply,
@ -48,7 +47,7 @@ class PushNotificationUtils {
loadFromNotification = async (notification) => {
// Set appStartedFromPushNotification to avoid channel screen to call selectInitialChannel
EphemeralStore.setStartFromNotification(true);
await this.store.dispatch(loadFromPushNotification(notification));
await Store.redux.dispatch(loadFromPushNotification(notification));
// if we have a componentId means that the app is already initialized
const componentId = EphemeralStore.getNavigationTopComponentId();
@ -64,7 +63,7 @@ class PushNotificationUtils {
};
onPushNotification = async (deviceNotification) => {
const {dispatch, getState} = this.store;
const {dispatch, getState} = Store.redux;
const {data, foreground, message, userInteraction} = deviceNotification;
const notification = {
data,
@ -80,7 +79,7 @@ class PushNotificationUtils {
if (foreground) {
EventEmitter.emit(ViewTypes.NOTIFICATION_IN_APP, notification);
} else if (userInteraction && !notification?.data?.localNotification) {
waitForHydration(this.store, () => {
waitForHydration(Store.redux, () => {
this.loadFromNotification(notification);
});
}
@ -88,7 +87,7 @@ class PushNotificationUtils {
};
onPushNotificationReply = async (data, text, completion) => {
const {dispatch, getState} = this.store;
const {dispatch, getState} = Store.redux;
const state = getState();
const credentials = await getAppCredentials(); // TODO Change to handle multiple servers
const url = await getCurrentServerUrl(); // TODO Change to handle multiple servers
@ -149,7 +148,7 @@ class PushNotificationUtils {
};
onRegisterDevice = (data) => {
const {dispatch} = this.store;
const {dispatch} = Store.redux;
let prefix;
if (Platform.OS === 'ios') {
@ -164,7 +163,7 @@ class PushNotificationUtils {
EphemeralStore.deviceToken = `${prefix}:${data.token}`;
// TODO: Remove when realm is ready
waitForHydration(this.store, () => {
waitForHydration(Store.redux, () => {
this.configured = true;
dispatch(setDeviceToken(EphemeralStore.deviceToken));
});

View file

@ -16,11 +16,14 @@ module.exports = {
'@actions': './app/actions',
'@constants': './app/constants',
'@i18n': './app/i18n',
'@init': './app/init',
'@mm-redux': './app/mm-redux',
'@screens': './app/screens',
'@selectors': './app/selectors',
'@store': './app/store',
'@telemetry': './app/telemetry',
'@utils': './app/utils',
'@websocket': './app/client/websocket',
'@mm-redux': './app/mm-redux',
},
}],
],

View file

@ -11,24 +11,6 @@
#import <os/log.h>
#import <RNHWKeyboardEvent.h>
#if DEBUG
#import <FlipperKit/FlipperClient.h>
#import <FlipperKitLayoutPlugin/FlipperKitLayoutPlugin.h>
#import <FlipperKitUserDefaultsPlugin/FKUserDefaultsPlugin.h>
#import <FlipperKitNetworkPlugin/FlipperKitNetworkPlugin.h>
#import <SKIOSNetworkPlugin/SKIOSNetworkAdapter.h>
#import <FlipperKitReactPlugin/FlipperKitReactPlugin.h>
static void InitializeFlipper(UIApplication *application) {
FlipperClient *client = [FlipperClient sharedClient];
SKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults];
[client addPlugin:[[FlipperKitLayoutPlugin alloc] initWithRootNode:application withDescriptorMapper:layoutDescriptorMapper]];
[client addPlugin:[[FKUserDefaultsPlugin alloc] initWithSuiteName:nil]];
[client addPlugin:[FlipperKitReactPlugin new]];
[client addPlugin:[[FlipperKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]];
[client start];
}
#endif
@implementation AppDelegate
NSString* const NOTIFICATION_MESSAGE_ACTION = @"message";
@ -43,10 +25,6 @@ NSString* const NOTIFICATION_UPDATE_BADGE_ACTION = @"update_badge";
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
#if DEBUG
InitializeFlipper(application);
#endif
// Clear keychain on first run in case of reinstallation
if (![[NSUserDefaults standardUserDefaults] objectForKey:@"FirstRun"]) {

View file

@ -1,40 +1,6 @@
platform :ios, '9.0'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
def add_flipper_pods!
version = '~> 0.33.1'
pod 'FlipperKit', version, :configuration => 'Debug'
pod 'FlipperKit/FlipperKitLayoutPlugin', version, :configuration => 'Debug'
pod 'FlipperKit/SKIOSNetworkPlugin', version, :configuration => 'Debug'
pod 'FlipperKit/FlipperKitUserDefaultsPlugin', version, :configuration => 'Debug'
pod 'FlipperKit/FlipperKitReactPlugin', version, :configuration => 'Debug'
end
# Post Install processing for Flipper
def flipper_post_install(installer)
installer.pods_project.targets.each do |target|
if target.name == 'YogaKit'
target.build_configurations.each do |config|
config.build_settings['SWIFT_VERSION'] = '4.1'
end
end
end
file_name = Dir.glob("*.xcodeproj")[0]
app_project = Xcodeproj::Project.open(file_name)
app_project.native_targets.each do |target|
target.build_configurations.each do |config|
cflags = config.build_settings['OTHER_CFLAGS'] || '$(inherited) '
unless cflags.include? '-DFB_SONARKIT_ENABLED=1'
puts 'Adding -DFB_SONARKIT_ENABLED=1 in OTHER_CFLAGS...'
cflags << '-DFB_SONARKIT_ENABLED=1'
end
config.build_settings['OTHER_CFLAGS'] = cflags
end
app_project.save
end
installer.pods_project.save
end
target 'Mattermost' do
# Pods for Mattermost
pod 'FBLazyVector', :path => "../node_modules/react-native/Libraries/FBLazyVector"
@ -76,13 +42,4 @@ target 'Mattermost' do
pod 'Swime', '3.0.6'
use_native_modules!
# Enables Flipper.
#
# Note that if you have use_frameworks! enabled, Flipper will not work and
# you should disable these next few lines.
add_flipper_pods!
post_install do |installer|
flipper_post_install(installer)
end
end

View file

@ -2,8 +2,6 @@ PODS:
- boost-for-react-native (1.63.0)
- BVLinearGradient (2.5.6):
- React
- CocoaAsyncSocket (7.6.4)
- CocoaLibEvent (1.0.0)
- DoubleConversion (1.1.6)
- FBLazyVector (0.62.2)
- FBReactNativeSpec (0.62.2):
@ -13,52 +11,6 @@ PODS:
- React-Core (= 0.62.2)
- React-jsi (= 0.62.2)
- ReactCommon/turbomodule/core (= 0.62.2)
- Flipper (0.33.1):
- Flipper-Folly (~> 2.1)
- Flipper-RSocket (~> 1.0)
- Flipper-DoubleConversion (1.1.7)
- Flipper-Folly (2.1.1):
- boost-for-react-native
- CocoaLibEvent (~> 1.0)
- Flipper-DoubleConversion
- Flipper-Glog
- OpenSSL-Universal (= 1.0.2.19)
- Flipper-Glog (0.3.6)
- Flipper-PeerTalk (0.0.4)
- Flipper-RSocket (1.0.0):
- Flipper-Folly (~> 2.0)
- FlipperKit (0.33.1):
- FlipperKit/Core (= 0.33.1)
- FlipperKit/Core (0.33.1):
- Flipper (~> 0.33.1)
- FlipperKit/CppBridge
- FlipperKit/FBCxxFollyDynamicConvert
- FlipperKit/FBDefines
- FlipperKit/FKPortForwarding
- FlipperKit/CppBridge (0.33.1):
- Flipper (~> 0.33.1)
- FlipperKit/FBCxxFollyDynamicConvert (0.33.1):
- Flipper-Folly (~> 2.1)
- FlipperKit/FBDefines (0.33.1)
- FlipperKit/FKPortForwarding (0.33.1):
- CocoaAsyncSocket (~> 7.6)
- Flipper-PeerTalk (~> 0.0.4)
- FlipperKit/FlipperKitHighlightOverlay (0.33.1)
- FlipperKit/FlipperKitLayoutPlugin (0.33.1):
- FlipperKit/Core
- FlipperKit/FlipperKitHighlightOverlay
- FlipperKit/FlipperKitLayoutTextSearchable
- YogaKit (~> 1.18)
- FlipperKit/FlipperKitLayoutTextSearchable (0.33.1)
- FlipperKit/FlipperKitNetworkPlugin (0.33.1):
- FlipperKit/Core
- FlipperKit/FlipperKitReactPlugin (0.33.1):
- FlipperKit/Core
- FlipperKit/FlipperKitUserDefaultsPlugin (0.33.1):
- FlipperKit/Core
- FlipperKit/SKIOSNetworkPlugin (0.33.1):
- FlipperKit/Core
- FlipperKit/FlipperKitNetworkPlugin
- Folly (2018.10.22.00):
- boost-for-react-native
- DoubleConversion
@ -80,10 +32,9 @@ PODS:
- libwebp/mux (1.1.0):
- libwebp/demux
- libwebp/webp (1.1.0)
- MMKV (1.0.24)
- OpenSSL-Universal (1.0.2.19):
- OpenSSL-Universal/Static (= 1.0.2.19)
- OpenSSL-Universal/Static (1.0.2.19)
- MMKV (1.1.0):
- MMKVCore (~> 1.1.0)
- MMKVCore (1.1.0)
- Permission-Camera (2.0.10):
- RNPermissions
- Permission-PhotoLibrary (2.0.10):
@ -264,8 +215,8 @@ PODS:
- React
- react-native-image-picker (2.3.1):
- React
- react-native-mmkv-storage (0.2.2):
- MMKV (= 1.0.24)
- react-native-mmkv-storage (0.3.1):
- MMKV (= 1.1.0)
- React
- react-native-netinfo (4.4.0):
- React
@ -388,8 +339,6 @@ PODS:
- Swime (3.0.6)
- XCDYouTubeKit (2.8.2)
- Yoga (1.14.0)
- YogaKit (1.18.1):
- Yoga (~> 1.14)
- YoutubePlayer-in-WKWebView (0.3.4)
DEPENDENCIES:
@ -397,11 +346,6 @@ DEPENDENCIES:
- DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
- FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`)
- FBReactNativeSpec (from `../node_modules/react-native/Libraries/FBReactNativeSpec`)
- FlipperKit (~> 0.33.1)
- FlipperKit/FlipperKitLayoutPlugin (~> 0.33.1)
- FlipperKit/FlipperKitReactPlugin (~> 0.33.1)
- FlipperKit/FlipperKitUserDefaultsPlugin (~> 0.33.1)
- FlipperKit/SKIOSNetworkPlugin (~> 0.33.1)
- Folly (from `../node_modules/react-native/third-party-podspecs/Folly.podspec`)
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
- jail-monkey (from `../node_modules/jail-monkey`)
@ -464,24 +408,14 @@ DEPENDENCIES:
SPEC REPOS:
https://github.com/cocoapods/specs.git:
- boost-for-react-native
- CocoaAsyncSocket
- CocoaLibEvent
- Flipper
- Flipper-DoubleConversion
- Flipper-Folly
- Flipper-Glog
- Flipper-PeerTalk
- Flipper-RSocket
- FlipperKit
- libwebp
- MMKV
- OpenSSL-Universal
- MMKVCore
- SDWebImage
- SDWebImageWebPCoder
- Sentry
- Swime
- XCDYouTubeKit
- YogaKit
- YoutubePlayer-in-WKWebView
EXTERNAL SOURCES:
@ -603,24 +537,15 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c
BVLinearGradient: e3aad03778a456d77928f594a649e96995f1c872
CocoaAsyncSocket: 694058e7c0ed05a9e217d1b3c7ded962f4180845
CocoaLibEvent: 2fab71b8bd46dd33ddb959f7928ec5909f838e3f
DoubleConversion: 5805e889d232975c086db112ece9ed034df7a0b2
FBLazyVector: 4aab18c93cd9546e4bfed752b4084585eca8b245
FBReactNativeSpec: 5465d51ccfeecb7faa12f9ae0024f2044ce4044e
Flipper: 6c1f484f9a88d30ab3e272800d53688439e50f69
Flipper-DoubleConversion: 38631e41ef4f9b12861c67d17cb5518d06badc41
Flipper-Folly: 2de3d03e0acc7064d5e4ed9f730e2f217486f162
Flipper-Glog: 1dfd6abf1e922806c52ceb8701a3599a79a200a6
Flipper-PeerTalk: 116d8f857dc6ef55c7a5a75ea3ceaafe878aadc9
Flipper-RSocket: 1260a31c05c238eabfa9bb8a64e3983049048371
FlipperKit: 6dc9b8f4ef60d9e5ded7f0264db299c91f18832e
Folly: 30e7936e1c45c08d884aa59369ed951a8e68cf51
glog: 1f3da668190260b06b429bb211bfbee5cd790c28
jail-monkey: d7c5048b2336f22ee9c9e0efa145f1f917338ea9
libwebp: 946cb3063cea9236285f7e9a8505d806d30e07f3
MMKV: 758b2edee46b08bdd958db4169191afb9a6d4ebd
OpenSSL-Universal: 8b48cc0d10c1b2923617dfe5c178aa9ed2689355
MMKV: 7bb6c30f9ff2ea45bc2398c86e66dd1cb63cfe20
MMKVCore: f1e0aad4fc330e7cbfbdff16720017bf0973db5a
Permission-Camera: 8f0e5decca5f28f70f28a8dc31f012c9bad40ad8
Permission-PhotoLibrary: b209bf23b784c9e1409a57d81c6d11ab1d3079c1
RCTRequired: cec6a34b3ac8a9915c37e7e4ad3aa74726ce4035
@ -638,7 +563,7 @@ SPEC CHECKSUMS:
react-native-document-picker: 6acd41af22988cf349848678fdaa294d4448478e
react-native-hw-keyboard-event: b517cefb8d5c659a38049c582de85ff43337dc53
react-native-image-picker: 668e72d0277dc8c12ae90e835507c1eddd2e4f85
react-native-mmkv-storage: 84162ebe353ecf7476d235c47becade29789ae2c
react-native-mmkv-storage: d413e1e00b1f410a744e1d547a912a1f87e67260
react-native-netinfo: 892a5130be97ff8bb69c523739c424a2ffc296d1
react-native-notifications: 24706907104a0f00c35c4bde7e0ca76a50f730e1
react-native-passcode-status: 88c4f6e074328bc278bd127646b6c694ad5a530a
@ -676,9 +601,8 @@ SPEC CHECKSUMS:
Swime: d7b2c277503b6cea317774aedc2dce05613f8b0b
XCDYouTubeKit: 79baadb0560673a67c771eba45f83e353fd12c1f
Yoga: 3ebccbdd559724312790e7742142d062476b698e
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
YoutubePlayer-in-WKWebView: af2f5929fc78882d94bfdfeea999b661b78d9717
PODFILE CHECKSUM: bc348868369c4079cbe32240a0dfc0f985e21f5d
PODFILE CHECKSUM: b702b8a61a954eeed0648fa4ec502da4ea8b6748
COCOAPODS: 1.7.5

6
package-lock.json generated
View file

@ -20040,9 +20040,9 @@
"integrity": "sha512-36cYGZGCG82pMiVJbQa5WMA93khP4v5JqLutFkMyB/eRpCULHmojNIBlbUPIY9SCeN4sg5VBRFTVGCtTg2r2kA=="
},
"react-native-mmkv-storage": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/react-native-mmkv-storage/-/react-native-mmkv-storage-0.2.2.tgz",
"integrity": "sha512-QBMQzz7wpS2DGjOn5kIppqiD26p+jOJaJ0Am4DG2PImcUGe9iub5mkamz02mlqjyhq+8EhUes533uLK98APtmw=="
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/react-native-mmkv-storage/-/react-native-mmkv-storage-0.3.1.tgz",
"integrity": "sha512-rNFgtgzvCsDsXU5MnKz5PA5GMkq8TyyixnGSXBZ3tIatwN/3Lq09OJA0YR7UEwsQOuIcnPsEtGEf7suBHo/SwA=="
},
"react-native-navigation": {
"version": "6.4.0",

View file

@ -51,7 +51,7 @@
"react-native-keychain": "4.0.5",
"react-native-linear-gradient": "2.5.6",
"react-native-local-auth": "1.6.0",
"react-native-mmkv-storage": "0.2.2",
"react-native-mmkv-storage": "0.3.1",
"react-native-navigation": "6.4.0",
"react-native-notifications": "2.1.7",
"react-native-passcode-status": "1.1.2",
@ -150,7 +150,7 @@
"check": "eslint --ignore-path .gitignore --ignore-pattern node_modules --quiet . && npm run tsc",
"fix": "eslint --ignore-path .gitignore --ignore-pattern node_modules --quiet . --fix",
"postinstall": "make post-install",
"tsc": "tsc --noEmit",
"tsc": "NODE_OPTIONS=--max_old_space_size=12000 tsc --noEmit",
"test": "jest --forceExit --detectOpenHandles",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",

View file

@ -6,8 +6,8 @@ module.exports = [
'app/actions/helpers/channels.ts',
'app/actions/navigation/index.js',
'app/actions/views/channel.js',
'app/actions/views/emoji.js',
'app/actions/views/post.js',
'app/actions/views/login.js',
'app/actions/views/root.js',
'app/actions/views/select_server.js',
'app/actions/views/user.js',
@ -28,62 +28,26 @@ module.exports = [
'app/init/emm_provider.js',
'app/init/fetch.js',
'app/init/global_event_handler.js',
'app/initial_state.js',
'app/mattermost.js',
'app/mattermost_bucket/index.js',
'app/mattermost_managed/index.js',
'app/mattermost_managed/mattermost-managed.android.js',
'app/push_notifications/index.js',
'app/push_notifications/push_notifications.android.js',
'app/reducers/app/build.js',
'app/reducers/app/index.js',
'app/reducers/app/version.js',
'app/reducers/device/connection.js',
'app/reducers/device/dimension.js',
'app/reducers/device/index.js',
'app/reducers/device/is_tablet.js',
'app/reducers/device/orientation.js',
'app/reducers/device/status_bar.js',
'app/reducers/index.js',
'app/reducers/navigation/index.js',
'app/reducers/views/announcement.js',
'app/reducers/views/channel.js',
'app/reducers/views/client_upgrade.js',
'app/reducers/views/emoji.js',
'app/reducers/views/extension.js',
'app/reducers/views/i18n.js',
'app/reducers/views/index.js',
'app/reducers/views/post.js',
'app/reducers/views/recent_emojis.js',
'app/reducers/views/root.js',
'app/reducers/views/search.js',
'app/reducers/views/select_server.js',
'app/reducers/views/team.js',
'app/reducers/views/thread.js',
'app/reducers/views/user.js',
'app/mm-redux/action_types/bots.ts',
'app/mm-redux/action_types/channels.ts',
'app/mm-redux/action_types/emojis.ts',
'app/mm-redux/action_types/errors.ts',
'app/mm-redux/action_types/files.ts',
'app/mm-redux/action_types/general.ts',
'app/mm-redux/action_types/gifs.ts',
'app/mm-redux/action_types/groups.ts',
'app/mm-redux/action_types/index.ts',
'app/mm-redux/action_types/integrations.ts',
'app/mm-redux/action_types/jobs.ts',
'app/mm-redux/action_types/plugins.ts',
'app/mm-redux/action_types/posts.ts',
'app/mm-redux/action_types/preferences.ts',
'app/mm-redux/action_types/roles.ts',
'app/mm-redux/action_types/schemes.ts',
'app/mm-redux/action_types/search.ts',
'app/mm-redux/action_types/teams.ts',
'app/mm-redux/action_types/users.ts',
'app/mm-redux/actions/channels.ts',
'app/mm-redux/actions/emojis.ts',
'app/mm-redux/actions/errors.ts',
'app/mm-redux/actions/files.ts',
'app/mm-redux/actions/general.ts',
'app/mm-redux/actions/helpers.ts',
'app/mm-redux/actions/posts.ts',
@ -96,19 +60,19 @@ module.exports = [
'app/mm-redux/client/fetch_etag.ts',
'app/mm-redux/client/index.ts',
'app/mm-redux/constants/emoji.ts',
'app/mm-redux/constants/files.ts',
'app/mm-redux/constants/general.ts',
'app/mm-redux/constants/groups.ts',
'app/mm-redux/constants/index.ts',
'app/mm-redux/constants/permissions.ts',
'app/mm-redux/constants/plugins.ts',
'app/mm-redux/constants/posts.ts',
'app/mm-redux/constants/preferences.ts',
'app/mm-redux/constants/request_status.ts',
'app/mm-redux/constants/roles.ts',
'app/mm-redux/constants/stats.ts',
'app/mm-redux/constants/teams.ts',
'app/mm-redux/constants/users.ts',
'app/mm-redux/reducers/entities/bots.ts',
'app/mm-redux/reducers/entities/channel_categories.ts',
'app/mm-redux/reducers/entities/channels.ts',
'app/mm-redux/reducers/entities/emojis.ts',
'app/mm-redux/reducers/entities/files.ts',
@ -152,10 +116,6 @@ module.exports = [
'app/mm-redux/selectors/entities/teams.ts',
'app/mm-redux/selectors/entities/timezone.ts',
'app/mm-redux/selectors/entities/users.ts',
'app/mm-redux/store/helpers.ts',
'app/mm-redux/store/index.ts',
'app/mm-redux/store/initial_state.ts',
'app/mm-redux/store/middleware.ts',
'app/mm-redux/types/actions.ts',
'app/mm-redux/utils/channel_utils.ts',
'app/mm-redux/utils/emoji_utils.ts',
@ -164,19 +124,54 @@ module.exports = [
'app/mm-redux/utils/helpers.ts',
'app/mm-redux/utils/i18n_utils.ts',
'app/mm-redux/utils/key_mirror.ts',
'app/mm-redux/utils/post_list.ts',
'app/mm-redux/utils/post_utils.ts',
'app/mm-redux/utils/preference_utils.ts',
'app/mm-redux/utils/sentry.ts',
'app/mm-redux/utils/team_utils.ts',
'app/mm-redux/utils/timezone_utils.ts',
'app/mm-redux/utils/user_utils.ts',
'app/push_notifications/index.js',
'app/push_notifications/push_notifications.android.js',
'app/reducers/app/build.js',
'app/reducers/app/index.js',
'app/reducers/app/version.js',
'app/reducers/device/connection.js',
'app/reducers/device/dimension.js',
'app/reducers/device/index.js',
'app/reducers/device/is_tablet.js',
'app/reducers/device/orientation.js',
'app/reducers/device/status_bar.js',
'app/reducers/index.js',
'app/reducers/views/announcement.js',
'app/reducers/views/channel.js',
'app/reducers/views/client_upgrade.js',
'app/reducers/views/emoji.js',
'app/reducers/views/extension.js',
'app/reducers/views/i18n.js',
'app/reducers/views/index.js',
'app/reducers/views/post.js',
'app/reducers/views/recent_emojis.js',
'app/reducers/views/root.js',
'app/reducers/views/search.js',
'app/reducers/views/select_server.js',
'app/reducers/views/team.js',
'app/reducers/views/thread.js',
'app/reducers/views/user.js',
'app/screens/index.js',
'app/selectors/channel.js',
'app/selectors/i18n.js',
'app/store/ephemeral_store.js',
'app/store/index.js',
'app/store/middleware.js',
'app/store/store.js',
'app/store/thunk.js',
'app/store/helpers.ts',
'app/store/index.ts',
'app/store/initial_state.js',
'app/store/middlewares/helpers.js',
'app/store/middlewares/index.ts',
'app/store/middlewares/message_retention.js',
'app/store/middlewares/sentry.js',
'app/store/middlewares/thunk.js',
'app/store/mmkv_adapter.ts',
'app/store/store.ts',
'app/store/utils.js',
'app/telemetry/index.js',
'app/telemetry/telemetry.android.js',
@ -185,13 +180,11 @@ module.exports = [
'app/utils/file.js',
'app/utils/general.js',
'app/utils/i18n.js',
'app/utils/image_cache_manager.js',
'app/utils/preferences.js',
'app/utils/push_notifications.js',
'app/utils/security.js',
'app/utils/segment.js',
'app/utils/sentry/index.js',
'app/utils/sentry/middleware.js',
'app/utils/time_tracker.js',
'app/utils/timezone.js',
'dist/assets/config.json',
@ -227,6 +220,7 @@ module.exports = [
'node_modules/@babel/runtime/helpers/typeof.js',
'node_modules/@babel/runtime/helpers/unsupportedIterableToArray.js',
'node_modules/@babel/runtime/helpers/wrapNativeSuper.js',
'node_modules/@babel/runtime/node_modules/regenerator-runtime/runtime.js',
'node_modules/@babel/runtime/regenerator/index.js',
'node_modules/@react-native-community/async-storage/lib/AsyncStorage.js',
'node_modules/@react-native-community/async-storage/lib/hooks.js',
@ -251,6 +245,12 @@ module.exports = [
'node_modules/color-name/index.js',
'node_modules/color-string/index.js',
'node_modules/color/index.js',
'node_modules/create-react-class/factory.js',
'node_modules/create-react-class/index.js',
'node_modules/create-react-class/node_modules/fbjs/lib/emptyFunction.js',
'node_modules/create-react-class/node_modules/fbjs/lib/emptyObject.js',
'node_modules/create-react-class/node_modules/fbjs/lib/invariant.js',
'node_modules/create-react-class/node_modules/fbjs/lib/warning.js',
'node_modules/component-emitter/index.js',
'node_modules/deepmerge/dist/cjs.js',
'node_modules/event-target-shim/dist/event-target-shim.js',
@ -263,10 +263,8 @@ module.exports = [
'node_modules/fbjs/lib/performance.js',
'node_modules/fbjs/lib/performanceNow.js',
'node_modules/fbjs/lib/warning.js',
'node_modules/get-params/index.js',
'node_modules/harmony-reflect/reflect.js',
'node_modules/hoist-non-react-statics/dist/hoist-non-react-statics.cjs.js',
'node_modules/ieee754/index.js',
'node_modules/inherits/inherits_browser.js',
'node_modules/intl-format-cache/dist/index.js',
'node_modules/intl-format-cache/index.js',
@ -290,16 +288,9 @@ module.exports = [
'node_modules/intl/index.js',
'node_modules/intl/lib/core.js',
'node_modules/intl/locale-data/complete.js',
'node_modules/invariant/browser.js',
'node_modules/is-arguments/index.js',
'node_modules/is-generator-function/index.js',
'node_modules/jsan/index.js',
'node_modules/jsan/lib/cycle.js',
'node_modules/jsan/lib/index.js',
'node_modules/jsan/lib/path-getter.js',
'node_modules/jsan/lib/utils.js',
'node_modules/json-stringify-safe/stringify.js',
'node_modules/linked-list/_source/linked-list.js',
'node_modules/linked-list/index.js',
'node_modules/lodash.clonedeep/index.js',
'node_modules/lodash.forin/index.js',
'node_modules/lodash.get/index.js',
@ -322,8 +313,6 @@ module.exports = [
'node_modules/lodash/_apply.js',
'node_modules/lodash/_arrayEach.js',
'node_modules/lodash/_arrayFilter.js',
'node_modules/lodash/_arrayIncludes.js',
'node_modules/lodash/_arrayIncludesWith.js',
'node_modules/lodash/_arrayLikeKeys.js',
'node_modules/lodash/_arrayMap.js',
'node_modules/lodash/_arrayPush.js',
@ -337,9 +326,7 @@ module.exports = [
'node_modules/lodash/_baseClamp.js',
'node_modules/lodash/_baseClone.js',
'node_modules/lodash/_baseCreate.js',
'node_modules/lodash/_baseDifference.js',
'node_modules/lodash/_baseEach.js',
'node_modules/lodash/_baseFindIndex.js',
'node_modules/lodash/_baseFlatten.js',
'node_modules/lodash/_baseFor.js',
'node_modules/lodash/_baseForOwn.js',
@ -347,13 +334,11 @@ module.exports = [
'node_modules/lodash/_baseGetAllKeys.js',
'node_modules/lodash/_baseGetTag.js',
'node_modules/lodash/_baseHasIn.js',
'node_modules/lodash/_baseIndexOf.js',
'node_modules/lodash/_baseIsArguments.js',
'node_modules/lodash/_baseIsEqual.js',
'node_modules/lodash/_baseIsEqualDeep.js',
'node_modules/lodash/_baseIsMap.js',
'node_modules/lodash/_baseIsMatch.js',
'node_modules/lodash/_baseIsNaN.js',
'node_modules/lodash/_baseIsNative.js',
'node_modules/lodash/_baseIsSet.js',
'node_modules/lodash/_baseIsTypedArray.js',
@ -376,7 +361,6 @@ module.exports = [
'node_modules/lodash/_baseTimes.js',
'node_modules/lodash/_baseToString.js',
'node_modules/lodash/_baseUnary.js',
'node_modules/lodash/_baseUniq.js',
'node_modules/lodash/_baseUnset.js',
'node_modules/lodash/_cacheHas.js',
'node_modules/lodash/_castFunction.js',
@ -464,14 +448,12 @@ module.exports = [
'node_modules/lodash/_stackGet.js',
'node_modules/lodash/_stackHas.js',
'node_modules/lodash/_stackSet.js',
'node_modules/lodash/_strictIndexOf.js',
'node_modules/lodash/_stringToPath.js',
'node_modules/lodash/_toKey.js',
'node_modules/lodash/_toSource.js',
'node_modules/lodash/clone.js',
'node_modules/lodash/cloneDeep.js',
'node_modules/lodash/constant.js',
'node_modules/lodash/difference.js',
'node_modules/lodash/endsWith.js',
'node_modules/lodash/eq.js',
'node_modules/lodash/flatten.js',
@ -501,7 +483,6 @@ module.exports = [
'node_modules/lodash/last.js',
'node_modules/lodash/lodash.js',
'node_modules/lodash/map.js',
'node_modules/lodash/mapValues.js',
'node_modules/lodash/memoize.js',
'node_modules/lodash/merge.js',
'node_modules/lodash/noop.js',
@ -516,7 +497,6 @@ module.exports = [
'node_modules/lodash/toNumber.js',
'node_modules/lodash/toPlainObject.js',
'node_modules/lodash/toString.js',
'node_modules/lodash/union.js',
'node_modules/lodash/uniqueId.js',
'node_modules/lodash/unset.js',
'node_modules/metro/src/lib/bundle-modules/HMRClient.js',
@ -524,7 +504,6 @@ module.exports = [
'node_modules/moment-timezone/index.js',
'node_modules/moment-timezone/moment-timezone.js',
'node_modules/moment/moment.js',
'node_modules/nanoid/non-secure/index.js',
'node_modules/nullthrows/nullthrows.js',
'node_modules/object-assign/index.js',
'node_modules/promise/setimmediate/core.js',
@ -536,9 +515,6 @@ module.exports = [
'node_modules/prop-types/factoryWithTypeCheckers.js',
'node_modules/prop-types/index.js',
'node_modules/prop-types/lib/ReactPropTypesSecret.js',
'node_modules/querystring/decode.js',
'node_modules/querystring/encode.js',
'node_modules/querystring/index.js',
'node_modules/querystringify/index.js',
'node_modules/react-intl/lib/index.js',
'node_modules/react-intl/locale-data/en.js',
@ -548,10 +524,6 @@ module.exports = [
'node_modules/react-lifecycles-compat/react-lifecycles-compat.cjs.js',
'node_modules/react-native-cookies/index.js',
'node_modules/react-native-device-info/deviceinfo.js',
'node_modules/react-native-gesture-handler/Directions.js',
'node_modules/react-native-gesture-handler/DrawerLayout.js',
'node_modules/react-native-gesture-handler/GestureButtons.js',
'node_modules/react-native-gesture-handler/GestureComponents.js',
'node_modules/react-native-gesture-handler/GestureHandler.js',
'node_modules/react-native-gesture-handler/GestureHandlerButton.js',
'node_modules/react-native-gesture-handler/GestureHandlerPropTypes.js',
@ -561,19 +533,24 @@ module.exports = [
'node_modules/react-native-gesture-handler/PlatformConstants.js',
'node_modules/react-native-gesture-handler/RNGestureHandlerModule.js',
'node_modules/react-native-gesture-handler/State.js',
'node_modules/react-native-gesture-handler/Swipeable.js',
'node_modules/react-native-gesture-handler/createHandler.js',
'node_modules/react-native-gesture-handler/createNativeWrapper.js',
'node_modules/react-native-gesture-handler/gestureHandlerRootHOC.js',
'node_modules/react-native-gesture-handler/index.js',
'node_modules/react-native-gesture-handler/touchables/GenericTouchable.js',
'node_modules/react-native-gesture-handler/touchables/TouchableHighlight.js',
'node_modules/react-native-gesture-handler/touchables/TouchableNativeFeedback.android.js',
'node_modules/react-native-gesture-handler/touchables/TouchableOpacity.js',
'node_modules/react-native-gesture-handler/touchables/TouchableWithoutFeedback.js',
'node_modules/react-native-gesture-handler/touchables/index.js',
'node_modules/react-native-haptic-feedback/index.js',
'node_modules/react-native-keychain/index.js',
'node_modules/react-native-mmkv-storage/index.js',
'node_modules/react-native-mmkv-storage/src/api.js',
'node_modules/react-native-mmkv-storage/src/encryption.js',
'node_modules/react-native-mmkv-storage/src/indexer/arrays.js',
'node_modules/react-native-mmkv-storage/src/indexer/booleans.js',
'node_modules/react-native-mmkv-storage/src/indexer/indexer.js',
'node_modules/react-native-mmkv-storage/src/indexer/maps.js',
'node_modules/react-native-mmkv-storage/src/indexer/numbers.js',
'node_modules/react-native-mmkv-storage/src/indexer/strings.js',
'node_modules/react-native-mmkv-storage/src/keygen.js',
'node_modules/react-native-mmkv-storage/src/loader.js',
'node_modules/react-native-mmkv-storage/src/utils.js',
'node_modules/react-native-navigation/lib/dist/Navigation.js',
'node_modules/react-native-navigation/lib/dist/adapters/AppRegistryService.js',
'node_modules/react-native-navigation/lib/dist/adapters/AssetResolver.js',
@ -851,25 +828,25 @@ module.exports = [
'node_modules/react-redux/lib/utils/verifyPlainObject.js',
'node_modules/react-redux/lib/utils/warning.js',
'node_modules/react-redux/node_modules/hoist-non-react-statics/dist/hoist-non-react-statics.cjs.js',
'node_modules/react-refresh/cjs/react-refresh-runtime.production.js',
'node_modules/react-refresh/cjs/react-refresh-runtime.production.min.js',
'node_modules/react-refresh/runtime.js',
'node_modules/react/cjs/react.production.js',
'node_modules/react/cjs/react.production.min.js',
'node_modules/react/index.js',
'node_modules/redux-action-buffer/index.js',
'node_modules/redux-batched-actions/lib/index.js',
'node_modules/redux-persist-transform-filter/dist/index.js',
'node_modules/redux-persist/lib/autoRehydrate.js',
'node_modules/redux-persist/lib/constants.js',
'node_modules/redux-persist/lib/createPersistor.js',
'node_modules/redux-persist/lib/createMigrate.js',
'node_modules/redux-persist/lib/createPersistoid.js',
'node_modules/redux-persist/lib/createTransform.js',
'node_modules/redux-persist/lib/defaults/asyncLocalStorage.js',
'node_modules/redux-persist/lib/getStoredState.js',
'node_modules/redux-persist/lib/index.js',
'node_modules/redux-persist/lib/persistCombineReducers.js',
'node_modules/redux-persist/lib/persistReducer.js',
'node_modules/redux-persist/lib/persistStore.js',
'node_modules/redux-persist/lib/purgeStoredState.js',
'node_modules/redux-persist/lib/utils/isStatePlainEnough.js',
'node_modules/redux-persist/lib/utils/setImmediate.js',
'node_modules/redux-thunk/lib/index.js',
'node_modules/redux-persist/lib/stateReconciler/autoMergeLevel1.js',
'node_modules/redux-reset/lib/index.js',
'node_modules/redux/lib/redux.js',
'node_modules/requires-port/index.js',
'node_modules/reselect/lib/index.js',
@ -880,13 +857,18 @@ module.exports = [
'node_modules/rn-fetch-blob/ios.js',
'node_modules/rn-fetch-blob/json-stream.js',
'node_modules/rn-fetch-blob/polyfill/Fetch.js',
'node_modules/rn-host-detect/index.js',
'node_modules/sc-channel/index.js',
'node_modules/sc-errors/decycle.js',
'node_modules/sc-errors/index.js',
'node_modules/sc-formatter/index.js',
'node_modules/scheduler/cjs/scheduler-tracing.production.js',
'node_modules/scheduler/cjs/scheduler.production.js',
'node_modules/rn-fetch-blob/polyfill/File.js',
'node_modules/rn-fetch-blob/polyfill/FileReader.js',
'node_modules/rn-fetch-blob/polyfill/ProgressEvent.js',
'node_modules/rn-fetch-blob/polyfill/XMLHttpRequest.js',
'node_modules/rn-fetch-blob/polyfill/XMLHttpRequestEventTarget.js',
'node_modules/rn-fetch-blob/polyfill/index.js',
'node_modules/rn-fetch-blob/utils/log.js',
'node_modules/rn-fetch-blob/utils/unicode.js',
'node_modules/rn-fetch-blob/utils/uri.js',
'node_modules/rn-fetch-blob/utils/uuid.js',
'node_modules/scheduler/cjs/scheduler-tracing.production.min.js',
'node_modules/scheduler/cjs/scheduler.production.min.js',
'node_modules/scheduler/index.js',
'node_modules/scheduler/tracing.js',
'node_modules/semver/classes/comparator.js',
@ -937,7 +919,6 @@ module.exports = [
'node_modules/stacktrace-parser/dist/stack-trace-parser.cjs.js',
'node_modules/symbol-observable/lib/index.js',
'node_modules/symbol-observable/lib/ponyfill.js',
'node_modules/tslib/tslib.js',
'node_modules/url-parse/index.js',
'node_modules/util/support/isBufferBrowser.js',
'node_modules/util/support/types.js',

View file

@ -1,12 +1,13 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
module.exports = [
'./node_modules/app/actions/device/index.js',
'./node_modules/app/actions/helpers/channels.ts',
'./node_modules/app/actions/navigation/index.js',
'./node_modules/app/actions/views/channel.js',
'./node_modules/app/actions/views/emoji.js',
'./node_modules/app/actions/views/post.js',
'./node_modules/app/actions/views/login.js',
'./node_modules/app/actions/views/root.js',
'./node_modules/app/actions/views/select_server.js',
'./node_modules/app/actions/views/user.js',
@ -27,62 +28,26 @@ module.exports = [
'./node_modules/app/init/emm_provider.js',
'./node_modules/app/init/fetch.js',
'./node_modules/app/init/global_event_handler.js',
'./node_modules/app/initial_state.js',
'./node_modules/app/mattermost.js',
'./node_modules/app/mattermost_bucket/index.js',
'./node_modules/app/mattermost_managed/index.js',
'./node_modules/app/mattermost_managed/mattermost-managed.android.js',
'./node_modules/app/push_notifications/index.js',
'./node_modules/app/push_notifications/push_notifications.android.js',
'./node_modules/app/reducers/app/build.js',
'./node_modules/app/reducers/app/index.js',
'./node_modules/app/reducers/app/version.js',
'./node_modules/app/reducers/device/connection.js',
'./node_modules/app/reducers/device/dimension.js',
'./node_modules/app/reducers/device/index.js',
'./node_modules/app/reducers/device/is_tablet.js',
'./node_modules/app/reducers/device/orientation.js',
'./node_modules/app/reducers/device/status_bar.js',
'./node_modules/app/reducers/index.js',
'./node_modules/app/reducers/navigation/index.js',
'./node_modules/app/reducers/views/announcement.js',
'./node_modules/app/reducers/views/channel.js',
'./node_modules/app/reducers/views/client_upgrade.js',
'./node_modules/app/reducers/views/emoji.js',
'./node_modules/app/reducers/views/extension.js',
'./node_modules/app/reducers/views/i18n.js',
'./node_modules/app/reducers/views/index.js',
'./node_modules/app/reducers/views/post.js',
'./node_modules/app/reducers/views/recent_emojis.js',
'./node_modules/app/reducers/views/root.js',
'./node_modules/app/reducers/views/search.js',
'./node_modules/app/reducers/views/select_server.js',
'./node_modules/app/reducers/views/team.js',
'./node_modules/app/reducers/views/thread.js',
'./node_modules/app/reducers/views/user.js',
'./node_modules/app/mm-redux/action_types/bots.ts',
'./node_modules/app/mm-redux/action_types/channels.ts',
'./node_modules/app/mm-redux/action_types/emojis.ts',
'./node_modules/app/mm-redux/action_types/errors.ts',
'./node_modules/app/mm-redux/action_types/files.ts',
'./node_modules/app/mm-redux/action_types/general.ts',
'./node_modules/app/mm-redux/action_types/gifs.ts',
'./node_modules/app/mm-redux/action_types/groups.ts',
'./node_modules/app/mm-redux/action_types/index.ts',
'./node_modules/app/mm-redux/action_types/integrations.ts',
'./node_modules/app/mm-redux/action_types/jobs.ts',
'./node_modules/app/mm-redux/action_types/plugins.ts',
'./node_modules/app/mm-redux/action_types/posts.ts',
'./node_modules/app/mm-redux/action_types/preferences.ts',
'./node_modules/app/mm-redux/action_types/roles.ts',
'./node_modules/app/mm-redux/action_types/schemes.ts',
'./node_modules/app/mm-redux/action_types/search.ts',
'./node_modules/app/mm-redux/action_types/teams.ts',
'./node_modules/app/mm-redux/action_types/users.ts',
'./node_modules/app/mm-redux/actions/channels.ts',
'./node_modules/app/mm-redux/actions/emojis.ts',
'./node_modules/app/mm-redux/actions/errors.ts',
'./node_modules/app/mm-redux/actions/files.ts',
'./node_modules/app/mm-redux/actions/general.ts',
'./node_modules/app/mm-redux/actions/helpers.ts',
'./node_modules/app/mm-redux/actions/posts.ts',
@ -95,19 +60,19 @@ module.exports = [
'./node_modules/app/mm-redux/client/fetch_etag.ts',
'./node_modules/app/mm-redux/client/index.ts',
'./node_modules/app/mm-redux/constants/emoji.ts',
'./node_modules/app/mm-redux/constants/files.ts',
'./node_modules/app/mm-redux/constants/general.ts',
'./node_modules/app/mm-redux/constants/groups.ts',
'./node_modules/app/mm-redux/constants/index.ts',
'./node_modules/app/mm-redux/constants/permissions.ts',
'./node_modules/app/mm-redux/constants/plugins.ts',
'./node_modules/app/mm-redux/constants/posts.ts',
'./node_modules/app/mm-redux/constants/preferences.ts',
'./node_modules/app/mm-redux/constants/request_status.ts',
'./node_modules/app/mm-redux/constants/roles.ts',
'./node_modules/app/mm-redux/constants/stats.ts',
'./node_modules/app/mm-redux/constants/teams.ts',
'./node_modules/app/mm-redux/constants/users.ts',
'./node_modules/app/mm-redux/reducers/entities/bots.ts',
'./node_modules/app/mm-redux/reducers/entities/channel_categories.ts',
'./node_modules/app/mm-redux/reducers/entities/channels.ts',
'./node_modules/app/mm-redux/reducers/entities/emojis.ts',
'./node_modules/app/mm-redux/reducers/entities/files.ts',
@ -151,10 +116,6 @@ module.exports = [
'./node_modules/app/mm-redux/selectors/entities/teams.ts',
'./node_modules/app/mm-redux/selectors/entities/timezone.ts',
'./node_modules/app/mm-redux/selectors/entities/users.ts',
'./node_modules/app/mm-redux/store/helpers.ts',
'./node_modules/app/mm-redux/store/index.ts',
'./node_modules/app/mm-redux/store/initial_state.ts',
'./node_modules/app/mm-redux/store/middleware.ts',
'./node_modules/app/mm-redux/types/actions.ts',
'./node_modules/app/mm-redux/utils/channel_utils.ts',
'./node_modules/app/mm-redux/utils/emoji_utils.ts',
@ -163,19 +124,54 @@ module.exports = [
'./node_modules/app/mm-redux/utils/helpers.ts',
'./node_modules/app/mm-redux/utils/i18n_utils.ts',
'./node_modules/app/mm-redux/utils/key_mirror.ts',
'./node_modules/app/mm-redux/utils/post_list.ts',
'./node_modules/app/mm-redux/utils/post_utils.ts',
'./node_modules/app/mm-redux/utils/preference_utils.ts',
'./node_modules/app/mm-redux/utils/sentry.ts',
'./node_modules/app/mm-redux/utils/team_utils.ts',
'./node_modules/app/mm-redux/utils/timezone_utils.ts',
'./node_modules/app/mm-redux/utils/user_utils.ts',
'./node_modules/app/push_notifications/index.js',
'./node_modules/app/push_notifications/push_notifications.android.js',
'./node_modules/app/reducers/app/build.js',
'./node_modules/app/reducers/app/index.js',
'./node_modules/app/reducers/app/version.js',
'./node_modules/app/reducers/device/connection.js',
'./node_modules/app/reducers/device/dimension.js',
'./node_modules/app/reducers/device/index.js',
'./node_modules/app/reducers/device/is_tablet.js',
'./node_modules/app/reducers/device/orientation.js',
'./node_modules/app/reducers/device/status_bar.js',
'./node_modules/app/reducers/index.js',
'./node_modules/app/reducers/views/announcement.js',
'./node_modules/app/reducers/views/channel.js',
'./node_modules/app/reducers/views/client_upgrade.js',
'./node_modules/app/reducers/views/emoji.js',
'./node_modules/app/reducers/views/extension.js',
'./node_modules/app/reducers/views/i18n.js',
'./node_modules/app/reducers/views/index.js',
'./node_modules/app/reducers/views/post.js',
'./node_modules/app/reducers/views/recent_emojis.js',
'./node_modules/app/reducers/views/root.js',
'./node_modules/app/reducers/views/search.js',
'./node_modules/app/reducers/views/select_server.js',
'./node_modules/app/reducers/views/team.js',
'./node_modules/app/reducers/views/thread.js',
'./node_modules/app/reducers/views/user.js',
'./node_modules/app/screens/index.js',
'./node_modules/app/selectors/channel.js',
'./node_modules/app/selectors/i18n.js',
'./node_modules/app/store/ephemeral_store.js',
'./node_modules/app/store/index.js',
'./node_modules/app/store/middleware.js',
'./node_modules/app/store/store.js',
'./node_modules/app/store/thunk.js',
'./node_modules/app/store/helpers.ts',
'./node_modules/app/store/index.ts',
'./node_modules/app/store/initial_state.js',
'./node_modules/app/store/middlewares/helpers.js',
'./node_modules/app/store/middlewares/index.ts',
'./node_modules/app/store/middlewares/message_retention.js',
'./node_modules/app/store/middlewares/sentry.js',
'./node_modules/app/store/middlewares/thunk.js',
'./node_modules/app/store/mmkv_adapter.ts',
'./node_modules/app/store/store.ts',
'./node_modules/app/store/utils.js',
'./node_modules/app/telemetry/index.js',
'./node_modules/app/telemetry/telemetry.android.js',
@ -184,13 +180,11 @@ module.exports = [
'./node_modules/app/utils/file.js',
'./node_modules/app/utils/general.js',
'./node_modules/app/utils/i18n.js',
'./node_modules/app/utils/image_cache_manager.js',
'./node_modules/app/utils/preferences.js',
'./node_modules/app/utils/push_notifications.js',
'./node_modules/app/utils/security.js',
'./node_modules/app/utils/segment.js',
'./node_modules/app/utils/sentry/index.js',
'./node_modules/app/utils/sentry/middleware.js',
'./node_modules/app/utils/time_tracker.js',
'./node_modules/app/utils/timezone.js',
'./node_modules/index.js',
@ -224,19 +218,11 @@ module.exports = [
'./node_modules/node_modules/@babel/runtime/helpers/typeof.js',
'./node_modules/node_modules/@babel/runtime/helpers/unsupportedIterableToArray.js',
'./node_modules/node_modules/@babel/runtime/helpers/wrapNativeSuper.js',
'./node_modules/node_modules/@babel/runtime/node_modules/regenerator-runtime/runtime.js',
'./node_modules/node_modules/@babel/runtime/regenerator/index.js',
'./node_modules/node_modules/@react-native-community/async-storage/lib/AsyncStorage.js',
'./node_modules/node_modules/@react-native-community/async-storage/lib/hooks.js',
'./node_modules/node_modules/@react-native-community/async-storage/lib/index.js',
'./node_modules/node_modules/@react-native-community/netinfo/src/index.ts',
'./node_modules/node_modules/@react-native-community/netinfo/src/internal/deprecatedState.ts',
'./node_modules/node_modules/@react-native-community/netinfo/src/internal/deprecatedTypes.ts',
'./node_modules/node_modules/@react-native-community/netinfo/src/internal/deprecatedUtils.ts',
'./node_modules/node_modules/@react-native-community/netinfo/src/internal/internetReachability.ts',
'./node_modules/node_modules/@react-native-community/netinfo/src/internal/nativeInterface.ts',
'./node_modules/node_modules/@react-native-community/netinfo/src/internal/state.ts',
'./node_modules/node_modules/@react-native-community/netinfo/src/internal/types.ts',
'./node_modules/node_modules/@react-native-community/netinfo/src/internal/utils.ts',
'./node_modules/node_modules/base-64/base64.js',
'./node_modules/node_modules/base64-js/index.js',
'./node_modules/node_modules/buffer/index.js',
@ -247,6 +233,12 @@ module.exports = [
'./node_modules/node_modules/color-name/index.js',
'./node_modules/node_modules/color-string/index.js',
'./node_modules/node_modules/color/index.js',
'./node_modules/node_modules/create-react-class/factory.js',
'./node_modules/node_modules/create-react-class/index.js',
'./node_modules/node_modules/create-react-class/node_modules/fbjs/lib/emptyFunction.js',
'./node_modules/node_modules/create-react-class/node_modules/fbjs/lib/emptyObject.js',
'./node_modules/node_modules/create-react-class/node_modules/fbjs/lib/invariant.js',
'./node_modules/node_modules/create-react-class/node_modules/fbjs/lib/warning.js',
'./node_modules/node_modules/component-emitter/index.js',
'./node_modules/node_modules/deepmerge/dist/cjs.js',
'./node_modules/node_modules/event-target-shim/dist/event-target-shim.js',
@ -259,10 +251,8 @@ module.exports = [
'./node_modules/node_modules/fbjs/lib/performance.js',
'./node_modules/node_modules/fbjs/lib/performanceNow.js',
'./node_modules/node_modules/fbjs/lib/warning.js',
'./node_modules/node_modules/get-params/index.js',
'./node_modules/node_modules/harmony-reflect/reflect.js',
'./node_modules/node_modules/hoist-non-react-statics/dist/hoist-non-react-statics.cjs.js',
'./node_modules/node_modules/ieee754/index.js',
'./node_modules/node_modules/inherits/inherits_browser.js',
'./node_modules/node_modules/intl-format-cache/dist/index.js',
'./node_modules/node_modules/intl-format-cache/index.js',
@ -286,16 +276,9 @@ module.exports = [
'./node_modules/node_modules/intl/index.js',
'./node_modules/node_modules/intl/lib/core.js',
'./node_modules/node_modules/intl/locale-data/complete.js',
'./node_modules/node_modules/invariant/browser.js',
'./node_modules/node_modules/is-arguments/index.js',
'./node_modules/node_modules/is-generator-function/index.js',
'./node_modules/node_modules/jsan/index.js',
'./node_modules/node_modules/jsan/lib/cycle.js',
'./node_modules/node_modules/jsan/lib/index.js',
'./node_modules/node_modules/jsan/lib/path-getter.js',
'./node_modules/node_modules/jsan/lib/utils.js',
'./node_modules/node_modules/json-stringify-safe/stringify.js',
'./node_modules/node_modules/linked-list/_source/linked-list.js',
'./node_modules/node_modules/linked-list/index.js',
'./node_modules/node_modules/lodash.clonedeep/index.js',
'./node_modules/node_modules/lodash.forin/index.js',
'./node_modules/node_modules/lodash.get/index.js',
@ -318,8 +301,6 @@ module.exports = [
'./node_modules/node_modules/lodash/_apply.js',
'./node_modules/node_modules/lodash/_arrayEach.js',
'./node_modules/node_modules/lodash/_arrayFilter.js',
'./node_modules/node_modules/lodash/_arrayIncludes.js',
'./node_modules/node_modules/lodash/_arrayIncludesWith.js',
'./node_modules/node_modules/lodash/_arrayLikeKeys.js',
'./node_modules/node_modules/lodash/_arrayMap.js',
'./node_modules/node_modules/lodash/_arrayPush.js',
@ -333,9 +314,7 @@ module.exports = [
'./node_modules/node_modules/lodash/_baseClamp.js',
'./node_modules/node_modules/lodash/_baseClone.js',
'./node_modules/node_modules/lodash/_baseCreate.js',
'./node_modules/node_modules/lodash/_baseDifference.js',
'./node_modules/node_modules/lodash/_baseEach.js',
'./node_modules/node_modules/lodash/_baseFindIndex.js',
'./node_modules/node_modules/lodash/_baseFlatten.js',
'./node_modules/node_modules/lodash/_baseFor.js',
'./node_modules/node_modules/lodash/_baseForOwn.js',
@ -343,13 +322,11 @@ module.exports = [
'./node_modules/node_modules/lodash/_baseGetAllKeys.js',
'./node_modules/node_modules/lodash/_baseGetTag.js',
'./node_modules/node_modules/lodash/_baseHasIn.js',
'./node_modules/node_modules/lodash/_baseIndexOf.js',
'./node_modules/node_modules/lodash/_baseIsArguments.js',
'./node_modules/node_modules/lodash/_baseIsEqual.js',
'./node_modules/node_modules/lodash/_baseIsEqualDeep.js',
'./node_modules/node_modules/lodash/_baseIsMap.js',
'./node_modules/node_modules/lodash/_baseIsMatch.js',
'./node_modules/node_modules/lodash/_baseIsNaN.js',
'./node_modules/node_modules/lodash/_baseIsNative.js',
'./node_modules/node_modules/lodash/_baseIsSet.js',
'./node_modules/node_modules/lodash/_baseIsTypedArray.js',
@ -372,7 +349,6 @@ module.exports = [
'./node_modules/node_modules/lodash/_baseTimes.js',
'./node_modules/node_modules/lodash/_baseToString.js',
'./node_modules/node_modules/lodash/_baseUnary.js',
'./node_modules/node_modules/lodash/_baseUniq.js',
'./node_modules/node_modules/lodash/_baseUnset.js',
'./node_modules/node_modules/lodash/_cacheHas.js',
'./node_modules/node_modules/lodash/_castFunction.js',
@ -460,14 +436,12 @@ module.exports = [
'./node_modules/node_modules/lodash/_stackGet.js',
'./node_modules/node_modules/lodash/_stackHas.js',
'./node_modules/node_modules/lodash/_stackSet.js',
'./node_modules/node_modules/lodash/_strictIndexOf.js',
'./node_modules/node_modules/lodash/_stringToPath.js',
'./node_modules/node_modules/lodash/_toKey.js',
'./node_modules/node_modules/lodash/_toSource.js',
'./node_modules/node_modules/lodash/clone.js',
'./node_modules/node_modules/lodash/cloneDeep.js',
'./node_modules/node_modules/lodash/constant.js',
'./node_modules/node_modules/lodash/difference.js',
'./node_modules/node_modules/lodash/endsWith.js',
'./node_modules/node_modules/lodash/eq.js',
'./node_modules/node_modules/lodash/flatten.js',
@ -497,7 +471,6 @@ module.exports = [
'./node_modules/node_modules/lodash/last.js',
'./node_modules/node_modules/lodash/lodash.js',
'./node_modules/node_modules/lodash/map.js',
'./node_modules/node_modules/lodash/mapValues.js',
'./node_modules/node_modules/lodash/memoize.js',
'./node_modules/node_modules/lodash/merge.js',
'./node_modules/node_modules/lodash/noop.js',
@ -512,14 +485,12 @@ module.exports = [
'./node_modules/node_modules/lodash/toNumber.js',
'./node_modules/node_modules/lodash/toPlainObject.js',
'./node_modules/node_modules/lodash/toString.js',
'./node_modules/node_modules/lodash/union.js',
'./node_modules/node_modules/lodash/uniqueId.js',
'./node_modules/node_modules/lodash/unset.js',
'./node_modules/node_modules/metro/src/lib/bundle-modules/HMRClient.js',
'./node_modules/node_modules/moment-timezone/index.js',
'./node_modules/node_modules/moment-timezone/moment-timezone.js',
'./node_modules/node_modules/moment/moment.js',
'./node_modules/node_modules/nanoid/non-secure/index.js',
'./node_modules/node_modules/nullthrows/nullthrows.js',
'./node_modules/node_modules/object-assign/index.js',
'./node_modules/node_modules/promise/setimmediate/core.js',
@ -531,22 +502,15 @@ module.exports = [
'./node_modules/node_modules/prop-types/factoryWithTypeCheckers.js',
'./node_modules/node_modules/prop-types/index.js',
'./node_modules/node_modules/prop-types/lib/ReactPropTypesSecret.js',
'./node_modules/node_modules/querystring/decode.js',
'./node_modules/node_modules/querystring/encode.js',
'./node_modules/node_modules/querystring/index.js',
'./node_modules/node_modules/querystringify/index.js',
'./node_modules/node_modules/react-intl/lib/index.js',
'./node_modules/node_modules/react-intl/locale-data/en.js',
'./node_modules/node_modules/react-intl/locale-data/index.js',
'./node_modules/node_modules/react-is/cjs/react-is.production.min.js',
'./node_modules/node_modules/react-is/cjs/react-is.production.js',
'./node_modules/node_modules/react-is/index.js',
'./node_modules/node_modules/react-lifecycles-compat/react-lifecycles-compat.cjs.js',
'./node_modules/node_modules/react-native-cookies/index.js',
'./node_modules/node_modules/react-native-device-info/deviceinfo.js',
'./node_modules/node_modules/react-native-gesture-handler/Directions.js',
'./node_modules/node_modules/react-native-gesture-handler/DrawerLayout.js',
'./node_modules/node_modules/react-native-gesture-handler/GestureButtons.js',
'./node_modules/node_modules/react-native-gesture-handler/GestureComponents.js',
'./node_modules/node_modules/react-native-gesture-handler/GestureHandler.js',
'./node_modules/node_modules/react-native-gesture-handler/GestureHandlerButton.js',
'./node_modules/node_modules/react-native-gesture-handler/GestureHandlerPropTypes.js',
@ -556,19 +520,24 @@ module.exports = [
'./node_modules/node_modules/react-native-gesture-handler/PlatformConstants.js',
'./node_modules/node_modules/react-native-gesture-handler/RNGestureHandlerModule.js',
'./node_modules/node_modules/react-native-gesture-handler/State.js',
'./node_modules/node_modules/react-native-gesture-handler/Swipeable.js',
'./node_modules/node_modules/react-native-gesture-handler/createHandler.js',
'./node_modules/node_modules/react-native-gesture-handler/createNativeWrapper.js',
'./node_modules/node_modules/react-native-gesture-handler/gestureHandlerRootHOC.js',
'./node_modules/node_modules/react-native-gesture-handler/index.js',
'./node_modules/node_modules/react-native-gesture-handler/touchables/GenericTouchable.js',
'./node_modules/node_modules/react-native-gesture-handler/touchables/TouchableHighlight.js',
'./node_modules/node_modules/react-native-gesture-handler/touchables/TouchableNativeFeedback.android.js',
'./node_modules/node_modules/react-native-gesture-handler/touchables/TouchableOpacity.js',
'./node_modules/node_modules/react-native-gesture-handler/touchables/TouchableWithoutFeedback.js',
'./node_modules/node_modules/react-native-gesture-handler/touchables/index.js',
'./node_modules/node_modules/react-native-haptic-feedback/index.js',
'./node_modules/node_modules/react-native-keychain/index.js',
'./node_modules/node_modules/react-native-mmkv-storage/index.js',
'./node_modules/node_modules/react-native-mmkv-storage/src/api.js',
'./node_modules/node_modules/react-native-mmkv-storage/src/encryption.js',
'./node_modules/node_modules/react-native-mmkv-storage/src/indexer/arrays.js',
'./node_modules/node_modules/react-native-mmkv-storage/src/indexer/booleans.js',
'./node_modules/node_modules/react-native-mmkv-storage/src/indexer/indexer.js',
'./node_modules/node_modules/react-native-mmkv-storage/src/indexer/maps.js',
'./node_modules/node_modules/react-native-mmkv-storage/src/indexer/numbers.js',
'./node_modules/node_modules/react-native-mmkv-storage/src/indexer/strings.js',
'./node_modules/node_modules/react-native-mmkv-storage/src/keygen.js',
'./node_modules/node_modules/react-native-mmkv-storage/src/loader.js',
'./node_modules/node_modules/react-native-mmkv-storage/src/utils.js',
'./node_modules/node_modules/react-native-navigation/lib/dist/Navigation.js',
'./node_modules/node_modules/react-native-navigation/lib/dist/adapters/AppRegistryService.js',
'./node_modules/node_modules/react-native-navigation/lib/dist/adapters/AssetResolver.js',
@ -765,6 +734,7 @@ module.exports = [
'./node_modules/node_modules/react-native/Libraries/ReactPrivate/ReactNativePrivateInitializeCore.js',
'./node_modules/node_modules/react-native/Libraries/ReactPrivate/ReactNativePrivateInterface.js',
'./node_modules/node_modules/react-native/Libraries/Renderer/implementations/ReactNativeRenderer-prod.js',
'./node_modules/node_modules/react-native/Libraries/Renderer/shims/NativeMethodsMixin.js',
'./node_modules/node_modules/react-native/Libraries/Renderer/shims/ReactNative.js',
'./node_modules/node_modules/react-native/Libraries/Renderer/shims/ReactNativeViewConfigRegistry.js',
'./node_modules/node_modules/react-native/Libraries/Renderer/shims/createReactNativeComponentClass.js',
@ -852,18 +822,18 @@ module.exports = [
'./node_modules/node_modules/redux-action-buffer/index.js',
'./node_modules/node_modules/redux-batched-actions/lib/index.js',
'./node_modules/node_modules/redux-persist-transform-filter/dist/index.js',
'./node_modules/node_modules/redux-persist/lib/autoRehydrate.js',
'./node_modules/node_modules/redux-persist/lib/constants.js',
'./node_modules/node_modules/redux-persist/lib/createPersistor.js',
'./node_modules/node_modules/redux-persist/lib/createMigrate.js',
'./node_modules/node_modules/redux-persist/lib/createPersistoid.js',
'./node_modules/node_modules/redux-persist/lib/createTransform.js',
'./node_modules/node_modules/redux-persist/lib/defaults/asyncLocalStorage.js',
'./node_modules/node_modules/redux-persist/lib/getStoredState.js',
'./node_modules/node_modules/redux-persist/lib/index.js',
'./node_modules/node_modules/redux-persist/lib/persistCombineReducers.js',
'./node_modules/node_modules/redux-persist/lib/persistReducer.js',
'./node_modules/node_modules/redux-persist/lib/persistStore.js',
'./node_modules/node_modules/redux-persist/lib/purgeStoredState.js',
'./node_modules/node_modules/redux-persist/lib/utils/isStatePlainEnough.js',
'./node_modules/node_modules/redux-persist/lib/utils/setImmediate.js',
'./node_modules/node_modules/redux-thunk/lib/index.js',
'./node_modules/node_modules/redux-persist/lib/stateReconciler/autoMergeLevel1.js',
'./node_modules/node_modules/redux-reset/lib/index.js',
'./node_modules/node_modules/redux/lib/redux.js',
'./node_modules/node_modules/requires-port/index.js',
'./node_modules/node_modules/reselect/lib/index.js',
@ -874,11 +844,16 @@ module.exports = [
'./node_modules/node_modules/rn-fetch-blob/ios.js',
'./node_modules/node_modules/rn-fetch-blob/json-stream.js',
'./node_modules/node_modules/rn-fetch-blob/polyfill/Fetch.js',
'./node_modules/node_modules/rn-host-detect/index.js',
'./node_modules/node_modules/sc-channel/index.js',
'./node_modules/node_modules/sc-errors/decycle.js',
'./node_modules/node_modules/sc-errors/index.js',
'./node_modules/node_modules/sc-formatter/index.js',
'./node_modules/node_modules/rn-fetch-blob/polyfill/File.js',
'./node_modules/node_modules/rn-fetch-blob/polyfill/FileReader.js',
'./node_modules/node_modules/rn-fetch-blob/polyfill/ProgressEvent.js',
'./node_modules/node_modules/rn-fetch-blob/polyfill/XMLHttpRequest.js',
'./node_modules/node_modules/rn-fetch-blob/polyfill/XMLHttpRequestEventTarget.js',
'./node_modules/node_modules/rn-fetch-blob/polyfill/index.js',
'./node_modules/node_modules/rn-fetch-blob/utils/log.js',
'./node_modules/node_modules/rn-fetch-blob/utils/unicode.js',
'./node_modules/node_modules/rn-fetch-blob/utils/uri.js',
'./node_modules/node_modules/rn-fetch-blob/utils/uuid.js',
'./node_modules/node_modules/scheduler/cjs/scheduler-tracing.production.min.js',
'./node_modules/node_modules/scheduler/cjs/scheduler.production.min.js',
'./node_modules/node_modules/scheduler/index.js',
@ -931,7 +906,6 @@ module.exports = [
'./node_modules/node_modules/stacktrace-parser/dist/stack-trace-parser.cjs.js',
'./node_modules/node_modules/symbol-observable/lib/index.js',
'./node_modules/node_modules/symbol-observable/lib/ponyfill.js',
'./node_modules/node_modules/tslib/tslib.js',
'./node_modules/node_modules/url-parse/index.js',
'./node_modules/node_modules/util/support/isBufferBrowser.js',
'./node_modules/node_modules/util/support/types.js',

View file

@ -0,0 +1,47 @@
diff --git a/node_modules/react-native-mmkv-storage/ios/MMKVStorage.m b/node_modules/react-native-mmkv-storage/ios/MMKVStorage.m
index 6ce0b16..a1b3c56 100644
--- a/node_modules/react-native-mmkv-storage/ios/MMKVStorage.m
+++ b/node_modules/react-native-mmkv-storage/ios/MMKVStorage.m
@@ -45,7 +45,10 @@ - (id)init
{
self = [super init];
if (self) {
- [MMKV initialize];
+ NSString *APP_GROUP_ID = @"group.com.mattermost.rnbeta";
+ NSString *groupDir = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:APP_GROUP_ID].path;
+ [MMKV initializeMMKV:nil groupDir:groupDir logLevel:MMKVLogInfo];
+
secureStorage = [[SecureStorage alloc]init];
IdStore = [[IDStore alloc] initWithMMKV:[MMKV mmkvWithID:@"mmkvIdStore"]];
mmkvMap = [NSMutableDictionary dictionary];
diff --git a/node_modules/react-native-mmkv-storage/ios/SecureStorage.m b/node_modules/react-native-mmkv-storage/ios/SecureStorage.m
index 70f3a01..07e1af0 100644
--- a/node_modules/react-native-mmkv-storage/ios/SecureStorage.m
+++ b/node_modules/react-native-mmkv-storage/ios/SecureStorage.m
@@ -184,7 +184,7 @@ - (BOOL)searchKeychainCopyMatchingExists:(NSString *)identifier {
}
- (BOOL)createKeychainValue:(NSString *)value forIdentifier:(NSString *)identifier options: (NSDictionary * __nullable)options {
- CFStringRef accessible = accessibleValue(options);
+ CFStringRef accessible = secureStoreAccessibleValue(options);
NSMutableDictionary *dictionary = [self newSearchDictionary:identifier];
NSData *valueData = [value dataUsingEncoding:NSUTF8StringEncoding];
@@ -201,7 +201,7 @@ - (BOOL)createKeychainValue:(NSString *)value forIdentifier:(NSString *)identifi
- (BOOL)updateKeychainValue:(NSString *)password forIdentifier:(NSString *)identifier options:(NSDictionary * __nullable)options {
- CFStringRef accessible = accessibleValue(options);
+ CFStringRef accessible = secureStoreAccessibleValue(options);
NSMutableDictionary *searchDictionary = [self newSearchDictionary:identifier];
NSMutableDictionary *updateDictionary = [[NSMutableDictionary alloc] init];
NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
@@ -255,7 +255,7 @@ - (void)handleAppUninstallation
-CFStringRef accessibleValue(NSDictionary *options)
+CFStringRef secureStoreAccessibleValue(NSDictionary *options)
{
if (options && options[@"accessible"] != nil) {
NSDictionary *keyMap = @{

View file

@ -0,0 +1,14 @@
diff --git a/node_modules/redux-persist/types/types.d.ts b/node_modules/redux-persist/types/types.d.ts
index b3733bc..882f4de 100644
--- a/node_modules/redux-persist/types/types.d.ts
+++ b/node_modules/redux-persist/types/types.d.ts
@@ -42,7 +42,8 @@ declare module "redux-persist/es/types" {
*/
getStoredState?: (config: PersistConfig<S, RS, HSS, ESS>) => Promise<PersistedState>;
debug?: boolean;
- serialize?: boolean;
+ serialize?: boolean | Function;
+ deserialize?: boolean | Function;
timeout?: number;
writeFailHandler?: (err: Error) => void;
}

View file

@ -12,16 +12,17 @@ if [ -n "$jsfiles" ]; then
echo "$js"
e=$(node_modules/.bin/eslint --quiet --fix $js)
if [[ -n "$e" ]]; then
echo "$e"
echo "ERROR: Check eslint hints."
echo "$e"
exit 1 # reject
fi
done
echo "Checking TypeScript"
tsce=$(npm run tsc)
if [[-n "$tsce"]]; then
echo "$tsce"
echo "Checking for TSC"
tsc=$(node_modules/.bin/tsc --noEmit)
if [[ -n "$tsc" ]]; then
echo "ERROR: Check TSC hints."
echo "$tsc"
exit 1 # reject
fi
fi

View file

@ -5,10 +5,12 @@ import React, {PureComponent} from 'react';
import {Provider} from 'react-redux';
import {IntlProvider} from 'react-intl';
import {getTranslations} from 'app/i18n';
import {getCurrentLocale} from 'app/selectors/i18n';
import store from 'app/store';
import {waitForHydration} from 'app/store/utils';
import {getTranslations} from '@i18n';
import {getCurrentLocale} from '@selectors/i18n';
import configureStore from '@store';
import getStorage from '@store/mmkv_adapter';
import Store from '@store/store';
import {waitForHydration} from '@store/utils';
import {extensionSelectTeamId} from './actions';
let Extension;
@ -26,8 +28,24 @@ export default class ShareApp extends PureComponent {
componentDidMount() {
this.mounted = true;
waitForHydration(store, () => {
const {dispatch, getState} = store;
this.initialize();
}
initialize = async () => {
if (Store.redux) {
this.hydrate();
return;
}
getStorage().then(this.hydrate);
};
hydrate = (MMKVStorage) => {
if (MMKVStorage) {
configureStore(MMKVStorage);
}
waitForHydration(Store.redux, () => {
const {dispatch, getState} = Store.redux;
const {currentTeamId} = getState().entities.teams;
dispatch(extensionSelectTeamId(currentTeamId));
this.setState({init: true});
@ -39,10 +57,10 @@ export default class ShareApp extends PureComponent {
return null;
}
const locale = getCurrentLocale(store.getState());
const locale = getCurrentLocale(Store.redux.getState());
return (
<Provider store={store}>
<Provider store={Store.redux}>
<IntlProvider
locale={locale}
messages={getTranslations(locale)}

View file

@ -3,7 +3,7 @@
import {AsyncNodeStorage} from 'redux-persist-node-storage';
import {createTransform} from 'redux-persist';
import configureStore from '@mm-redux/store';
import configureStore from '@store';
export default async function testConfigureStore(preloadedState) {
const storageTransform = createTransform(
@ -15,12 +15,13 @@ export default async function testConfigureStore(preloadedState) {
key: 'root',
storage: new AsyncNodeStorage('./.tmp'),
whitelist: [],
serialize: true,
deserialize: true,
transforms: [
storageTransform,
],
};
const {store} = configureStore(preloadedState, {}, persistConfig, () => ({}), {enableBuffer: false});
const {store} = configureStore(null, preloadedState, persistConfig, {enableBuffer: false});
const wait = () => new Promise((resolve) => setTimeout(resolve), 300); //eslint-disable-line
await wait();

View file

@ -35,10 +35,11 @@
"@constants/*": ["app/constants/*"],
"@constants": ["app/constants/index"],
"@i18n": ["app/i18n/index"],
"@mm-redux/*": ["app/mm-redux/*"],
"@selectors/*": ["app/selectors/*"],
"@store/*": ["app/store"],
"@telemetry/*": ["/app/telemetry/*"],
"@utils/*": ["app/utils/*"],
"@mm-redux/*": ["app/mm-redux/*"],
"@websocket": ["app/client/websocket"],
"*": ["./*", "node_modules/*"]
}