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
parent 3a887b446a
commit 0e2bc65d70
No known key found for this signature in database
GPG key ID: E038DB71E0B61702
61 changed files with 1053 additions and 1677 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,
app: {
@ -298,19 +298,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 = '';
@ -332,7 +336,7 @@ class GlobalEventHandler {
handleInAppNotification = (notification) => {
const {data} = notification;
const {getState} = this.store;
const {getState} = Store.redux;
const state = getState();
const currentChannelId = getCurrentChannelId(state);
@ -348,7 +352,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,10 +56,9 @@ const launchApp = (credentials) => {
]);
if (credentials) {
waitForHydration(store, () => {
const valid = validatePreviousVersion(store);
if (valid) {
store.dispatch(loadMe());
waitForHydration(Store.redux, async () => {
if (validatePreviousVersion(Store.redux, EphemeralStore.prevAppVersion)) {
Store.redux.dispatch(loadMe());
resetToChannel({skipMetrics: true});
}
});
@ -68,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) {
@ -83,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,172 +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 {ViewTypes} from 'app/constants';
import initialState from 'app/initial_state';
import {throttle} from 'app/utils/general';
import initialState from '@store/initial_state';
import mattermostBucket from 'app/mattermost_bucket';
import EphemeralStore from './ephemeral_store';
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) {
// On first run payload is not set (when installed)
if (!action.payload) {
action.payload = {
app: {
build: DeviceInfo.getBuildNumber(),
version: DeviceInfo.getVersion(),
},
views: {
root: {
hydrationComplete: true,
},
},
};
}
const {app} = action.payload;
const {entities, views} = action.payload;
if (!entities || !views) {
const version = DeviceInfo.getVersion();
EphemeralStore.prevAppVersion = version;
action.payload = {
...action.payload,
app: {
build: DeviceInfo.getBuildNumber(),
version,
},
};
return next(action);
}
EphemeralStore.prevAppVersion = app?.version;
if (app?.version !== DeviceInfo.getVersion() || app?.build !== DeviceInfo.getBuildNumber()) {
// When a new version of the app has been detected
return next(resetStateForNewVersion(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;
@ -258,6 +97,9 @@ function resetStateForNewVersion(payload) {
}
const nextState = {
_persist: {
rehydrated: true,
},
app: {
build: DeviceInfo.getBuildNumber(),
version: DeviceInfo.getVersion(),
@ -295,7 +137,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));
@ -444,9 +286,6 @@ export function cleanUpState(payload, keepCurrent = false) {
...resetPayload.views.channel,
...payload.views.channel,
},
root: {
hydrationComplete: true,
},
},
websocket: {
lastConnectAt: payload.websocket?.lastConnectAt,
@ -536,4 +375,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

@ -46,14 +46,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;
@ -106,6 +113,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

@ -7,7 +7,6 @@
objects = {
/* Begin PBXBuildFile section */
00E356F31AD99517003FC87E /* MattermostTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* MattermostTests.m */; };
0111A42B7F264BCF8CBDE3ED /* OpenSans-ExtraBoldItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = FBBEC29EE2D3418D9AC33BD5 /* OpenSans-ExtraBoldItalic.ttf */; };
0C0D24F53F254F75869E5951 /* OpenSans-Italic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 41F3AFE83AAF4B74878AB78A /* OpenSans-Italic.ttf */; };
13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; };
@ -61,7 +60,6 @@
A08D512E7ADC40CCAD055A9E /* OpenSans-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = BC977883E2624E05975CA65B /* OpenSans-Regular.ttf */; };
AA9605CFDA8E4E7CB8A041BF /* Roboto-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = C5BD64DE829E455A997DCAD5 /* Roboto-Regular.ttf */; };
ABF5F93B1D0A47BAACEAC376 /* Roboto-Light.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 34B20A903038487E8D7DEA1E /* Roboto-Light.ttf */; };
B9899621C7F80C4B5C51BB8B /* libPods-MattermostTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5BB993883BB9CFFBD2BEB9FC /* libPods-MattermostTests.a */; };
C99BB3F4E4564F01A531FBEA /* Roboto-Medium.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 09FA716C56874BDDA30FE8C4 /* Roboto-Medium.ttf */; };
D719A67137964F08BE47A5FC /* OpenSans-ExtraBold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 3647DF63D6764CF093375861 /* OpenSans-ExtraBold.ttf */; };
DDE492F7425D451884DAA088 /* Roboto-Black.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 6EFF13DD24CE4E26953E598A /* Roboto-Black.ttf */; };
@ -72,13 +70,6 @@
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
00E356F41AD99517003FC87E /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 13B07F861A680F5B00A75B9A;
remoteInfo = Mattermost;
};
7F240A21220D3A2300637665 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;
@ -149,7 +140,6 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
00E356EE1AD99517003FC87E /* MattermostTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MattermostTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
00E356F21AD99517003FC87E /* MattermostTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MattermostTests.m; sourceTree = "<group>"; };
031EF04FB2D14EEFAACBAA1A /* Roboto-Italic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Roboto-Italic.ttf"; path = "../assets/fonts/Roboto-Italic.ttf"; sourceTree = "<group>"; };
@ -165,7 +155,6 @@
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = Mattermost/main.m; sourceTree = "<group>"; };
25BF2BACE89201DE6E585B7E /* Pods-Mattermost.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mattermost.release.xcconfig"; path = "Target Support Files/Pods-Mattermost/Pods-Mattermost.release.xcconfig"; sourceTree = "<group>"; };
263D389521BE459684618177 /* Octicons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Octicons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Octicons.ttf"; sourceTree = "<group>"; };
297AAFCCF0BD99FC109DA2BC /* Pods-MattermostTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MattermostTests.release.xcconfig"; path = "Target Support Files/Pods-MattermostTests/Pods-MattermostTests.release.xcconfig"; sourceTree = "<group>"; };
2CD0ACABF8EE4E1A94982CC8 /* Zocial.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Zocial.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Zocial.ttf"; sourceTree = "<group>"; };
32AC3D4EA79E44738A6E9766 /* OpenSans-BoldItalic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "OpenSans-BoldItalic.ttf"; path = "../assets/fonts/OpenSans-BoldItalic.ttf"; sourceTree = "<group>"; };
34B20A903038487E8D7DEA1E /* Roboto-Light.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Roboto-Light.ttf"; path = "../assets/fonts/Roboto-Light.ttf"; sourceTree = "<group>"; };
@ -189,8 +178,6 @@
536CC6C123E79287002C478C /* RNNotificationEventHandler+HandleReplyAction.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "RNNotificationEventHandler+HandleReplyAction.m"; path = "Mattermost/RNNotificationEventHandler+HandleReplyAction.m"; sourceTree = "<group>"; };
536CC6C223E79287002C478C /* RNNotificationEventHandler+HandleReplyAction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "RNNotificationEventHandler+HandleReplyAction.h"; path = "Mattermost/RNNotificationEventHandler+HandleReplyAction.h"; sourceTree = "<group>"; };
563B800AC53A447FA18F47D3 /* FontAwesome5_Solid.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = FontAwesome5_Solid.ttf; path = "../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Solid.ttf"; sourceTree = "<group>"; };
57CB4735B7E57B50D0B50E16 /* Pods-MattermostTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MattermostTests.debug.xcconfig"; path = "Target Support Files/Pods-MattermostTests/Pods-MattermostTests.debug.xcconfig"; sourceTree = "<group>"; };
5BB993883BB9CFFBD2BEB9FC /* libPods-MattermostTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-MattermostTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
6561AEAC21CC40B8A72ABB93 /* OpenSans-Light.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "OpenSans-Light.ttf"; path = "../assets/fonts/OpenSans-Light.ttf"; sourceTree = "<group>"; };
6BAF8296411D4657B5A0E8F8 /* libRNReactNativeDocViewer.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNReactNativeDocViewer.a; sourceTree = "<group>"; };
6EFF13DD24CE4E26953E598A /* Roboto-Black.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Roboto-Black.ttf"; path = "../assets/fonts/Roboto-Black.ttf"; sourceTree = "<group>"; };
@ -272,14 +259,6 @@
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
00E356EB1AD99517003FC87E /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
B9899621C7F80C4B5C51BB8B /* libPods-MattermostTests.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
13B07F8C1A680F5B00A75B9A /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@ -412,8 +391,6 @@
children = (
EB4F0DF36537B0B21BE962FB /* Pods-Mattermost.debug.xcconfig */,
25BF2BACE89201DE6E585B7E /* Pods-Mattermost.release.xcconfig */,
57CB4735B7E57B50D0B50E16 /* Pods-MattermostTests.debug.xcconfig */,
297AAFCCF0BD99FC109DA2BC /* Pods-MattermostTests.release.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
@ -448,7 +425,6 @@
7F43D6051F6BF9EB001FC614 /* libPods-Mattermost.a */,
81061F4CBB31484A94D5A8EE /* libz.tbd */,
8DEEFB3ED6175724A2653247 /* libPods-Mattermost.a */,
5BB993883BB9CFFBD2BEB9FC /* libPods-MattermostTests.a */,
);
name = Frameworks;
sourceTree = "<group>";
@ -533,7 +509,6 @@
isa = PBXGroup;
children = (
13B07F961A680F5B00A75B9A /* Mattermost.app */,
00E356EE1AD99517003FC87E /* MattermostTests.xctest */,
7F240A19220D3A2300637665 /* MattermostShare.appex */,
7F581D32221ED5C60099E66B /* NotificationService.appex */,
);
@ -543,25 +518,6 @@
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
00E356ED1AD99517003FC87E /* MattermostTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "MattermostTests" */;
buildPhases = (
C540C9BFE580D99F0700EC9D /* [CP] Check Pods Manifest.lock */,
00E356EA1AD99517003FC87E /* Sources */,
00E356EB1AD99517003FC87E /* Frameworks */,
00E356EC1AD99517003FC87E /* Resources */,
);
buildRules = (
);
dependencies = (
00E356F51AD99517003FC87E /* PBXTargetDependency */,
);
name = MattermostTests;
productName = MattermostTests;
productReference = 00E356EE1AD99517003FC87E /* MattermostTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
13B07F861A680F5B00A75B9A /* Mattermost */ = {
isa = PBXNativeTarget;
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "Mattermost" */;
@ -634,10 +590,6 @@
LastUpgradeCheck = 820;
ORGANIZATIONNAME = Facebook;
TargetAttributes = {
00E356ED1AD99517003FC87E = {
CreatedOnToolsVersion = 6.2;
TestTargetID = 13B07F861A680F5B00A75B9A;
};
13B07F861A680F5B00A75B9A = {
DevelopmentTeam = UQ8HT4Q2XM;
LastSwiftMigration = 1010;
@ -703,7 +655,6 @@
projectRoot = "";
targets = (
13B07F861A680F5B00A75B9A /* Mattermost */,
00E356ED1AD99517003FC87E /* MattermostTests */,
7F240A18220D3A2300637665 /* MattermostShare */,
7F581D31221ED5C60099E66B /* NotificationService */,
);
@ -721,13 +672,6 @@
/* End PBXReferenceProxy section */
/* Begin PBXResourcesBuildPhase section */
00E356EC1AD99517003FC87E /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
13B07F8E1A680F5B00A75B9A /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@ -820,58 +764,6 @@
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
1D22DC6FC0A37F7F657B8274 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${SRCROOT}/Pods/Target Support Files/Pods-Mattermost/Pods-Mattermost-resources.sh",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/AntDesign.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Entypo.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Feather.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Brands.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Regular.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Solid.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Foundation.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Ionicons.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialIcons.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Octicons.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Zocial.ttf",
"${PODS_ROOT}/../../node_modules/react-native-youtube/assets/YTPlayerView-iframe-player.html",
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
);
outputPaths = (
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AntDesign.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Entypo.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EvilIcons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Feather.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Brands.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Regular.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Solid.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Foundation.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Ionicons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialCommunityIcons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialIcons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Octicons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SimpleLineIcons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Zocial.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/YTPlayerView-iframe-player.html",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Mattermost/Pods-Mattermost-resources.sh\"\n";
showEnvVarsInLog = 0;
};
AE4769B235D14E6C9C64EA78 /* Upload Debug Symbols to Sentry */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@ -886,28 +778,6 @@
shellPath = /bin/sh;
shellScript = "./uploadDebugSymbols.sh\n";
};
C540C9BFE580D99F0700EC9D /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-MattermostTests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
ED4C644925C525E30315E09E /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@ -961,14 +831,6 @@
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
00E356EA1AD99517003FC87E /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
00E356F31AD99517003FC87E /* MattermostTests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
13B07F871A680F5B00A75B9A /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@ -1015,11 +877,6 @@
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
00E356F51AD99517003FC87E /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 13B07F861A680F5B00A75B9A /* Mattermost */;
targetProxy = 00E356F41AD99517003FC87E /* PBXContainerItemProxy */;
};
7F240A22220D3A2300637665 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 7F240A18220D3A2300637665 /* MattermostShare */;
@ -1059,55 +916,6 @@
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
00E356F61AD99517003FC87E /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 57CB4735B7E57B50D0B50E16 /* Pods-MattermostTests.debug.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)";
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
HEADER_SEARCH_PATHS = "$(inherited)";
INFOPLIST_FILE = MattermostTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
"-lc++",
);
PRODUCT_BUNDLE_IDENTIFIER = com.mattermost.rnbeta;
PRODUCT_NAME = "$(TARGET_NAME)";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Mattermost.app/Mattermost";
};
name = Debug;
};
00E356F71AD99517003FC87E /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 297AAFCCF0BD99FC109DA2BC /* Pods-MattermostTests.release.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)";
COPY_PHASE_STRIP = NO;
HEADER_SEARCH_PATHS = "$(inherited)";
INFOPLIST_FILE = MattermostTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
"-lc++",
);
PRODUCT_BUNDLE_IDENTIFIER = com.mattermost.rnbeta;
PRODUCT_NAME = "$(TARGET_NAME)";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Mattermost.app/Mattermost";
};
name = Release;
};
13B07F941A680F5B00A75B9A /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = EB4F0DF36537B0B21BE962FB /* Pods-Mattermost.debug.xcconfig */;
@ -1428,15 +1236,6 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "MattermostTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
00E356F61AD99517003FC87E /* Debug */,
00E356F71AD99517003FC87E /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "Mattermost" */ = {
isa = XCConfigurationList;
buildConfigurations = (

View file

@ -1,24 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.30.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>282</string>
</dict>
</plist>

View file

@ -1,70 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <UIKit/UIKit.h>
#import <XCTest/XCTest.h>
#import <React/RCTLog.h>
#import <React/RCTRootView.h>
#define TIMEOUT_SECONDS 600
#define TEXT_TO_LOOK_FOR @"Welcome to React Native!"
@interface MattermostTests : XCTestCase
@end
@implementation MattermostTests
- (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test
{
if (test(view)) {
return YES;
}
for (UIView *subview in [view subviews]) {
if ([self findSubviewInView:subview matching:test]) {
return YES;
}
}
return NO;
}
- (void)testRendersWelcomeScreen
{
UIViewController *vc = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS];
BOOL foundElement = NO;
__block NSString *redboxError = nil;
RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) {
if (level >= RCTLogLevelError) {
redboxError = message;
}
});
while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) {
[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
[[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) {
if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) {
return YES;
}
return NO;
}];
}
RCTSetLogFunction(RCTDefaultLogFunction);
XCTAssertNil(redboxError, @"RedBox error: %@", redboxError);
XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS);
}
@end

View file

@ -42,10 +42,5 @@ target 'Mattermost' do
pod 'XCDYouTubeKit', '2.8.2'
pod 'Swime', '3.0.6'
target 'MattermostTests' do
inherit! :search_paths
# Pods for testing
end
use_native_modules!
end

View file

@ -32,7 +32,9 @@ PODS:
- libwebp/mux (1.1.0):
- libwebp/demux
- libwebp/webp (1.1.0)
- MMKV (1.0.24)
- MMKV (1.1.0):
- MMKVCore (~> 1.1.0)
- MMKVCore (1.1.0)
- Permission-Camera (2.0.10):
- RNPermissions
- Permission-PhotoLibrary (2.0.10):
@ -213,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
@ -384,6 +386,7 @@ SPEC REPOS:
- boost-for-react-native
- libwebp
- MMKV
- MMKVCore
- SDWebImage
- SDWebImageWebPCoder
- Sentry
@ -517,7 +520,8 @@ SPEC CHECKSUMS:
glog: 1f3da668190260b06b429bb211bfbee5cd790c28
jail-monkey: d7c5048b2336f22ee9c9e0efa145f1f917338ea9
libwebp: 946cb3063cea9236285f7e9a8505d806d30e07f3
MMKV: 758b2edee46b08bdd958db4169191afb9a6d4ebd
MMKV: 7bb6c30f9ff2ea45bc2398c86e66dd1cb63cfe20
MMKVCore: f1e0aad4fc330e7cbfbdff16720017bf0973db5a
Permission-Camera: 8f0e5decca5f28f70f28a8dc31f012c9bad40ad8
Permission-PhotoLibrary: b209bf23b784c9e1409a57d81c6d11ab1d3079c1
RCTRequired: b153add4da6e7dbc44aebf93f3cf4fcae392ddf1
@ -535,7 +539,7 @@ SPEC CHECKSUMS:
react-native-document-picker: 0573c02d742d4bef38a5d16b5f039754cfa69888
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: d5cb54ef8bf3004dcb56c887650dea08ecbddee7
react-native-passcode-status: 88c4f6e074328bc278bd127646b6c694ad5a530a
@ -575,6 +579,6 @@ SPEC CHECKSUMS:
Yoga: f2a7cd4280bfe2cca5a7aed98ba0eb3d1310f18b
YoutubePlayer-in-WKWebView: af2f5929fc78882d94bfdfeea999b661b78d9717
PODFILE CHECKSUM: 8199a7b8e5d4cc8c741ea6292c067c33ad835da9
PODFILE CHECKSUM: 77806e4d5b8185ba5cb04fc8d698bcc9123cb578
COCOAPODS: 1.7.5

19
package-lock.json generated
View file

@ -4855,6 +4855,11 @@
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.8.24.tgz",
"integrity": "sha512-bImQZbBv86zcOWOq6fLg7r4aqMx8fScdmykA7cSh+gH1Yh8AM0Dbw0gHYrsOrza6oBBnkK+/OaR+UAa9UsMrDw=="
},
"debounce": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.0.tgz",
"integrity": "sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg=="
},
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
@ -15241,9 +15246,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.3.0",
@ -15330,6 +15335,14 @@
"hoist-non-react-statics": "^2.3.1"
}
},
"react-native-screens": {
"version": "1.0.0-alpha.23",
"resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-1.0.0-alpha.23.tgz",
"integrity": "sha512-tOxHGQUN83MTmQB4ghoQkibqOdGiX4JQEmeyEv96MKWO/x8T2PJv84ECUos9hD3blPRQwVwSpAid1PPPhrVEaw==",
"requires": {
"debounce": "^1.2.0"
}
},
"react-native-section-list-get-item-layout": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/react-native-section-list-get-item-layout/-/react-native-section-list-get-item-layout-2.2.3.tgz",

View file

@ -50,12 +50,13 @@
"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.3.0",
"react-native-notifications": "2.0.6",
"react-native-passcode-status": "1.1.2",
"react-native-permissions": "2.0.10",
"react-native-safe-area": "0.5.1",
"react-native-screens": "1.0.0-alpha.23",
"react-native-section-list-get-item-layout": "2.2.3",
"react-native-slider": "0.11.0",
"react-native-status-bar-size": "0.3.3",
@ -143,7 +144,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 --skipLibCheck",
"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',
@ -27,62 +27,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',
@ -95,19 +59,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',
@ -151,10 +115,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',
@ -163,19 +123,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',
@ -184,13 +179,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',
@ -223,6 +216,7 @@ module.exports = [
'node_modules/@babel/runtime/helpers/toConsumableArray.js',
'node_modules/@babel/runtime/helpers/typeof.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',
@ -258,10 +252,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',
@ -285,16 +277,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',
@ -317,8 +302,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',
@ -332,9 +315,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',
@ -342,13 +323,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',
@ -368,7 +347,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',
@ -454,14 +432,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/forEach.js',
@ -490,7 +466,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',
@ -502,7 +477,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',
@ -512,7 +486,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',
@ -524,9 +497,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',
@ -536,10 +506,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',
@ -549,19 +515,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',
@ -829,25 +800,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',
@ -858,13 +829,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',
@ -914,7 +890,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',
@ -26,62 +27,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',
@ -94,19 +59,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',
@ -150,10 +115,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',
@ -162,19 +123,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',
@ -183,13 +179,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',
@ -220,19 +214,11 @@ module.exports = [
'./node_modules/node_modules/@babel/runtime/helpers/toConsumableArray.js',
'./node_modules/node_modules/@babel/runtime/helpers/typeof.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',
@ -255,10 +241,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',
@ -282,16 +266,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',
@ -314,8 +291,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',
@ -329,9 +304,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',
@ -339,13 +312,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',
@ -365,7 +336,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',
@ -451,14 +421,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/forEach.js',
@ -487,7 +455,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',
@ -499,7 +466,6 @@ 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',
@ -508,7 +474,6 @@ module.exports = [
'./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',
@ -520,22 +485,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',
@ -545,19 +503,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',
@ -743,6 +706,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',
@ -830,18 +794,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',
@ -852,11 +816,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',
@ -908,7 +877,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

@ -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

@ -34,10 +34,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/*"]
}