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:
parent
b025ed0f01
commit
fda4948c8c
60 changed files with 1057 additions and 1527 deletions
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -29,11 +29,12 @@ import {NavigationTypes, ViewTypes} from '@constants';
|
|||
import {getTranslations, resetMomentLocale} from '@i18n';
|
||||
import PushNotifications from 'app/push_notifications';
|
||||
import {getCurrentLocale} from '@selectors/i18n';
|
||||
import initialState from '@store/initial_state';
|
||||
import Store from '@store/store';
|
||||
import {t} from '@utils/i18n';
|
||||
import {deleteFileCache} from '@utils/file';
|
||||
import {getDeviceTimezoneAsync} from '@utils/timezone';
|
||||
|
||||
import initialState from 'app/initial_state';
|
||||
import mattermostBucket from 'app/mattermost_bucket';
|
||||
import mattermostManaged from 'app/mattermost_managed';
|
||||
import LocalConfig from 'assets/config';
|
||||
|
|
@ -66,7 +67,7 @@ class GlobalEventHandler {
|
|||
|
||||
// Once the app becomes active we check if the device needs to have a passcode set
|
||||
const prompt = emmProvider.inBackgroundSince && authExpired; // if more than 5 minutes have passed prompt for passcode
|
||||
await emmProvider.handleAuthentication(this.store, prompt);
|
||||
await emmProvider.handleAuthentication(prompt);
|
||||
}
|
||||
|
||||
emmProvider.inBackgroundSince = null; /* eslint-disable-line require-atomic-updates */
|
||||
|
|
@ -75,7 +76,7 @@ class GlobalEventHandler {
|
|||
appInactive = () => {
|
||||
this.turnOffInAppNotificationHandling();
|
||||
|
||||
const {dispatch} = this.store;
|
||||
const {dispatch} = Store.redux;
|
||||
|
||||
// When the app is sent to the background we set the time when that happens
|
||||
// and perform a data clean up to improve on performance
|
||||
|
|
@ -85,7 +86,6 @@ class GlobalEventHandler {
|
|||
};
|
||||
|
||||
configure = (opts) => {
|
||||
this.store = opts.store;
|
||||
this.launchApp = opts.launchApp;
|
||||
|
||||
// onAppStateChange may be called by the AppState listener before we
|
||||
|
|
@ -107,7 +107,7 @@ class GlobalEventHandler {
|
|||
}
|
||||
|
||||
this.JavascriptAndNativeErrorHandler = require('app/utils/error_handling').default;
|
||||
this.JavascriptAndNativeErrorHandler.initializeErrorHandling(this.store);
|
||||
this.JavascriptAndNativeErrorHandler.initializeErrorHandling(Store.redux);
|
||||
|
||||
mattermostManaged.addEventListener('managedConfigDidChange', this.onManagedConfigurationChange);
|
||||
};
|
||||
|
|
@ -126,8 +126,8 @@ class GlobalEventHandler {
|
|||
const isActive = appState === 'active';
|
||||
const isBackground = appState === 'background';
|
||||
|
||||
if (this.store) {
|
||||
this.store.dispatch(setAppState(isActive));
|
||||
if (Store.redux) {
|
||||
Store.redux.dispatch(setAppState(isActive));
|
||||
|
||||
if (isActive && (!emmProvider.enabled || emmProvider.previousAppState === 'background')) {
|
||||
this.appActive();
|
||||
|
|
@ -142,12 +142,12 @@ class GlobalEventHandler {
|
|||
onDeepLink = (event) => {
|
||||
const {url} = event;
|
||||
if (url) {
|
||||
this.store.dispatch(setDeepLinkURL(url));
|
||||
Store.redux.dispatch(setDeepLinkURL(url));
|
||||
}
|
||||
};
|
||||
|
||||
onManagedConfigurationChange = () => {
|
||||
emmProvider.handleManagedConfig(this.store, true);
|
||||
emmProvider.handleManagedConfig(true);
|
||||
};
|
||||
|
||||
onServerConfigChanged = (config) => {
|
||||
|
|
@ -155,9 +155,9 @@ class GlobalEventHandler {
|
|||
};
|
||||
|
||||
onLogout = async () => {
|
||||
this.store.dispatch(closeWebSocket(false));
|
||||
this.store.dispatch(setServerVersion(''));
|
||||
this.resetState();
|
||||
Store.redux.dispatch(closeWebSocket(false));
|
||||
Store.redux.dispatch(setServerVersion(''));
|
||||
await this.resetState();
|
||||
removeAppCredentials();
|
||||
deleteFileCache();
|
||||
resetMomentLocale();
|
||||
|
|
@ -190,8 +190,8 @@ class GlobalEventHandler {
|
|||
};
|
||||
|
||||
onOrientationChange = (dimensions) => {
|
||||
if (this.store) {
|
||||
const {dispatch, getState} = this.store;
|
||||
if (Store.redux) {
|
||||
const {dispatch, getState} = Store.redux;
|
||||
const deviceState = getState().device;
|
||||
|
||||
if (DeviceInfo.isTablet()) {
|
||||
|
|
@ -215,7 +215,7 @@ class GlobalEventHandler {
|
|||
};
|
||||
|
||||
onRestartApp = async () => {
|
||||
const {dispatch, getState} = this.store;
|
||||
const {dispatch, getState} = Store.redux;
|
||||
const state = getState();
|
||||
const {currentUserId} = state.entities.users;
|
||||
const user = getUser(state, currentUserId);
|
||||
|
|
@ -236,7 +236,7 @@ class GlobalEventHandler {
|
|||
};
|
||||
|
||||
onServerVersionChanged = async (serverVersion) => {
|
||||
const {dispatch, getState} = this.store;
|
||||
const {dispatch, getState} = Store.redux;
|
||||
const state = getState();
|
||||
const match = serverVersion && serverVersion.match(/^[0-9]*.[0-9]*.[0-9]*(-[a-zA-Z0-9.-]*)?/g);
|
||||
const version = match && match[0];
|
||||
|
|
@ -263,17 +263,17 @@ class GlobalEventHandler {
|
|||
};
|
||||
|
||||
onStatusBarHeightChange = (nextStatusBarHeight) => {
|
||||
this.store.dispatch(setStatusBarHeight(nextStatusBarHeight));
|
||||
Store.redux.dispatch(setStatusBarHeight(nextStatusBarHeight));
|
||||
};
|
||||
|
||||
onSwitchToDefaultChannel = (teamId) => {
|
||||
this.store.dispatch(selectDefaultChannel(teamId));
|
||||
Store.redux.dispatch(selectDefaultChannel(teamId));
|
||||
};
|
||||
|
||||
resetState = async () => {
|
||||
try {
|
||||
await AsyncStorage.clear();
|
||||
const state = this.store.getState();
|
||||
const state = Store.redux.getState();
|
||||
const newState = {
|
||||
...initialState,
|
||||
entities: {
|
||||
|
|
@ -294,19 +294,23 @@ class GlobalEventHandler {
|
|||
serverUrl: state.views.selectServer.serverUrl,
|
||||
},
|
||||
},
|
||||
_persist: {
|
||||
rehydrated: true,
|
||||
},
|
||||
};
|
||||
|
||||
this.store.dispatch({
|
||||
return Store.redux.dispatch({
|
||||
type: General.OFFLINE_STORE_PURGE,
|
||||
state: newState,
|
||||
});
|
||||
} catch (e) {
|
||||
// clear error
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
serverUpgradeNeeded = async () => {
|
||||
const {dispatch} = this.store;
|
||||
const {dispatch} = Store.redux;
|
||||
|
||||
dispatch(setServerVersion(''));
|
||||
Client4.serverVersion = '';
|
||||
|
|
@ -328,7 +332,7 @@ class GlobalEventHandler {
|
|||
|
||||
handleInAppNotification = (notification) => {
|
||||
const {data} = notification;
|
||||
const {getState} = this.store;
|
||||
const {getState} = Store.redux;
|
||||
const state = getState();
|
||||
const currentChannelId = getCurrentChannelId(state);
|
||||
|
||||
|
|
@ -344,7 +348,7 @@ class GlobalEventHandler {
|
|||
};
|
||||
|
||||
setUserTimezone = async () => {
|
||||
const {dispatch, getState} = this.store;
|
||||
const {dispatch, getState} = Store.redux;
|
||||
const state = getState();
|
||||
const currentUserId = getCurrentUserId(state);
|
||||
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
||||
|
|
|
|||
|
|
@ -7,34 +7,38 @@ import {Provider} from 'react-redux';
|
|||
|
||||
import EventEmitter from '@mm-redux/utils/event_emitter';
|
||||
|
||||
import {loadMe} from 'app/actions/views/user';
|
||||
|
||||
import {resetToChannel, resetToSelectServer} from 'app/actions/navigation';
|
||||
import {setDeepLinkURL} from 'app/actions/views/root';
|
||||
import {NavigationTypes} from 'app/constants';
|
||||
import {getAppCredentials} from 'app/init/credentials';
|
||||
import emmProvider from 'app/init/emm_provider';
|
||||
import 'app/init/device';
|
||||
import 'app/init/fetch';
|
||||
import globalEventHandler from 'app/init/global_event_handler';
|
||||
import {registerScreens} from 'app/screens';
|
||||
import store, {persistor} from 'app/store';
|
||||
import {waitForHydration} from 'app/store/utils';
|
||||
import EphemeralStore from 'app/store/ephemeral_store';
|
||||
import {resetToChannel, resetToSelectServer} from '@actions/navigation';
|
||||
import {setDeepLinkURL} from '@actions/views/root';
|
||||
import {loadMe, logout} from '@actions/views/user';
|
||||
import telemetry from 'app/telemetry';
|
||||
import {validatePreviousVersion} from 'app/utils/general';
|
||||
import pushNotificationsUtils from 'app/utils/push_notifications';
|
||||
import {NavigationTypes} from '@constants';
|
||||
import {getAppCredentials} from '@init/credentials';
|
||||
import emmProvider from '@init/emm_provider';
|
||||
import '@init/device';
|
||||
import '@init/fetch';
|
||||
import globalEventHandler from '@init/global_event_handler';
|
||||
import {registerScreens} from '@screens';
|
||||
import configureStore from '@store';
|
||||
import EphemeralStore from '@store/ephemeral_store';
|
||||
import getStorage from '@store/mmkv_adapter';
|
||||
import Store from '@store/store';
|
||||
import {waitForHydration} from '@store/utils';
|
||||
import {validatePreviousVersion} from '@utils/general';
|
||||
import pushNotificationsUtils from '@utils/push_notifications';
|
||||
|
||||
const init = async () => {
|
||||
const credentials = await getAppCredentials();
|
||||
const dt = Date.now();
|
||||
const MMKVStorage = await getStorage();
|
||||
|
||||
const {store} = configureStore(MMKVStorage);
|
||||
if (EphemeralStore.appStarted) {
|
||||
launchApp(credentials);
|
||||
return;
|
||||
}
|
||||
|
||||
pushNotificationsUtils.configure(store);
|
||||
pushNotificationsUtils.configure();
|
||||
globalEventHandler.configure({
|
||||
store,
|
||||
launchApp,
|
||||
});
|
||||
|
||||
|
|
@ -52,9 +56,9 @@ const launchApp = (credentials) => {
|
|||
]);
|
||||
|
||||
if (credentials) {
|
||||
waitForHydration(store, async () => {
|
||||
if (validatePreviousVersion(store, EphemeralStore.prevAppVersion)) {
|
||||
store.dispatch(loadMe());
|
||||
waitForHydration(Store.redux, async () => {
|
||||
if (validatePreviousVersion(Store.redux, EphemeralStore.prevAppVersion)) {
|
||||
Store.redux.dispatch(loadMe());
|
||||
resetToChannel({skipMetrics: true});
|
||||
}
|
||||
});
|
||||
|
|
@ -67,13 +71,13 @@ const launchApp = (credentials) => {
|
|||
|
||||
Linking.getInitialURL().then((url) => {
|
||||
if (url) {
|
||||
store.dispatch(setDeepLinkURL(url));
|
||||
Store.redux.dispatch(setDeepLinkURL(url));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const launchAppAndAuthenticateIfNeeded = async (credentials) => {
|
||||
await emmProvider.handleManagedConfig(store);
|
||||
await emmProvider.handleManagedConfig();
|
||||
await launchApp(credentials);
|
||||
|
||||
if (emmProvider.enabled) {
|
||||
|
|
@ -82,7 +86,7 @@ const launchAppAndAuthenticateIfNeeded = async (credentials) => {
|
|||
}
|
||||
|
||||
if (emmProvider.inAppPinCode) {
|
||||
await emmProvider.handleAuthentication(store);
|
||||
await emmProvider.handleAuthentication();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -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;
|
||||
5
app/mm-redux/types/module.d.ts
vendored
5
app/mm-redux/types/module.d.ts
vendored
|
|
@ -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';
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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> = [];
|
||||
|
|
@ -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
242
app/store/index.ts
Normal 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};
|
||||
}
|
||||
|
|
@ -1,173 +1,11 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Platform} from 'react-native';
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
import {REHYDRATE} from 'redux-persist';
|
||||
import semver from 'semver/preload';
|
||||
|
||||
import {ViewTypes} from 'app/constants';
|
||||
import initialState from 'app/initial_state';
|
||||
import EphemeralStore from 'app/store/ephemeral_store';
|
||||
import {throttle} from 'app/utils/general';
|
||||
import initialState from '@store/initial_state';
|
||||
|
||||
import mattermostBucket from 'app/mattermost_bucket';
|
||||
|
||||
import {
|
||||
captureException,
|
||||
LOGGER_JAVASCRIPT_WARNING,
|
||||
} from 'app/utils/sentry';
|
||||
|
||||
const SAVE_STATE_ACTIONS = [
|
||||
'CONNECTION_CHANGED',
|
||||
'DATA_CLEANUP',
|
||||
'LOGIN',
|
||||
'Offline/STATUS_CHANGED',
|
||||
'persist/REHYDRATE',
|
||||
'RECEIVED_APP_STATE',
|
||||
'WEBSOCKET_CLOSED',
|
||||
'WEBSOCKET_SUCCESS',
|
||||
];
|
||||
|
||||
export const middlewares = () => {
|
||||
const middlewareFunctions = [
|
||||
messageRetention,
|
||||
];
|
||||
|
||||
if (Platform.OS === 'ios') {
|
||||
middlewareFunctions.push(saveShareExtensionState);
|
||||
}
|
||||
|
||||
return middlewareFunctions;
|
||||
};
|
||||
|
||||
// This middleware stores key parts of state entities into a file (in the App Group container) on certain actions.
|
||||
// iOS only. Allows the share extension to work, without having access available to the redux store object.
|
||||
// Remove this middleware if/when state is moved to a persisted solution.
|
||||
function saveShareExtensionState(store) {
|
||||
return (next) => (action) => {
|
||||
if (SAVE_STATE_ACTIONS.includes(action.type)) {
|
||||
throttle(saveStateToFile(store));
|
||||
}
|
||||
return next(action);
|
||||
};
|
||||
}
|
||||
|
||||
async function saveStateToFile(store) {
|
||||
const state = store.getState();
|
||||
|
||||
if (state.entities) {
|
||||
const channelsInTeam = {...state.entities.channels.channelsInTeam};
|
||||
Object.keys(channelsInTeam).forEach((teamId) => {
|
||||
channelsInTeam[teamId] = Array.from(channelsInTeam[teamId]);
|
||||
});
|
||||
|
||||
const profilesInChannel = {...state.entities.users.profilesInChannel};
|
||||
Object.keys(profilesInChannel).forEach((channelId) => {
|
||||
profilesInChannel[channelId] = Array.from(profilesInChannel[channelId]);
|
||||
});
|
||||
|
||||
let url;
|
||||
if (state.entities.users.currentUserId) {
|
||||
url = state.entities.general.credentials.url || state.views.selectServer.serverUrl;
|
||||
}
|
||||
|
||||
const entities = {
|
||||
...state.entities,
|
||||
general: {
|
||||
...state.entities.general,
|
||||
credentials: {
|
||||
url,
|
||||
},
|
||||
},
|
||||
channels: {
|
||||
...state.entities.channels,
|
||||
channelsInTeam,
|
||||
},
|
||||
users: {
|
||||
...state.entities.users,
|
||||
profilesInChannel,
|
||||
profilesNotInTeam: [],
|
||||
profilesWithoutTeam: [],
|
||||
profilesNotInChannel: [],
|
||||
},
|
||||
};
|
||||
|
||||
mattermostBucket.writeToFile('entities', JSON.stringify(entities));
|
||||
}
|
||||
}
|
||||
|
||||
function messageRetention(store) {
|
||||
return (next) => (action) => {
|
||||
if (action.type === REHYDRATE) {
|
||||
if (!action.payload) {
|
||||
// On first run payload is not set (when installed)
|
||||
const version = DeviceInfo.getVersion();
|
||||
const major = semver.major(version);
|
||||
const minor = semver.minor(version);
|
||||
const patch = semver.patch(version);
|
||||
const prevAppVersion = `${major}.${parseInt(minor, 10) - 1}.${patch}`;
|
||||
EphemeralStore.prevAppVersion = prevAppVersion;
|
||||
action.payload = {
|
||||
app: {
|
||||
build: DeviceInfo.getBuildNumber(),
|
||||
version,
|
||||
},
|
||||
views: {
|
||||
root: {
|
||||
hydrationComplete: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const {app} = action.payload;
|
||||
const {entities, views} = action.payload;
|
||||
|
||||
if (!EphemeralStore.prevAppVersion) {
|
||||
EphemeralStore.prevAppVersion = app?.version;
|
||||
}
|
||||
|
||||
if (!entities || !views) {
|
||||
return next(action);
|
||||
}
|
||||
|
||||
// When a new version of the app has been detected
|
||||
if (!app || !app.version || app.version !== DeviceInfo.getVersion() || app.build !== DeviceInfo.getBuildNumber()) {
|
||||
action.payload = resetStateForNewVersion(action.payload);
|
||||
return next(action);
|
||||
}
|
||||
|
||||
// Keep only the last 60 messages for the last 5 viewed channels in each team
|
||||
// and apply data retention on those posts if applies
|
||||
try {
|
||||
action.payload = cleanUpState(action.payload);
|
||||
} catch (e) {
|
||||
// Sometimes, the payload is incomplete so log the error to Sentry and skip the cleanup
|
||||
console.warn(e); // eslint-disable-line no-console
|
||||
captureException(e, LOGGER_JAVASCRIPT_WARNING, store);
|
||||
}
|
||||
|
||||
return next(action);
|
||||
} else if (action.type === ViewTypes.DATA_CLEANUP) {
|
||||
action.payload = cleanUpState(action.payload, true);
|
||||
return next(action);
|
||||
}
|
||||
|
||||
/* Uncomment the following lines to log the actions being dispatched */
|
||||
// if (action.type === 'BATCHING_REDUCER.BATCH') {
|
||||
// action.payload.forEach((p) => {
|
||||
// console.log('BATCHED ACTIONS', p.type);
|
||||
// });
|
||||
// } else {
|
||||
// console.log('ACTION', action.type);
|
||||
// }
|
||||
|
||||
return next(action);
|
||||
};
|
||||
}
|
||||
|
||||
function resetStateForNewVersion(payload) {
|
||||
export function resetStateForNewVersion(payload) {
|
||||
const lastChannelForTeam = getLastChannelForTeam(payload);
|
||||
|
||||
let general = initialState.entities.general;
|
||||
|
|
@ -259,6 +97,9 @@ function resetStateForNewVersion(payload) {
|
|||
}
|
||||
|
||||
const nextState = {
|
||||
_persist: {
|
||||
rehydrated: true,
|
||||
},
|
||||
app: {
|
||||
build: DeviceInfo.getBuildNumber(),
|
||||
version: DeviceInfo.getVersion(),
|
||||
|
|
@ -299,7 +140,7 @@ function resetStateForNewVersion(payload) {
|
|||
return nextState;
|
||||
}
|
||||
|
||||
function getLastChannelForTeam(payload) {
|
||||
export function getLastChannelForTeam(payload) {
|
||||
if (payload?.views?.team?.lastChannelForTeam) {
|
||||
const lastChannelForTeam = {...payload.views.team.lastChannelForTeam};
|
||||
const convertLastChannelForTeam = Object.values(lastChannelForTeam).some((value) => !Array.isArray(value));
|
||||
|
|
@ -448,9 +289,6 @@ export function cleanUpState(payload, keepCurrent = false) {
|
|||
...resetPayload.views.channel,
|
||||
...payload.views.channel,
|
||||
},
|
||||
root: {
|
||||
hydrationComplete: true,
|
||||
},
|
||||
},
|
||||
websocket: {
|
||||
lastConnectAt: payload.websocket?.lastConnectAt,
|
||||
|
|
@ -540,4 +378,4 @@ function removePendingPost(pendingPostIds, id) {
|
|||
if (pendingIndex !== -1) {
|
||||
pendingPostIds.splice(pendingIndex, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
35
app/store/middlewares/index.ts
Normal file
35
app/store/middlewares/index.ts
Normal 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;
|
||||
}
|
||||
72
app/store/middlewares/ios_extension.js
Normal file
72
app/store/middlewares/ios_extension.js
Normal 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));
|
||||
}
|
||||
}
|
||||
84
app/store/middlewares/message_retention.js
Normal file
84
app/store/middlewares/message_retention.js
Normal 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);
|
||||
};
|
||||
}
|
||||
|
|
@ -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 = [
|
||||
{
|
||||
|
|
@ -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');
|
||||
}
|
||||
39
app/store/middlewares/thunk.js
Normal file
39
app/store/middlewares/thunk.js
Normal 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
98
app/store/mmkv_adapter.ts
Normal 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);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -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
17
app/store/store.ts
Normal 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();
|
||||
|
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
|
@ -45,14 +45,21 @@ export function transformSet(incoming, setTransforms, toStorage = true) {
|
|||
|
||||
export function waitForHydration(store, callback) {
|
||||
let executed = false; // this is to prevent a race condition when subcription runs before unsubscribed
|
||||
if (store.getState().views?.root?.hydrationComplete && !executed) {
|
||||
let state = store.getState();
|
||||
let root = state.views?.root;
|
||||
let persist = state._persist; //eslint-disable-line no-underscore-dangle
|
||||
|
||||
if (root?.hydrationComplete && !executed) {
|
||||
if (callback && typeof callback === 'function') {
|
||||
executed = true;
|
||||
callback();
|
||||
}
|
||||
} else {
|
||||
const subscription = () => {
|
||||
if (store.getState().views?.root?.hydrationComplete && !executed) {
|
||||
state = store.getState();
|
||||
root = state.views?.root;
|
||||
persist = state._persist; //eslint-disable-line no-underscore-dangle
|
||||
if (root?.hydrationComplete && !executed) {
|
||||
unsubscribeFromStore();
|
||||
if (callback && typeof callback === 'function') {
|
||||
executed = true;
|
||||
|
|
@ -94,6 +101,9 @@ export function getStateForReset(initialState, currentState) {
|
|||
hydrationComplete: true,
|
||||
},
|
||||
},
|
||||
_persist: {
|
||||
rehydrated: true,
|
||||
},
|
||||
});
|
||||
|
||||
return resetState;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
},
|
||||
}],
|
||||
],
|
||||
|
|
|
|||
|
|
@ -11,24 +11,6 @@
|
|||
#import <os/log.h>
|
||||
#import <RNHWKeyboardEvent.h>
|
||||
|
||||
#if DEBUG
|
||||
#import <FlipperKit/FlipperClient.h>
|
||||
#import <FlipperKitLayoutPlugin/FlipperKitLayoutPlugin.h>
|
||||
#import <FlipperKitUserDefaultsPlugin/FKUserDefaultsPlugin.h>
|
||||
#import <FlipperKitNetworkPlugin/FlipperKitNetworkPlugin.h>
|
||||
#import <SKIOSNetworkPlugin/SKIOSNetworkAdapter.h>
|
||||
#import <FlipperKitReactPlugin/FlipperKitReactPlugin.h>
|
||||
static void InitializeFlipper(UIApplication *application) {
|
||||
FlipperClient *client = [FlipperClient sharedClient];
|
||||
SKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults];
|
||||
[client addPlugin:[[FlipperKitLayoutPlugin alloc] initWithRootNode:application withDescriptorMapper:layoutDescriptorMapper]];
|
||||
[client addPlugin:[[FKUserDefaultsPlugin alloc] initWithSuiteName:nil]];
|
||||
[client addPlugin:[FlipperKitReactPlugin new]];
|
||||
[client addPlugin:[[FlipperKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]];
|
||||
[client start];
|
||||
}
|
||||
#endif
|
||||
|
||||
@implementation AppDelegate
|
||||
|
||||
NSString* const NOTIFICATION_MESSAGE_ACTION = @"message";
|
||||
|
|
@ -43,10 +25,6 @@ NSString* const NOTIFICATION_UPDATE_BADGE_ACTION = @"update_badge";
|
|||
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
||||
{
|
||||
#if DEBUG
|
||||
InitializeFlipper(application);
|
||||
#endif
|
||||
|
||||
// Clear keychain on first run in case of reinstallation
|
||||
if (![[NSUserDefaults standardUserDefaults] objectForKey:@"FirstRun"]) {
|
||||
|
||||
|
|
|
|||
43
ios/Podfile
43
ios/Podfile
|
|
@ -1,40 +1,6 @@
|
|||
platform :ios, '9.0'
|
||||
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
|
||||
|
||||
def add_flipper_pods!
|
||||
version = '~> 0.33.1'
|
||||
pod 'FlipperKit', version, :configuration => 'Debug'
|
||||
pod 'FlipperKit/FlipperKitLayoutPlugin', version, :configuration => 'Debug'
|
||||
pod 'FlipperKit/SKIOSNetworkPlugin', version, :configuration => 'Debug'
|
||||
pod 'FlipperKit/FlipperKitUserDefaultsPlugin', version, :configuration => 'Debug'
|
||||
pod 'FlipperKit/FlipperKitReactPlugin', version, :configuration => 'Debug'
|
||||
end
|
||||
|
||||
# Post Install processing for Flipper
|
||||
def flipper_post_install(installer)
|
||||
installer.pods_project.targets.each do |target|
|
||||
if target.name == 'YogaKit'
|
||||
target.build_configurations.each do |config|
|
||||
config.build_settings['SWIFT_VERSION'] = '4.1'
|
||||
end
|
||||
end
|
||||
end
|
||||
file_name = Dir.glob("*.xcodeproj")[0]
|
||||
app_project = Xcodeproj::Project.open(file_name)
|
||||
app_project.native_targets.each do |target|
|
||||
target.build_configurations.each do |config|
|
||||
cflags = config.build_settings['OTHER_CFLAGS'] || '$(inherited) '
|
||||
unless cflags.include? '-DFB_SONARKIT_ENABLED=1'
|
||||
puts 'Adding -DFB_SONARKIT_ENABLED=1 in OTHER_CFLAGS...'
|
||||
cflags << '-DFB_SONARKIT_ENABLED=1'
|
||||
end
|
||||
config.build_settings['OTHER_CFLAGS'] = cflags
|
||||
end
|
||||
app_project.save
|
||||
end
|
||||
installer.pods_project.save
|
||||
end
|
||||
|
||||
target 'Mattermost' do
|
||||
# Pods for Mattermost
|
||||
pod 'FBLazyVector', :path => "../node_modules/react-native/Libraries/FBLazyVector"
|
||||
|
|
@ -76,13 +42,4 @@ target 'Mattermost' do
|
|||
pod 'Swime', '3.0.6'
|
||||
|
||||
use_native_modules!
|
||||
|
||||
# Enables Flipper.
|
||||
#
|
||||
# Note that if you have use_frameworks! enabled, Flipper will not work and
|
||||
# you should disable these next few lines.
|
||||
add_flipper_pods!
|
||||
post_install do |installer|
|
||||
flipper_post_install(installer)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@ PODS:
|
|||
- boost-for-react-native (1.63.0)
|
||||
- BVLinearGradient (2.5.6):
|
||||
- React
|
||||
- CocoaAsyncSocket (7.6.4)
|
||||
- CocoaLibEvent (1.0.0)
|
||||
- DoubleConversion (1.1.6)
|
||||
- FBLazyVector (0.62.2)
|
||||
- FBReactNativeSpec (0.62.2):
|
||||
|
|
@ -13,52 +11,6 @@ PODS:
|
|||
- React-Core (= 0.62.2)
|
||||
- React-jsi (= 0.62.2)
|
||||
- ReactCommon/turbomodule/core (= 0.62.2)
|
||||
- Flipper (0.33.1):
|
||||
- Flipper-Folly (~> 2.1)
|
||||
- Flipper-RSocket (~> 1.0)
|
||||
- Flipper-DoubleConversion (1.1.7)
|
||||
- Flipper-Folly (2.1.1):
|
||||
- boost-for-react-native
|
||||
- CocoaLibEvent (~> 1.0)
|
||||
- Flipper-DoubleConversion
|
||||
- Flipper-Glog
|
||||
- OpenSSL-Universal (= 1.0.2.19)
|
||||
- Flipper-Glog (0.3.6)
|
||||
- Flipper-PeerTalk (0.0.4)
|
||||
- Flipper-RSocket (1.0.0):
|
||||
- Flipper-Folly (~> 2.0)
|
||||
- FlipperKit (0.33.1):
|
||||
- FlipperKit/Core (= 0.33.1)
|
||||
- FlipperKit/Core (0.33.1):
|
||||
- Flipper (~> 0.33.1)
|
||||
- FlipperKit/CppBridge
|
||||
- FlipperKit/FBCxxFollyDynamicConvert
|
||||
- FlipperKit/FBDefines
|
||||
- FlipperKit/FKPortForwarding
|
||||
- FlipperKit/CppBridge (0.33.1):
|
||||
- Flipper (~> 0.33.1)
|
||||
- FlipperKit/FBCxxFollyDynamicConvert (0.33.1):
|
||||
- Flipper-Folly (~> 2.1)
|
||||
- FlipperKit/FBDefines (0.33.1)
|
||||
- FlipperKit/FKPortForwarding (0.33.1):
|
||||
- CocoaAsyncSocket (~> 7.6)
|
||||
- Flipper-PeerTalk (~> 0.0.4)
|
||||
- FlipperKit/FlipperKitHighlightOverlay (0.33.1)
|
||||
- FlipperKit/FlipperKitLayoutPlugin (0.33.1):
|
||||
- FlipperKit/Core
|
||||
- FlipperKit/FlipperKitHighlightOverlay
|
||||
- FlipperKit/FlipperKitLayoutTextSearchable
|
||||
- YogaKit (~> 1.18)
|
||||
- FlipperKit/FlipperKitLayoutTextSearchable (0.33.1)
|
||||
- FlipperKit/FlipperKitNetworkPlugin (0.33.1):
|
||||
- FlipperKit/Core
|
||||
- FlipperKit/FlipperKitReactPlugin (0.33.1):
|
||||
- FlipperKit/Core
|
||||
- FlipperKit/FlipperKitUserDefaultsPlugin (0.33.1):
|
||||
- FlipperKit/Core
|
||||
- FlipperKit/SKIOSNetworkPlugin (0.33.1):
|
||||
- FlipperKit/Core
|
||||
- FlipperKit/FlipperKitNetworkPlugin
|
||||
- Folly (2018.10.22.00):
|
||||
- boost-for-react-native
|
||||
- DoubleConversion
|
||||
|
|
@ -80,10 +32,9 @@ PODS:
|
|||
- libwebp/mux (1.1.0):
|
||||
- libwebp/demux
|
||||
- libwebp/webp (1.1.0)
|
||||
- MMKV (1.0.24)
|
||||
- OpenSSL-Universal (1.0.2.19):
|
||||
- OpenSSL-Universal/Static (= 1.0.2.19)
|
||||
- OpenSSL-Universal/Static (1.0.2.19)
|
||||
- MMKV (1.1.0):
|
||||
- MMKVCore (~> 1.1.0)
|
||||
- MMKVCore (1.1.0)
|
||||
- Permission-Camera (2.0.10):
|
||||
- RNPermissions
|
||||
- Permission-PhotoLibrary (2.0.10):
|
||||
|
|
@ -264,8 +215,8 @@ PODS:
|
|||
- React
|
||||
- react-native-image-picker (2.3.1):
|
||||
- React
|
||||
- react-native-mmkv-storage (0.2.2):
|
||||
- MMKV (= 1.0.24)
|
||||
- react-native-mmkv-storage (0.3.1):
|
||||
- MMKV (= 1.1.0)
|
||||
- React
|
||||
- react-native-netinfo (4.4.0):
|
||||
- React
|
||||
|
|
@ -388,8 +339,6 @@ PODS:
|
|||
- Swime (3.0.6)
|
||||
- XCDYouTubeKit (2.8.2)
|
||||
- Yoga (1.14.0)
|
||||
- YogaKit (1.18.1):
|
||||
- Yoga (~> 1.14)
|
||||
- YoutubePlayer-in-WKWebView (0.3.4)
|
||||
|
||||
DEPENDENCIES:
|
||||
|
|
@ -397,11 +346,6 @@ DEPENDENCIES:
|
|||
- DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
|
||||
- FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`)
|
||||
- FBReactNativeSpec (from `../node_modules/react-native/Libraries/FBReactNativeSpec`)
|
||||
- FlipperKit (~> 0.33.1)
|
||||
- FlipperKit/FlipperKitLayoutPlugin (~> 0.33.1)
|
||||
- FlipperKit/FlipperKitReactPlugin (~> 0.33.1)
|
||||
- FlipperKit/FlipperKitUserDefaultsPlugin (~> 0.33.1)
|
||||
- FlipperKit/SKIOSNetworkPlugin (~> 0.33.1)
|
||||
- Folly (from `../node_modules/react-native/third-party-podspecs/Folly.podspec`)
|
||||
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
|
||||
- jail-monkey (from `../node_modules/jail-monkey`)
|
||||
|
|
@ -464,24 +408,14 @@ DEPENDENCIES:
|
|||
SPEC REPOS:
|
||||
https://github.com/cocoapods/specs.git:
|
||||
- boost-for-react-native
|
||||
- CocoaAsyncSocket
|
||||
- CocoaLibEvent
|
||||
- Flipper
|
||||
- Flipper-DoubleConversion
|
||||
- Flipper-Folly
|
||||
- Flipper-Glog
|
||||
- Flipper-PeerTalk
|
||||
- Flipper-RSocket
|
||||
- FlipperKit
|
||||
- libwebp
|
||||
- MMKV
|
||||
- OpenSSL-Universal
|
||||
- MMKVCore
|
||||
- SDWebImage
|
||||
- SDWebImageWebPCoder
|
||||
- Sentry
|
||||
- Swime
|
||||
- XCDYouTubeKit
|
||||
- YogaKit
|
||||
- YoutubePlayer-in-WKWebView
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
|
|
@ -603,24 +537,15 @@ EXTERNAL SOURCES:
|
|||
SPEC CHECKSUMS:
|
||||
boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c
|
||||
BVLinearGradient: e3aad03778a456d77928f594a649e96995f1c872
|
||||
CocoaAsyncSocket: 694058e7c0ed05a9e217d1b3c7ded962f4180845
|
||||
CocoaLibEvent: 2fab71b8bd46dd33ddb959f7928ec5909f838e3f
|
||||
DoubleConversion: 5805e889d232975c086db112ece9ed034df7a0b2
|
||||
FBLazyVector: 4aab18c93cd9546e4bfed752b4084585eca8b245
|
||||
FBReactNativeSpec: 5465d51ccfeecb7faa12f9ae0024f2044ce4044e
|
||||
Flipper: 6c1f484f9a88d30ab3e272800d53688439e50f69
|
||||
Flipper-DoubleConversion: 38631e41ef4f9b12861c67d17cb5518d06badc41
|
||||
Flipper-Folly: 2de3d03e0acc7064d5e4ed9f730e2f217486f162
|
||||
Flipper-Glog: 1dfd6abf1e922806c52ceb8701a3599a79a200a6
|
||||
Flipper-PeerTalk: 116d8f857dc6ef55c7a5a75ea3ceaafe878aadc9
|
||||
Flipper-RSocket: 1260a31c05c238eabfa9bb8a64e3983049048371
|
||||
FlipperKit: 6dc9b8f4ef60d9e5ded7f0264db299c91f18832e
|
||||
Folly: 30e7936e1c45c08d884aa59369ed951a8e68cf51
|
||||
glog: 1f3da668190260b06b429bb211bfbee5cd790c28
|
||||
jail-monkey: d7c5048b2336f22ee9c9e0efa145f1f917338ea9
|
||||
libwebp: 946cb3063cea9236285f7e9a8505d806d30e07f3
|
||||
MMKV: 758b2edee46b08bdd958db4169191afb9a6d4ebd
|
||||
OpenSSL-Universal: 8b48cc0d10c1b2923617dfe5c178aa9ed2689355
|
||||
MMKV: 7bb6c30f9ff2ea45bc2398c86e66dd1cb63cfe20
|
||||
MMKVCore: f1e0aad4fc330e7cbfbdff16720017bf0973db5a
|
||||
Permission-Camera: 8f0e5decca5f28f70f28a8dc31f012c9bad40ad8
|
||||
Permission-PhotoLibrary: b209bf23b784c9e1409a57d81c6d11ab1d3079c1
|
||||
RCTRequired: cec6a34b3ac8a9915c37e7e4ad3aa74726ce4035
|
||||
|
|
@ -638,7 +563,7 @@ SPEC CHECKSUMS:
|
|||
react-native-document-picker: 6acd41af22988cf349848678fdaa294d4448478e
|
||||
react-native-hw-keyboard-event: b517cefb8d5c659a38049c582de85ff43337dc53
|
||||
react-native-image-picker: 668e72d0277dc8c12ae90e835507c1eddd2e4f85
|
||||
react-native-mmkv-storage: 84162ebe353ecf7476d235c47becade29789ae2c
|
||||
react-native-mmkv-storage: d413e1e00b1f410a744e1d547a912a1f87e67260
|
||||
react-native-netinfo: 892a5130be97ff8bb69c523739c424a2ffc296d1
|
||||
react-native-notifications: 24706907104a0f00c35c4bde7e0ca76a50f730e1
|
||||
react-native-passcode-status: 88c4f6e074328bc278bd127646b6c694ad5a530a
|
||||
|
|
@ -676,9 +601,8 @@ SPEC CHECKSUMS:
|
|||
Swime: d7b2c277503b6cea317774aedc2dce05613f8b0b
|
||||
XCDYouTubeKit: 79baadb0560673a67c771eba45f83e353fd12c1f
|
||||
Yoga: 3ebccbdd559724312790e7742142d062476b698e
|
||||
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
|
||||
YoutubePlayer-in-WKWebView: af2f5929fc78882d94bfdfeea999b661b78d9717
|
||||
|
||||
PODFILE CHECKSUM: bc348868369c4079cbe32240a0dfc0f985e21f5d
|
||||
PODFILE CHECKSUM: b702b8a61a954eeed0648fa4ec502da4ea8b6748
|
||||
|
||||
COCOAPODS: 1.7.5
|
||||
|
|
|
|||
6
package-lock.json
generated
6
package-lock.json
generated
|
|
@ -20040,9 +20040,9 @@
|
|||
"integrity": "sha512-36cYGZGCG82pMiVJbQa5WMA93khP4v5JqLutFkMyB/eRpCULHmojNIBlbUPIY9SCeN4sg5VBRFTVGCtTg2r2kA=="
|
||||
},
|
||||
"react-native-mmkv-storage": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/react-native-mmkv-storage/-/react-native-mmkv-storage-0.2.2.tgz",
|
||||
"integrity": "sha512-QBMQzz7wpS2DGjOn5kIppqiD26p+jOJaJ0Am4DG2PImcUGe9iub5mkamz02mlqjyhq+8EhUes533uLK98APtmw=="
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-mmkv-storage/-/react-native-mmkv-storage-0.3.1.tgz",
|
||||
"integrity": "sha512-rNFgtgzvCsDsXU5MnKz5PA5GMkq8TyyixnGSXBZ3tIatwN/3Lq09OJA0YR7UEwsQOuIcnPsEtGEf7suBHo/SwA=="
|
||||
},
|
||||
"react-native-navigation": {
|
||||
"version": "6.4.0",
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@
|
|||
"react-native-keychain": "4.0.5",
|
||||
"react-native-linear-gradient": "2.5.6",
|
||||
"react-native-local-auth": "1.6.0",
|
||||
"react-native-mmkv-storage": "0.2.2",
|
||||
"react-native-mmkv-storage": "0.3.1",
|
||||
"react-native-navigation": "6.4.0",
|
||||
"react-native-notifications": "2.1.7",
|
||||
"react-native-passcode-status": "1.1.2",
|
||||
|
|
@ -150,7 +150,7 @@
|
|||
"check": "eslint --ignore-path .gitignore --ignore-pattern node_modules --quiet . && npm run tsc",
|
||||
"fix": "eslint --ignore-path .gitignore --ignore-pattern node_modules --quiet . --fix",
|
||||
"postinstall": "make post-install",
|
||||
"tsc": "tsc --noEmit",
|
||||
"tsc": "NODE_OPTIONS=--max_old_space_size=12000 tsc --noEmit",
|
||||
"test": "jest --forceExit --detectOpenHandles",
|
||||
"test:watch": "jest --watch",
|
||||
"test:coverage": "jest --coverage",
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ module.exports = [
|
|||
'app/actions/helpers/channels.ts',
|
||||
'app/actions/navigation/index.js',
|
||||
'app/actions/views/channel.js',
|
||||
'app/actions/views/emoji.js',
|
||||
'app/actions/views/post.js',
|
||||
'app/actions/views/login.js',
|
||||
'app/actions/views/root.js',
|
||||
'app/actions/views/select_server.js',
|
||||
'app/actions/views/user.js',
|
||||
|
|
@ -28,62 +28,26 @@ module.exports = [
|
|||
'app/init/emm_provider.js',
|
||||
'app/init/fetch.js',
|
||||
'app/init/global_event_handler.js',
|
||||
'app/initial_state.js',
|
||||
'app/mattermost.js',
|
||||
'app/mattermost_bucket/index.js',
|
||||
'app/mattermost_managed/index.js',
|
||||
'app/mattermost_managed/mattermost-managed.android.js',
|
||||
'app/push_notifications/index.js',
|
||||
'app/push_notifications/push_notifications.android.js',
|
||||
'app/reducers/app/build.js',
|
||||
'app/reducers/app/index.js',
|
||||
'app/reducers/app/version.js',
|
||||
'app/reducers/device/connection.js',
|
||||
'app/reducers/device/dimension.js',
|
||||
'app/reducers/device/index.js',
|
||||
'app/reducers/device/is_tablet.js',
|
||||
'app/reducers/device/orientation.js',
|
||||
'app/reducers/device/status_bar.js',
|
||||
'app/reducers/index.js',
|
||||
'app/reducers/navigation/index.js',
|
||||
'app/reducers/views/announcement.js',
|
||||
'app/reducers/views/channel.js',
|
||||
'app/reducers/views/client_upgrade.js',
|
||||
'app/reducers/views/emoji.js',
|
||||
'app/reducers/views/extension.js',
|
||||
'app/reducers/views/i18n.js',
|
||||
'app/reducers/views/index.js',
|
||||
'app/reducers/views/post.js',
|
||||
'app/reducers/views/recent_emojis.js',
|
||||
'app/reducers/views/root.js',
|
||||
'app/reducers/views/search.js',
|
||||
'app/reducers/views/select_server.js',
|
||||
'app/reducers/views/team.js',
|
||||
'app/reducers/views/thread.js',
|
||||
'app/reducers/views/user.js',
|
||||
'app/mm-redux/action_types/bots.ts',
|
||||
'app/mm-redux/action_types/channels.ts',
|
||||
'app/mm-redux/action_types/emojis.ts',
|
||||
'app/mm-redux/action_types/errors.ts',
|
||||
'app/mm-redux/action_types/files.ts',
|
||||
'app/mm-redux/action_types/general.ts',
|
||||
'app/mm-redux/action_types/gifs.ts',
|
||||
'app/mm-redux/action_types/groups.ts',
|
||||
'app/mm-redux/action_types/index.ts',
|
||||
'app/mm-redux/action_types/integrations.ts',
|
||||
'app/mm-redux/action_types/jobs.ts',
|
||||
'app/mm-redux/action_types/plugins.ts',
|
||||
'app/mm-redux/action_types/posts.ts',
|
||||
'app/mm-redux/action_types/preferences.ts',
|
||||
'app/mm-redux/action_types/roles.ts',
|
||||
'app/mm-redux/action_types/schemes.ts',
|
||||
'app/mm-redux/action_types/search.ts',
|
||||
'app/mm-redux/action_types/teams.ts',
|
||||
'app/mm-redux/action_types/users.ts',
|
||||
'app/mm-redux/actions/channels.ts',
|
||||
'app/mm-redux/actions/emojis.ts',
|
||||
'app/mm-redux/actions/errors.ts',
|
||||
'app/mm-redux/actions/files.ts',
|
||||
'app/mm-redux/actions/general.ts',
|
||||
'app/mm-redux/actions/helpers.ts',
|
||||
'app/mm-redux/actions/posts.ts',
|
||||
|
|
@ -96,19 +60,19 @@ module.exports = [
|
|||
'app/mm-redux/client/fetch_etag.ts',
|
||||
'app/mm-redux/client/index.ts',
|
||||
'app/mm-redux/constants/emoji.ts',
|
||||
'app/mm-redux/constants/files.ts',
|
||||
'app/mm-redux/constants/general.ts',
|
||||
'app/mm-redux/constants/groups.ts',
|
||||
'app/mm-redux/constants/index.ts',
|
||||
'app/mm-redux/constants/permissions.ts',
|
||||
'app/mm-redux/constants/plugins.ts',
|
||||
'app/mm-redux/constants/posts.ts',
|
||||
'app/mm-redux/constants/preferences.ts',
|
||||
'app/mm-redux/constants/request_status.ts',
|
||||
'app/mm-redux/constants/roles.ts',
|
||||
'app/mm-redux/constants/stats.ts',
|
||||
'app/mm-redux/constants/teams.ts',
|
||||
'app/mm-redux/constants/users.ts',
|
||||
'app/mm-redux/reducers/entities/bots.ts',
|
||||
'app/mm-redux/reducers/entities/channel_categories.ts',
|
||||
'app/mm-redux/reducers/entities/channels.ts',
|
||||
'app/mm-redux/reducers/entities/emojis.ts',
|
||||
'app/mm-redux/reducers/entities/files.ts',
|
||||
|
|
@ -152,10 +116,6 @@ module.exports = [
|
|||
'app/mm-redux/selectors/entities/teams.ts',
|
||||
'app/mm-redux/selectors/entities/timezone.ts',
|
||||
'app/mm-redux/selectors/entities/users.ts',
|
||||
'app/mm-redux/store/helpers.ts',
|
||||
'app/mm-redux/store/index.ts',
|
||||
'app/mm-redux/store/initial_state.ts',
|
||||
'app/mm-redux/store/middleware.ts',
|
||||
'app/mm-redux/types/actions.ts',
|
||||
'app/mm-redux/utils/channel_utils.ts',
|
||||
'app/mm-redux/utils/emoji_utils.ts',
|
||||
|
|
@ -164,19 +124,54 @@ module.exports = [
|
|||
'app/mm-redux/utils/helpers.ts',
|
||||
'app/mm-redux/utils/i18n_utils.ts',
|
||||
'app/mm-redux/utils/key_mirror.ts',
|
||||
'app/mm-redux/utils/post_list.ts',
|
||||
'app/mm-redux/utils/post_utils.ts',
|
||||
'app/mm-redux/utils/preference_utils.ts',
|
||||
'app/mm-redux/utils/sentry.ts',
|
||||
'app/mm-redux/utils/team_utils.ts',
|
||||
'app/mm-redux/utils/timezone_utils.ts',
|
||||
'app/mm-redux/utils/user_utils.ts',
|
||||
'app/push_notifications/index.js',
|
||||
'app/push_notifications/push_notifications.android.js',
|
||||
'app/reducers/app/build.js',
|
||||
'app/reducers/app/index.js',
|
||||
'app/reducers/app/version.js',
|
||||
'app/reducers/device/connection.js',
|
||||
'app/reducers/device/dimension.js',
|
||||
'app/reducers/device/index.js',
|
||||
'app/reducers/device/is_tablet.js',
|
||||
'app/reducers/device/orientation.js',
|
||||
'app/reducers/device/status_bar.js',
|
||||
'app/reducers/index.js',
|
||||
'app/reducers/views/announcement.js',
|
||||
'app/reducers/views/channel.js',
|
||||
'app/reducers/views/client_upgrade.js',
|
||||
'app/reducers/views/emoji.js',
|
||||
'app/reducers/views/extension.js',
|
||||
'app/reducers/views/i18n.js',
|
||||
'app/reducers/views/index.js',
|
||||
'app/reducers/views/post.js',
|
||||
'app/reducers/views/recent_emojis.js',
|
||||
'app/reducers/views/root.js',
|
||||
'app/reducers/views/search.js',
|
||||
'app/reducers/views/select_server.js',
|
||||
'app/reducers/views/team.js',
|
||||
'app/reducers/views/thread.js',
|
||||
'app/reducers/views/user.js',
|
||||
'app/screens/index.js',
|
||||
'app/selectors/channel.js',
|
||||
'app/selectors/i18n.js',
|
||||
'app/store/ephemeral_store.js',
|
||||
'app/store/index.js',
|
||||
'app/store/middleware.js',
|
||||
'app/store/store.js',
|
||||
'app/store/thunk.js',
|
||||
'app/store/helpers.ts',
|
||||
'app/store/index.ts',
|
||||
'app/store/initial_state.js',
|
||||
'app/store/middlewares/helpers.js',
|
||||
'app/store/middlewares/index.ts',
|
||||
'app/store/middlewares/message_retention.js',
|
||||
'app/store/middlewares/sentry.js',
|
||||
'app/store/middlewares/thunk.js',
|
||||
'app/store/mmkv_adapter.ts',
|
||||
'app/store/store.ts',
|
||||
'app/store/utils.js',
|
||||
'app/telemetry/index.js',
|
||||
'app/telemetry/telemetry.android.js',
|
||||
|
|
@ -185,13 +180,11 @@ module.exports = [
|
|||
'app/utils/file.js',
|
||||
'app/utils/general.js',
|
||||
'app/utils/i18n.js',
|
||||
'app/utils/image_cache_manager.js',
|
||||
'app/utils/preferences.js',
|
||||
'app/utils/push_notifications.js',
|
||||
'app/utils/security.js',
|
||||
'app/utils/segment.js',
|
||||
'app/utils/sentry/index.js',
|
||||
'app/utils/sentry/middleware.js',
|
||||
'app/utils/time_tracker.js',
|
||||
'app/utils/timezone.js',
|
||||
'dist/assets/config.json',
|
||||
|
|
@ -227,6 +220,7 @@ module.exports = [
|
|||
'node_modules/@babel/runtime/helpers/typeof.js',
|
||||
'node_modules/@babel/runtime/helpers/unsupportedIterableToArray.js',
|
||||
'node_modules/@babel/runtime/helpers/wrapNativeSuper.js',
|
||||
'node_modules/@babel/runtime/node_modules/regenerator-runtime/runtime.js',
|
||||
'node_modules/@babel/runtime/regenerator/index.js',
|
||||
'node_modules/@react-native-community/async-storage/lib/AsyncStorage.js',
|
||||
'node_modules/@react-native-community/async-storage/lib/hooks.js',
|
||||
|
|
@ -251,6 +245,12 @@ module.exports = [
|
|||
'node_modules/color-name/index.js',
|
||||
'node_modules/color-string/index.js',
|
||||
'node_modules/color/index.js',
|
||||
'node_modules/create-react-class/factory.js',
|
||||
'node_modules/create-react-class/index.js',
|
||||
'node_modules/create-react-class/node_modules/fbjs/lib/emptyFunction.js',
|
||||
'node_modules/create-react-class/node_modules/fbjs/lib/emptyObject.js',
|
||||
'node_modules/create-react-class/node_modules/fbjs/lib/invariant.js',
|
||||
'node_modules/create-react-class/node_modules/fbjs/lib/warning.js',
|
||||
'node_modules/component-emitter/index.js',
|
||||
'node_modules/deepmerge/dist/cjs.js',
|
||||
'node_modules/event-target-shim/dist/event-target-shim.js',
|
||||
|
|
@ -263,10 +263,8 @@ module.exports = [
|
|||
'node_modules/fbjs/lib/performance.js',
|
||||
'node_modules/fbjs/lib/performanceNow.js',
|
||||
'node_modules/fbjs/lib/warning.js',
|
||||
'node_modules/get-params/index.js',
|
||||
'node_modules/harmony-reflect/reflect.js',
|
||||
'node_modules/hoist-non-react-statics/dist/hoist-non-react-statics.cjs.js',
|
||||
'node_modules/ieee754/index.js',
|
||||
'node_modules/inherits/inherits_browser.js',
|
||||
'node_modules/intl-format-cache/dist/index.js',
|
||||
'node_modules/intl-format-cache/index.js',
|
||||
|
|
@ -290,16 +288,9 @@ module.exports = [
|
|||
'node_modules/intl/index.js',
|
||||
'node_modules/intl/lib/core.js',
|
||||
'node_modules/intl/locale-data/complete.js',
|
||||
'node_modules/invariant/browser.js',
|
||||
'node_modules/is-arguments/index.js',
|
||||
'node_modules/is-generator-function/index.js',
|
||||
'node_modules/jsan/index.js',
|
||||
'node_modules/jsan/lib/cycle.js',
|
||||
'node_modules/jsan/lib/index.js',
|
||||
'node_modules/jsan/lib/path-getter.js',
|
||||
'node_modules/jsan/lib/utils.js',
|
||||
'node_modules/json-stringify-safe/stringify.js',
|
||||
'node_modules/linked-list/_source/linked-list.js',
|
||||
'node_modules/linked-list/index.js',
|
||||
'node_modules/lodash.clonedeep/index.js',
|
||||
'node_modules/lodash.forin/index.js',
|
||||
'node_modules/lodash.get/index.js',
|
||||
|
|
@ -322,8 +313,6 @@ module.exports = [
|
|||
'node_modules/lodash/_apply.js',
|
||||
'node_modules/lodash/_arrayEach.js',
|
||||
'node_modules/lodash/_arrayFilter.js',
|
||||
'node_modules/lodash/_arrayIncludes.js',
|
||||
'node_modules/lodash/_arrayIncludesWith.js',
|
||||
'node_modules/lodash/_arrayLikeKeys.js',
|
||||
'node_modules/lodash/_arrayMap.js',
|
||||
'node_modules/lodash/_arrayPush.js',
|
||||
|
|
@ -337,9 +326,7 @@ module.exports = [
|
|||
'node_modules/lodash/_baseClamp.js',
|
||||
'node_modules/lodash/_baseClone.js',
|
||||
'node_modules/lodash/_baseCreate.js',
|
||||
'node_modules/lodash/_baseDifference.js',
|
||||
'node_modules/lodash/_baseEach.js',
|
||||
'node_modules/lodash/_baseFindIndex.js',
|
||||
'node_modules/lodash/_baseFlatten.js',
|
||||
'node_modules/lodash/_baseFor.js',
|
||||
'node_modules/lodash/_baseForOwn.js',
|
||||
|
|
@ -347,13 +334,11 @@ module.exports = [
|
|||
'node_modules/lodash/_baseGetAllKeys.js',
|
||||
'node_modules/lodash/_baseGetTag.js',
|
||||
'node_modules/lodash/_baseHasIn.js',
|
||||
'node_modules/lodash/_baseIndexOf.js',
|
||||
'node_modules/lodash/_baseIsArguments.js',
|
||||
'node_modules/lodash/_baseIsEqual.js',
|
||||
'node_modules/lodash/_baseIsEqualDeep.js',
|
||||
'node_modules/lodash/_baseIsMap.js',
|
||||
'node_modules/lodash/_baseIsMatch.js',
|
||||
'node_modules/lodash/_baseIsNaN.js',
|
||||
'node_modules/lodash/_baseIsNative.js',
|
||||
'node_modules/lodash/_baseIsSet.js',
|
||||
'node_modules/lodash/_baseIsTypedArray.js',
|
||||
|
|
@ -376,7 +361,6 @@ module.exports = [
|
|||
'node_modules/lodash/_baseTimes.js',
|
||||
'node_modules/lodash/_baseToString.js',
|
||||
'node_modules/lodash/_baseUnary.js',
|
||||
'node_modules/lodash/_baseUniq.js',
|
||||
'node_modules/lodash/_baseUnset.js',
|
||||
'node_modules/lodash/_cacheHas.js',
|
||||
'node_modules/lodash/_castFunction.js',
|
||||
|
|
@ -464,14 +448,12 @@ module.exports = [
|
|||
'node_modules/lodash/_stackGet.js',
|
||||
'node_modules/lodash/_stackHas.js',
|
||||
'node_modules/lodash/_stackSet.js',
|
||||
'node_modules/lodash/_strictIndexOf.js',
|
||||
'node_modules/lodash/_stringToPath.js',
|
||||
'node_modules/lodash/_toKey.js',
|
||||
'node_modules/lodash/_toSource.js',
|
||||
'node_modules/lodash/clone.js',
|
||||
'node_modules/lodash/cloneDeep.js',
|
||||
'node_modules/lodash/constant.js',
|
||||
'node_modules/lodash/difference.js',
|
||||
'node_modules/lodash/endsWith.js',
|
||||
'node_modules/lodash/eq.js',
|
||||
'node_modules/lodash/flatten.js',
|
||||
|
|
@ -501,7 +483,6 @@ module.exports = [
|
|||
'node_modules/lodash/last.js',
|
||||
'node_modules/lodash/lodash.js',
|
||||
'node_modules/lodash/map.js',
|
||||
'node_modules/lodash/mapValues.js',
|
||||
'node_modules/lodash/memoize.js',
|
||||
'node_modules/lodash/merge.js',
|
||||
'node_modules/lodash/noop.js',
|
||||
|
|
@ -516,7 +497,6 @@ module.exports = [
|
|||
'node_modules/lodash/toNumber.js',
|
||||
'node_modules/lodash/toPlainObject.js',
|
||||
'node_modules/lodash/toString.js',
|
||||
'node_modules/lodash/union.js',
|
||||
'node_modules/lodash/uniqueId.js',
|
||||
'node_modules/lodash/unset.js',
|
||||
'node_modules/metro/src/lib/bundle-modules/HMRClient.js',
|
||||
|
|
@ -524,7 +504,6 @@ module.exports = [
|
|||
'node_modules/moment-timezone/index.js',
|
||||
'node_modules/moment-timezone/moment-timezone.js',
|
||||
'node_modules/moment/moment.js',
|
||||
'node_modules/nanoid/non-secure/index.js',
|
||||
'node_modules/nullthrows/nullthrows.js',
|
||||
'node_modules/object-assign/index.js',
|
||||
'node_modules/promise/setimmediate/core.js',
|
||||
|
|
@ -536,9 +515,6 @@ module.exports = [
|
|||
'node_modules/prop-types/factoryWithTypeCheckers.js',
|
||||
'node_modules/prop-types/index.js',
|
||||
'node_modules/prop-types/lib/ReactPropTypesSecret.js',
|
||||
'node_modules/querystring/decode.js',
|
||||
'node_modules/querystring/encode.js',
|
||||
'node_modules/querystring/index.js',
|
||||
'node_modules/querystringify/index.js',
|
||||
'node_modules/react-intl/lib/index.js',
|
||||
'node_modules/react-intl/locale-data/en.js',
|
||||
|
|
@ -548,10 +524,6 @@ module.exports = [
|
|||
'node_modules/react-lifecycles-compat/react-lifecycles-compat.cjs.js',
|
||||
'node_modules/react-native-cookies/index.js',
|
||||
'node_modules/react-native-device-info/deviceinfo.js',
|
||||
'node_modules/react-native-gesture-handler/Directions.js',
|
||||
'node_modules/react-native-gesture-handler/DrawerLayout.js',
|
||||
'node_modules/react-native-gesture-handler/GestureButtons.js',
|
||||
'node_modules/react-native-gesture-handler/GestureComponents.js',
|
||||
'node_modules/react-native-gesture-handler/GestureHandler.js',
|
||||
'node_modules/react-native-gesture-handler/GestureHandlerButton.js',
|
||||
'node_modules/react-native-gesture-handler/GestureHandlerPropTypes.js',
|
||||
|
|
@ -561,19 +533,24 @@ module.exports = [
|
|||
'node_modules/react-native-gesture-handler/PlatformConstants.js',
|
||||
'node_modules/react-native-gesture-handler/RNGestureHandlerModule.js',
|
||||
'node_modules/react-native-gesture-handler/State.js',
|
||||
'node_modules/react-native-gesture-handler/Swipeable.js',
|
||||
'node_modules/react-native-gesture-handler/createHandler.js',
|
||||
'node_modules/react-native-gesture-handler/createNativeWrapper.js',
|
||||
'node_modules/react-native-gesture-handler/gestureHandlerRootHOC.js',
|
||||
'node_modules/react-native-gesture-handler/index.js',
|
||||
'node_modules/react-native-gesture-handler/touchables/GenericTouchable.js',
|
||||
'node_modules/react-native-gesture-handler/touchables/TouchableHighlight.js',
|
||||
'node_modules/react-native-gesture-handler/touchables/TouchableNativeFeedback.android.js',
|
||||
'node_modules/react-native-gesture-handler/touchables/TouchableOpacity.js',
|
||||
'node_modules/react-native-gesture-handler/touchables/TouchableWithoutFeedback.js',
|
||||
'node_modules/react-native-gesture-handler/touchables/index.js',
|
||||
'node_modules/react-native-haptic-feedback/index.js',
|
||||
'node_modules/react-native-keychain/index.js',
|
||||
'node_modules/react-native-mmkv-storage/index.js',
|
||||
'node_modules/react-native-mmkv-storage/src/api.js',
|
||||
'node_modules/react-native-mmkv-storage/src/encryption.js',
|
||||
'node_modules/react-native-mmkv-storage/src/indexer/arrays.js',
|
||||
'node_modules/react-native-mmkv-storage/src/indexer/booleans.js',
|
||||
'node_modules/react-native-mmkv-storage/src/indexer/indexer.js',
|
||||
'node_modules/react-native-mmkv-storage/src/indexer/maps.js',
|
||||
'node_modules/react-native-mmkv-storage/src/indexer/numbers.js',
|
||||
'node_modules/react-native-mmkv-storage/src/indexer/strings.js',
|
||||
'node_modules/react-native-mmkv-storage/src/keygen.js',
|
||||
'node_modules/react-native-mmkv-storage/src/loader.js',
|
||||
'node_modules/react-native-mmkv-storage/src/utils.js',
|
||||
'node_modules/react-native-navigation/lib/dist/Navigation.js',
|
||||
'node_modules/react-native-navigation/lib/dist/adapters/AppRegistryService.js',
|
||||
'node_modules/react-native-navigation/lib/dist/adapters/AssetResolver.js',
|
||||
|
|
@ -851,25 +828,25 @@ module.exports = [
|
|||
'node_modules/react-redux/lib/utils/verifyPlainObject.js',
|
||||
'node_modules/react-redux/lib/utils/warning.js',
|
||||
'node_modules/react-redux/node_modules/hoist-non-react-statics/dist/hoist-non-react-statics.cjs.js',
|
||||
'node_modules/react-refresh/cjs/react-refresh-runtime.production.js',
|
||||
'node_modules/react-refresh/cjs/react-refresh-runtime.production.min.js',
|
||||
'node_modules/react-refresh/runtime.js',
|
||||
'node_modules/react/cjs/react.production.js',
|
||||
'node_modules/react/cjs/react.production.min.js',
|
||||
'node_modules/react/index.js',
|
||||
'node_modules/redux-action-buffer/index.js',
|
||||
'node_modules/redux-batched-actions/lib/index.js',
|
||||
'node_modules/redux-persist-transform-filter/dist/index.js',
|
||||
'node_modules/redux-persist/lib/autoRehydrate.js',
|
||||
'node_modules/redux-persist/lib/constants.js',
|
||||
'node_modules/redux-persist/lib/createPersistor.js',
|
||||
'node_modules/redux-persist/lib/createMigrate.js',
|
||||
'node_modules/redux-persist/lib/createPersistoid.js',
|
||||
'node_modules/redux-persist/lib/createTransform.js',
|
||||
'node_modules/redux-persist/lib/defaults/asyncLocalStorage.js',
|
||||
'node_modules/redux-persist/lib/getStoredState.js',
|
||||
'node_modules/redux-persist/lib/index.js',
|
||||
'node_modules/redux-persist/lib/persistCombineReducers.js',
|
||||
'node_modules/redux-persist/lib/persistReducer.js',
|
||||
'node_modules/redux-persist/lib/persistStore.js',
|
||||
'node_modules/redux-persist/lib/purgeStoredState.js',
|
||||
'node_modules/redux-persist/lib/utils/isStatePlainEnough.js',
|
||||
'node_modules/redux-persist/lib/utils/setImmediate.js',
|
||||
'node_modules/redux-thunk/lib/index.js',
|
||||
'node_modules/redux-persist/lib/stateReconciler/autoMergeLevel1.js',
|
||||
'node_modules/redux-reset/lib/index.js',
|
||||
'node_modules/redux/lib/redux.js',
|
||||
'node_modules/requires-port/index.js',
|
||||
'node_modules/reselect/lib/index.js',
|
||||
|
|
@ -880,13 +857,18 @@ module.exports = [
|
|||
'node_modules/rn-fetch-blob/ios.js',
|
||||
'node_modules/rn-fetch-blob/json-stream.js',
|
||||
'node_modules/rn-fetch-blob/polyfill/Fetch.js',
|
||||
'node_modules/rn-host-detect/index.js',
|
||||
'node_modules/sc-channel/index.js',
|
||||
'node_modules/sc-errors/decycle.js',
|
||||
'node_modules/sc-errors/index.js',
|
||||
'node_modules/sc-formatter/index.js',
|
||||
'node_modules/scheduler/cjs/scheduler-tracing.production.js',
|
||||
'node_modules/scheduler/cjs/scheduler.production.js',
|
||||
'node_modules/rn-fetch-blob/polyfill/File.js',
|
||||
'node_modules/rn-fetch-blob/polyfill/FileReader.js',
|
||||
'node_modules/rn-fetch-blob/polyfill/ProgressEvent.js',
|
||||
'node_modules/rn-fetch-blob/polyfill/XMLHttpRequest.js',
|
||||
'node_modules/rn-fetch-blob/polyfill/XMLHttpRequestEventTarget.js',
|
||||
'node_modules/rn-fetch-blob/polyfill/index.js',
|
||||
'node_modules/rn-fetch-blob/utils/log.js',
|
||||
'node_modules/rn-fetch-blob/utils/unicode.js',
|
||||
'node_modules/rn-fetch-blob/utils/uri.js',
|
||||
'node_modules/rn-fetch-blob/utils/uuid.js',
|
||||
'node_modules/scheduler/cjs/scheduler-tracing.production.min.js',
|
||||
'node_modules/scheduler/cjs/scheduler.production.min.js',
|
||||
'node_modules/scheduler/index.js',
|
||||
'node_modules/scheduler/tracing.js',
|
||||
'node_modules/semver/classes/comparator.js',
|
||||
|
|
@ -937,7 +919,6 @@ module.exports = [
|
|||
'node_modules/stacktrace-parser/dist/stack-trace-parser.cjs.js',
|
||||
'node_modules/symbol-observable/lib/index.js',
|
||||
'node_modules/symbol-observable/lib/ponyfill.js',
|
||||
'node_modules/tslib/tslib.js',
|
||||
'node_modules/url-parse/index.js',
|
||||
'node_modules/util/support/isBufferBrowser.js',
|
||||
'node_modules/util/support/types.js',
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
module.exports = [
|
||||
'./node_modules/app/actions/device/index.js',
|
||||
'./node_modules/app/actions/helpers/channels.ts',
|
||||
'./node_modules/app/actions/navigation/index.js',
|
||||
'./node_modules/app/actions/views/channel.js',
|
||||
'./node_modules/app/actions/views/emoji.js',
|
||||
'./node_modules/app/actions/views/post.js',
|
||||
'./node_modules/app/actions/views/login.js',
|
||||
'./node_modules/app/actions/views/root.js',
|
||||
'./node_modules/app/actions/views/select_server.js',
|
||||
'./node_modules/app/actions/views/user.js',
|
||||
|
|
@ -27,62 +28,26 @@ module.exports = [
|
|||
'./node_modules/app/init/emm_provider.js',
|
||||
'./node_modules/app/init/fetch.js',
|
||||
'./node_modules/app/init/global_event_handler.js',
|
||||
'./node_modules/app/initial_state.js',
|
||||
'./node_modules/app/mattermost.js',
|
||||
'./node_modules/app/mattermost_bucket/index.js',
|
||||
'./node_modules/app/mattermost_managed/index.js',
|
||||
'./node_modules/app/mattermost_managed/mattermost-managed.android.js',
|
||||
'./node_modules/app/push_notifications/index.js',
|
||||
'./node_modules/app/push_notifications/push_notifications.android.js',
|
||||
'./node_modules/app/reducers/app/build.js',
|
||||
'./node_modules/app/reducers/app/index.js',
|
||||
'./node_modules/app/reducers/app/version.js',
|
||||
'./node_modules/app/reducers/device/connection.js',
|
||||
'./node_modules/app/reducers/device/dimension.js',
|
||||
'./node_modules/app/reducers/device/index.js',
|
||||
'./node_modules/app/reducers/device/is_tablet.js',
|
||||
'./node_modules/app/reducers/device/orientation.js',
|
||||
'./node_modules/app/reducers/device/status_bar.js',
|
||||
'./node_modules/app/reducers/index.js',
|
||||
'./node_modules/app/reducers/navigation/index.js',
|
||||
'./node_modules/app/reducers/views/announcement.js',
|
||||
'./node_modules/app/reducers/views/channel.js',
|
||||
'./node_modules/app/reducers/views/client_upgrade.js',
|
||||
'./node_modules/app/reducers/views/emoji.js',
|
||||
'./node_modules/app/reducers/views/extension.js',
|
||||
'./node_modules/app/reducers/views/i18n.js',
|
||||
'./node_modules/app/reducers/views/index.js',
|
||||
'./node_modules/app/reducers/views/post.js',
|
||||
'./node_modules/app/reducers/views/recent_emojis.js',
|
||||
'./node_modules/app/reducers/views/root.js',
|
||||
'./node_modules/app/reducers/views/search.js',
|
||||
'./node_modules/app/reducers/views/select_server.js',
|
||||
'./node_modules/app/reducers/views/team.js',
|
||||
'./node_modules/app/reducers/views/thread.js',
|
||||
'./node_modules/app/reducers/views/user.js',
|
||||
'./node_modules/app/mm-redux/action_types/bots.ts',
|
||||
'./node_modules/app/mm-redux/action_types/channels.ts',
|
||||
'./node_modules/app/mm-redux/action_types/emojis.ts',
|
||||
'./node_modules/app/mm-redux/action_types/errors.ts',
|
||||
'./node_modules/app/mm-redux/action_types/files.ts',
|
||||
'./node_modules/app/mm-redux/action_types/general.ts',
|
||||
'./node_modules/app/mm-redux/action_types/gifs.ts',
|
||||
'./node_modules/app/mm-redux/action_types/groups.ts',
|
||||
'./node_modules/app/mm-redux/action_types/index.ts',
|
||||
'./node_modules/app/mm-redux/action_types/integrations.ts',
|
||||
'./node_modules/app/mm-redux/action_types/jobs.ts',
|
||||
'./node_modules/app/mm-redux/action_types/plugins.ts',
|
||||
'./node_modules/app/mm-redux/action_types/posts.ts',
|
||||
'./node_modules/app/mm-redux/action_types/preferences.ts',
|
||||
'./node_modules/app/mm-redux/action_types/roles.ts',
|
||||
'./node_modules/app/mm-redux/action_types/schemes.ts',
|
||||
'./node_modules/app/mm-redux/action_types/search.ts',
|
||||
'./node_modules/app/mm-redux/action_types/teams.ts',
|
||||
'./node_modules/app/mm-redux/action_types/users.ts',
|
||||
'./node_modules/app/mm-redux/actions/channels.ts',
|
||||
'./node_modules/app/mm-redux/actions/emojis.ts',
|
||||
'./node_modules/app/mm-redux/actions/errors.ts',
|
||||
'./node_modules/app/mm-redux/actions/files.ts',
|
||||
'./node_modules/app/mm-redux/actions/general.ts',
|
||||
'./node_modules/app/mm-redux/actions/helpers.ts',
|
||||
'./node_modules/app/mm-redux/actions/posts.ts',
|
||||
|
|
@ -95,19 +60,19 @@ module.exports = [
|
|||
'./node_modules/app/mm-redux/client/fetch_etag.ts',
|
||||
'./node_modules/app/mm-redux/client/index.ts',
|
||||
'./node_modules/app/mm-redux/constants/emoji.ts',
|
||||
'./node_modules/app/mm-redux/constants/files.ts',
|
||||
'./node_modules/app/mm-redux/constants/general.ts',
|
||||
'./node_modules/app/mm-redux/constants/groups.ts',
|
||||
'./node_modules/app/mm-redux/constants/index.ts',
|
||||
'./node_modules/app/mm-redux/constants/permissions.ts',
|
||||
'./node_modules/app/mm-redux/constants/plugins.ts',
|
||||
'./node_modules/app/mm-redux/constants/posts.ts',
|
||||
'./node_modules/app/mm-redux/constants/preferences.ts',
|
||||
'./node_modules/app/mm-redux/constants/request_status.ts',
|
||||
'./node_modules/app/mm-redux/constants/roles.ts',
|
||||
'./node_modules/app/mm-redux/constants/stats.ts',
|
||||
'./node_modules/app/mm-redux/constants/teams.ts',
|
||||
'./node_modules/app/mm-redux/constants/users.ts',
|
||||
'./node_modules/app/mm-redux/reducers/entities/bots.ts',
|
||||
'./node_modules/app/mm-redux/reducers/entities/channel_categories.ts',
|
||||
'./node_modules/app/mm-redux/reducers/entities/channels.ts',
|
||||
'./node_modules/app/mm-redux/reducers/entities/emojis.ts',
|
||||
'./node_modules/app/mm-redux/reducers/entities/files.ts',
|
||||
|
|
@ -151,10 +116,6 @@ module.exports = [
|
|||
'./node_modules/app/mm-redux/selectors/entities/teams.ts',
|
||||
'./node_modules/app/mm-redux/selectors/entities/timezone.ts',
|
||||
'./node_modules/app/mm-redux/selectors/entities/users.ts',
|
||||
'./node_modules/app/mm-redux/store/helpers.ts',
|
||||
'./node_modules/app/mm-redux/store/index.ts',
|
||||
'./node_modules/app/mm-redux/store/initial_state.ts',
|
||||
'./node_modules/app/mm-redux/store/middleware.ts',
|
||||
'./node_modules/app/mm-redux/types/actions.ts',
|
||||
'./node_modules/app/mm-redux/utils/channel_utils.ts',
|
||||
'./node_modules/app/mm-redux/utils/emoji_utils.ts',
|
||||
|
|
@ -163,19 +124,54 @@ module.exports = [
|
|||
'./node_modules/app/mm-redux/utils/helpers.ts',
|
||||
'./node_modules/app/mm-redux/utils/i18n_utils.ts',
|
||||
'./node_modules/app/mm-redux/utils/key_mirror.ts',
|
||||
'./node_modules/app/mm-redux/utils/post_list.ts',
|
||||
'./node_modules/app/mm-redux/utils/post_utils.ts',
|
||||
'./node_modules/app/mm-redux/utils/preference_utils.ts',
|
||||
'./node_modules/app/mm-redux/utils/sentry.ts',
|
||||
'./node_modules/app/mm-redux/utils/team_utils.ts',
|
||||
'./node_modules/app/mm-redux/utils/timezone_utils.ts',
|
||||
'./node_modules/app/mm-redux/utils/user_utils.ts',
|
||||
'./node_modules/app/push_notifications/index.js',
|
||||
'./node_modules/app/push_notifications/push_notifications.android.js',
|
||||
'./node_modules/app/reducers/app/build.js',
|
||||
'./node_modules/app/reducers/app/index.js',
|
||||
'./node_modules/app/reducers/app/version.js',
|
||||
'./node_modules/app/reducers/device/connection.js',
|
||||
'./node_modules/app/reducers/device/dimension.js',
|
||||
'./node_modules/app/reducers/device/index.js',
|
||||
'./node_modules/app/reducers/device/is_tablet.js',
|
||||
'./node_modules/app/reducers/device/orientation.js',
|
||||
'./node_modules/app/reducers/device/status_bar.js',
|
||||
'./node_modules/app/reducers/index.js',
|
||||
'./node_modules/app/reducers/views/announcement.js',
|
||||
'./node_modules/app/reducers/views/channel.js',
|
||||
'./node_modules/app/reducers/views/client_upgrade.js',
|
||||
'./node_modules/app/reducers/views/emoji.js',
|
||||
'./node_modules/app/reducers/views/extension.js',
|
||||
'./node_modules/app/reducers/views/i18n.js',
|
||||
'./node_modules/app/reducers/views/index.js',
|
||||
'./node_modules/app/reducers/views/post.js',
|
||||
'./node_modules/app/reducers/views/recent_emojis.js',
|
||||
'./node_modules/app/reducers/views/root.js',
|
||||
'./node_modules/app/reducers/views/search.js',
|
||||
'./node_modules/app/reducers/views/select_server.js',
|
||||
'./node_modules/app/reducers/views/team.js',
|
||||
'./node_modules/app/reducers/views/thread.js',
|
||||
'./node_modules/app/reducers/views/user.js',
|
||||
'./node_modules/app/screens/index.js',
|
||||
'./node_modules/app/selectors/channel.js',
|
||||
'./node_modules/app/selectors/i18n.js',
|
||||
'./node_modules/app/store/ephemeral_store.js',
|
||||
'./node_modules/app/store/index.js',
|
||||
'./node_modules/app/store/middleware.js',
|
||||
'./node_modules/app/store/store.js',
|
||||
'./node_modules/app/store/thunk.js',
|
||||
'./node_modules/app/store/helpers.ts',
|
||||
'./node_modules/app/store/index.ts',
|
||||
'./node_modules/app/store/initial_state.js',
|
||||
'./node_modules/app/store/middlewares/helpers.js',
|
||||
'./node_modules/app/store/middlewares/index.ts',
|
||||
'./node_modules/app/store/middlewares/message_retention.js',
|
||||
'./node_modules/app/store/middlewares/sentry.js',
|
||||
'./node_modules/app/store/middlewares/thunk.js',
|
||||
'./node_modules/app/store/mmkv_adapter.ts',
|
||||
'./node_modules/app/store/store.ts',
|
||||
'./node_modules/app/store/utils.js',
|
||||
'./node_modules/app/telemetry/index.js',
|
||||
'./node_modules/app/telemetry/telemetry.android.js',
|
||||
|
|
@ -184,13 +180,11 @@ module.exports = [
|
|||
'./node_modules/app/utils/file.js',
|
||||
'./node_modules/app/utils/general.js',
|
||||
'./node_modules/app/utils/i18n.js',
|
||||
'./node_modules/app/utils/image_cache_manager.js',
|
||||
'./node_modules/app/utils/preferences.js',
|
||||
'./node_modules/app/utils/push_notifications.js',
|
||||
'./node_modules/app/utils/security.js',
|
||||
'./node_modules/app/utils/segment.js',
|
||||
'./node_modules/app/utils/sentry/index.js',
|
||||
'./node_modules/app/utils/sentry/middleware.js',
|
||||
'./node_modules/app/utils/time_tracker.js',
|
||||
'./node_modules/app/utils/timezone.js',
|
||||
'./node_modules/index.js',
|
||||
|
|
@ -224,19 +218,11 @@ module.exports = [
|
|||
'./node_modules/node_modules/@babel/runtime/helpers/typeof.js',
|
||||
'./node_modules/node_modules/@babel/runtime/helpers/unsupportedIterableToArray.js',
|
||||
'./node_modules/node_modules/@babel/runtime/helpers/wrapNativeSuper.js',
|
||||
'./node_modules/node_modules/@babel/runtime/node_modules/regenerator-runtime/runtime.js',
|
||||
'./node_modules/node_modules/@babel/runtime/regenerator/index.js',
|
||||
'./node_modules/node_modules/@react-native-community/async-storage/lib/AsyncStorage.js',
|
||||
'./node_modules/node_modules/@react-native-community/async-storage/lib/hooks.js',
|
||||
'./node_modules/node_modules/@react-native-community/async-storage/lib/index.js',
|
||||
'./node_modules/node_modules/@react-native-community/netinfo/src/index.ts',
|
||||
'./node_modules/node_modules/@react-native-community/netinfo/src/internal/deprecatedState.ts',
|
||||
'./node_modules/node_modules/@react-native-community/netinfo/src/internal/deprecatedTypes.ts',
|
||||
'./node_modules/node_modules/@react-native-community/netinfo/src/internal/deprecatedUtils.ts',
|
||||
'./node_modules/node_modules/@react-native-community/netinfo/src/internal/internetReachability.ts',
|
||||
'./node_modules/node_modules/@react-native-community/netinfo/src/internal/nativeInterface.ts',
|
||||
'./node_modules/node_modules/@react-native-community/netinfo/src/internal/state.ts',
|
||||
'./node_modules/node_modules/@react-native-community/netinfo/src/internal/types.ts',
|
||||
'./node_modules/node_modules/@react-native-community/netinfo/src/internal/utils.ts',
|
||||
'./node_modules/node_modules/base-64/base64.js',
|
||||
'./node_modules/node_modules/base64-js/index.js',
|
||||
'./node_modules/node_modules/buffer/index.js',
|
||||
|
|
@ -247,6 +233,12 @@ module.exports = [
|
|||
'./node_modules/node_modules/color-name/index.js',
|
||||
'./node_modules/node_modules/color-string/index.js',
|
||||
'./node_modules/node_modules/color/index.js',
|
||||
'./node_modules/node_modules/create-react-class/factory.js',
|
||||
'./node_modules/node_modules/create-react-class/index.js',
|
||||
'./node_modules/node_modules/create-react-class/node_modules/fbjs/lib/emptyFunction.js',
|
||||
'./node_modules/node_modules/create-react-class/node_modules/fbjs/lib/emptyObject.js',
|
||||
'./node_modules/node_modules/create-react-class/node_modules/fbjs/lib/invariant.js',
|
||||
'./node_modules/node_modules/create-react-class/node_modules/fbjs/lib/warning.js',
|
||||
'./node_modules/node_modules/component-emitter/index.js',
|
||||
'./node_modules/node_modules/deepmerge/dist/cjs.js',
|
||||
'./node_modules/node_modules/event-target-shim/dist/event-target-shim.js',
|
||||
|
|
@ -259,10 +251,8 @@ module.exports = [
|
|||
'./node_modules/node_modules/fbjs/lib/performance.js',
|
||||
'./node_modules/node_modules/fbjs/lib/performanceNow.js',
|
||||
'./node_modules/node_modules/fbjs/lib/warning.js',
|
||||
'./node_modules/node_modules/get-params/index.js',
|
||||
'./node_modules/node_modules/harmony-reflect/reflect.js',
|
||||
'./node_modules/node_modules/hoist-non-react-statics/dist/hoist-non-react-statics.cjs.js',
|
||||
'./node_modules/node_modules/ieee754/index.js',
|
||||
'./node_modules/node_modules/inherits/inherits_browser.js',
|
||||
'./node_modules/node_modules/intl-format-cache/dist/index.js',
|
||||
'./node_modules/node_modules/intl-format-cache/index.js',
|
||||
|
|
@ -286,16 +276,9 @@ module.exports = [
|
|||
'./node_modules/node_modules/intl/index.js',
|
||||
'./node_modules/node_modules/intl/lib/core.js',
|
||||
'./node_modules/node_modules/intl/locale-data/complete.js',
|
||||
'./node_modules/node_modules/invariant/browser.js',
|
||||
'./node_modules/node_modules/is-arguments/index.js',
|
||||
'./node_modules/node_modules/is-generator-function/index.js',
|
||||
'./node_modules/node_modules/jsan/index.js',
|
||||
'./node_modules/node_modules/jsan/lib/cycle.js',
|
||||
'./node_modules/node_modules/jsan/lib/index.js',
|
||||
'./node_modules/node_modules/jsan/lib/path-getter.js',
|
||||
'./node_modules/node_modules/jsan/lib/utils.js',
|
||||
'./node_modules/node_modules/json-stringify-safe/stringify.js',
|
||||
'./node_modules/node_modules/linked-list/_source/linked-list.js',
|
||||
'./node_modules/node_modules/linked-list/index.js',
|
||||
'./node_modules/node_modules/lodash.clonedeep/index.js',
|
||||
'./node_modules/node_modules/lodash.forin/index.js',
|
||||
'./node_modules/node_modules/lodash.get/index.js',
|
||||
|
|
@ -318,8 +301,6 @@ module.exports = [
|
|||
'./node_modules/node_modules/lodash/_apply.js',
|
||||
'./node_modules/node_modules/lodash/_arrayEach.js',
|
||||
'./node_modules/node_modules/lodash/_arrayFilter.js',
|
||||
'./node_modules/node_modules/lodash/_arrayIncludes.js',
|
||||
'./node_modules/node_modules/lodash/_arrayIncludesWith.js',
|
||||
'./node_modules/node_modules/lodash/_arrayLikeKeys.js',
|
||||
'./node_modules/node_modules/lodash/_arrayMap.js',
|
||||
'./node_modules/node_modules/lodash/_arrayPush.js',
|
||||
|
|
@ -333,9 +314,7 @@ module.exports = [
|
|||
'./node_modules/node_modules/lodash/_baseClamp.js',
|
||||
'./node_modules/node_modules/lodash/_baseClone.js',
|
||||
'./node_modules/node_modules/lodash/_baseCreate.js',
|
||||
'./node_modules/node_modules/lodash/_baseDifference.js',
|
||||
'./node_modules/node_modules/lodash/_baseEach.js',
|
||||
'./node_modules/node_modules/lodash/_baseFindIndex.js',
|
||||
'./node_modules/node_modules/lodash/_baseFlatten.js',
|
||||
'./node_modules/node_modules/lodash/_baseFor.js',
|
||||
'./node_modules/node_modules/lodash/_baseForOwn.js',
|
||||
|
|
@ -343,13 +322,11 @@ module.exports = [
|
|||
'./node_modules/node_modules/lodash/_baseGetAllKeys.js',
|
||||
'./node_modules/node_modules/lodash/_baseGetTag.js',
|
||||
'./node_modules/node_modules/lodash/_baseHasIn.js',
|
||||
'./node_modules/node_modules/lodash/_baseIndexOf.js',
|
||||
'./node_modules/node_modules/lodash/_baseIsArguments.js',
|
||||
'./node_modules/node_modules/lodash/_baseIsEqual.js',
|
||||
'./node_modules/node_modules/lodash/_baseIsEqualDeep.js',
|
||||
'./node_modules/node_modules/lodash/_baseIsMap.js',
|
||||
'./node_modules/node_modules/lodash/_baseIsMatch.js',
|
||||
'./node_modules/node_modules/lodash/_baseIsNaN.js',
|
||||
'./node_modules/node_modules/lodash/_baseIsNative.js',
|
||||
'./node_modules/node_modules/lodash/_baseIsSet.js',
|
||||
'./node_modules/node_modules/lodash/_baseIsTypedArray.js',
|
||||
|
|
@ -372,7 +349,6 @@ module.exports = [
|
|||
'./node_modules/node_modules/lodash/_baseTimes.js',
|
||||
'./node_modules/node_modules/lodash/_baseToString.js',
|
||||
'./node_modules/node_modules/lodash/_baseUnary.js',
|
||||
'./node_modules/node_modules/lodash/_baseUniq.js',
|
||||
'./node_modules/node_modules/lodash/_baseUnset.js',
|
||||
'./node_modules/node_modules/lodash/_cacheHas.js',
|
||||
'./node_modules/node_modules/lodash/_castFunction.js',
|
||||
|
|
@ -460,14 +436,12 @@ module.exports = [
|
|||
'./node_modules/node_modules/lodash/_stackGet.js',
|
||||
'./node_modules/node_modules/lodash/_stackHas.js',
|
||||
'./node_modules/node_modules/lodash/_stackSet.js',
|
||||
'./node_modules/node_modules/lodash/_strictIndexOf.js',
|
||||
'./node_modules/node_modules/lodash/_stringToPath.js',
|
||||
'./node_modules/node_modules/lodash/_toKey.js',
|
||||
'./node_modules/node_modules/lodash/_toSource.js',
|
||||
'./node_modules/node_modules/lodash/clone.js',
|
||||
'./node_modules/node_modules/lodash/cloneDeep.js',
|
||||
'./node_modules/node_modules/lodash/constant.js',
|
||||
'./node_modules/node_modules/lodash/difference.js',
|
||||
'./node_modules/node_modules/lodash/endsWith.js',
|
||||
'./node_modules/node_modules/lodash/eq.js',
|
||||
'./node_modules/node_modules/lodash/flatten.js',
|
||||
|
|
@ -497,7 +471,6 @@ module.exports = [
|
|||
'./node_modules/node_modules/lodash/last.js',
|
||||
'./node_modules/node_modules/lodash/lodash.js',
|
||||
'./node_modules/node_modules/lodash/map.js',
|
||||
'./node_modules/node_modules/lodash/mapValues.js',
|
||||
'./node_modules/node_modules/lodash/memoize.js',
|
||||
'./node_modules/node_modules/lodash/merge.js',
|
||||
'./node_modules/node_modules/lodash/noop.js',
|
||||
|
|
@ -512,14 +485,12 @@ module.exports = [
|
|||
'./node_modules/node_modules/lodash/toNumber.js',
|
||||
'./node_modules/node_modules/lodash/toPlainObject.js',
|
||||
'./node_modules/node_modules/lodash/toString.js',
|
||||
'./node_modules/node_modules/lodash/union.js',
|
||||
'./node_modules/node_modules/lodash/uniqueId.js',
|
||||
'./node_modules/node_modules/lodash/unset.js',
|
||||
'./node_modules/node_modules/metro/src/lib/bundle-modules/HMRClient.js',
|
||||
'./node_modules/node_modules/moment-timezone/index.js',
|
||||
'./node_modules/node_modules/moment-timezone/moment-timezone.js',
|
||||
'./node_modules/node_modules/moment/moment.js',
|
||||
'./node_modules/node_modules/nanoid/non-secure/index.js',
|
||||
'./node_modules/node_modules/nullthrows/nullthrows.js',
|
||||
'./node_modules/node_modules/object-assign/index.js',
|
||||
'./node_modules/node_modules/promise/setimmediate/core.js',
|
||||
|
|
@ -531,22 +502,15 @@ module.exports = [
|
|||
'./node_modules/node_modules/prop-types/factoryWithTypeCheckers.js',
|
||||
'./node_modules/node_modules/prop-types/index.js',
|
||||
'./node_modules/node_modules/prop-types/lib/ReactPropTypesSecret.js',
|
||||
'./node_modules/node_modules/querystring/decode.js',
|
||||
'./node_modules/node_modules/querystring/encode.js',
|
||||
'./node_modules/node_modules/querystring/index.js',
|
||||
'./node_modules/node_modules/querystringify/index.js',
|
||||
'./node_modules/node_modules/react-intl/lib/index.js',
|
||||
'./node_modules/node_modules/react-intl/locale-data/en.js',
|
||||
'./node_modules/node_modules/react-intl/locale-data/index.js',
|
||||
'./node_modules/node_modules/react-is/cjs/react-is.production.min.js',
|
||||
'./node_modules/node_modules/react-is/cjs/react-is.production.js',
|
||||
'./node_modules/node_modules/react-is/index.js',
|
||||
'./node_modules/node_modules/react-lifecycles-compat/react-lifecycles-compat.cjs.js',
|
||||
'./node_modules/node_modules/react-native-cookies/index.js',
|
||||
'./node_modules/node_modules/react-native-device-info/deviceinfo.js',
|
||||
'./node_modules/node_modules/react-native-gesture-handler/Directions.js',
|
||||
'./node_modules/node_modules/react-native-gesture-handler/DrawerLayout.js',
|
||||
'./node_modules/node_modules/react-native-gesture-handler/GestureButtons.js',
|
||||
'./node_modules/node_modules/react-native-gesture-handler/GestureComponents.js',
|
||||
'./node_modules/node_modules/react-native-gesture-handler/GestureHandler.js',
|
||||
'./node_modules/node_modules/react-native-gesture-handler/GestureHandlerButton.js',
|
||||
'./node_modules/node_modules/react-native-gesture-handler/GestureHandlerPropTypes.js',
|
||||
|
|
@ -556,19 +520,24 @@ module.exports = [
|
|||
'./node_modules/node_modules/react-native-gesture-handler/PlatformConstants.js',
|
||||
'./node_modules/node_modules/react-native-gesture-handler/RNGestureHandlerModule.js',
|
||||
'./node_modules/node_modules/react-native-gesture-handler/State.js',
|
||||
'./node_modules/node_modules/react-native-gesture-handler/Swipeable.js',
|
||||
'./node_modules/node_modules/react-native-gesture-handler/createHandler.js',
|
||||
'./node_modules/node_modules/react-native-gesture-handler/createNativeWrapper.js',
|
||||
'./node_modules/node_modules/react-native-gesture-handler/gestureHandlerRootHOC.js',
|
||||
'./node_modules/node_modules/react-native-gesture-handler/index.js',
|
||||
'./node_modules/node_modules/react-native-gesture-handler/touchables/GenericTouchable.js',
|
||||
'./node_modules/node_modules/react-native-gesture-handler/touchables/TouchableHighlight.js',
|
||||
'./node_modules/node_modules/react-native-gesture-handler/touchables/TouchableNativeFeedback.android.js',
|
||||
'./node_modules/node_modules/react-native-gesture-handler/touchables/TouchableOpacity.js',
|
||||
'./node_modules/node_modules/react-native-gesture-handler/touchables/TouchableWithoutFeedback.js',
|
||||
'./node_modules/node_modules/react-native-gesture-handler/touchables/index.js',
|
||||
'./node_modules/node_modules/react-native-haptic-feedback/index.js',
|
||||
'./node_modules/node_modules/react-native-keychain/index.js',
|
||||
'./node_modules/node_modules/react-native-mmkv-storage/index.js',
|
||||
'./node_modules/node_modules/react-native-mmkv-storage/src/api.js',
|
||||
'./node_modules/node_modules/react-native-mmkv-storage/src/encryption.js',
|
||||
'./node_modules/node_modules/react-native-mmkv-storage/src/indexer/arrays.js',
|
||||
'./node_modules/node_modules/react-native-mmkv-storage/src/indexer/booleans.js',
|
||||
'./node_modules/node_modules/react-native-mmkv-storage/src/indexer/indexer.js',
|
||||
'./node_modules/node_modules/react-native-mmkv-storage/src/indexer/maps.js',
|
||||
'./node_modules/node_modules/react-native-mmkv-storage/src/indexer/numbers.js',
|
||||
'./node_modules/node_modules/react-native-mmkv-storage/src/indexer/strings.js',
|
||||
'./node_modules/node_modules/react-native-mmkv-storage/src/keygen.js',
|
||||
'./node_modules/node_modules/react-native-mmkv-storage/src/loader.js',
|
||||
'./node_modules/node_modules/react-native-mmkv-storage/src/utils.js',
|
||||
'./node_modules/node_modules/react-native-navigation/lib/dist/Navigation.js',
|
||||
'./node_modules/node_modules/react-native-navigation/lib/dist/adapters/AppRegistryService.js',
|
||||
'./node_modules/node_modules/react-native-navigation/lib/dist/adapters/AssetResolver.js',
|
||||
|
|
@ -765,6 +734,7 @@ module.exports = [
|
|||
'./node_modules/node_modules/react-native/Libraries/ReactPrivate/ReactNativePrivateInitializeCore.js',
|
||||
'./node_modules/node_modules/react-native/Libraries/ReactPrivate/ReactNativePrivateInterface.js',
|
||||
'./node_modules/node_modules/react-native/Libraries/Renderer/implementations/ReactNativeRenderer-prod.js',
|
||||
'./node_modules/node_modules/react-native/Libraries/Renderer/shims/NativeMethodsMixin.js',
|
||||
'./node_modules/node_modules/react-native/Libraries/Renderer/shims/ReactNative.js',
|
||||
'./node_modules/node_modules/react-native/Libraries/Renderer/shims/ReactNativeViewConfigRegistry.js',
|
||||
'./node_modules/node_modules/react-native/Libraries/Renderer/shims/createReactNativeComponentClass.js',
|
||||
|
|
@ -852,18 +822,18 @@ module.exports = [
|
|||
'./node_modules/node_modules/redux-action-buffer/index.js',
|
||||
'./node_modules/node_modules/redux-batched-actions/lib/index.js',
|
||||
'./node_modules/node_modules/redux-persist-transform-filter/dist/index.js',
|
||||
'./node_modules/node_modules/redux-persist/lib/autoRehydrate.js',
|
||||
'./node_modules/node_modules/redux-persist/lib/constants.js',
|
||||
'./node_modules/node_modules/redux-persist/lib/createPersistor.js',
|
||||
'./node_modules/node_modules/redux-persist/lib/createMigrate.js',
|
||||
'./node_modules/node_modules/redux-persist/lib/createPersistoid.js',
|
||||
'./node_modules/node_modules/redux-persist/lib/createTransform.js',
|
||||
'./node_modules/node_modules/redux-persist/lib/defaults/asyncLocalStorage.js',
|
||||
'./node_modules/node_modules/redux-persist/lib/getStoredState.js',
|
||||
'./node_modules/node_modules/redux-persist/lib/index.js',
|
||||
'./node_modules/node_modules/redux-persist/lib/persistCombineReducers.js',
|
||||
'./node_modules/node_modules/redux-persist/lib/persistReducer.js',
|
||||
'./node_modules/node_modules/redux-persist/lib/persistStore.js',
|
||||
'./node_modules/node_modules/redux-persist/lib/purgeStoredState.js',
|
||||
'./node_modules/node_modules/redux-persist/lib/utils/isStatePlainEnough.js',
|
||||
'./node_modules/node_modules/redux-persist/lib/utils/setImmediate.js',
|
||||
'./node_modules/node_modules/redux-thunk/lib/index.js',
|
||||
'./node_modules/node_modules/redux-persist/lib/stateReconciler/autoMergeLevel1.js',
|
||||
'./node_modules/node_modules/redux-reset/lib/index.js',
|
||||
'./node_modules/node_modules/redux/lib/redux.js',
|
||||
'./node_modules/node_modules/requires-port/index.js',
|
||||
'./node_modules/node_modules/reselect/lib/index.js',
|
||||
|
|
@ -874,11 +844,16 @@ module.exports = [
|
|||
'./node_modules/node_modules/rn-fetch-blob/ios.js',
|
||||
'./node_modules/node_modules/rn-fetch-blob/json-stream.js',
|
||||
'./node_modules/node_modules/rn-fetch-blob/polyfill/Fetch.js',
|
||||
'./node_modules/node_modules/rn-host-detect/index.js',
|
||||
'./node_modules/node_modules/sc-channel/index.js',
|
||||
'./node_modules/node_modules/sc-errors/decycle.js',
|
||||
'./node_modules/node_modules/sc-errors/index.js',
|
||||
'./node_modules/node_modules/sc-formatter/index.js',
|
||||
'./node_modules/node_modules/rn-fetch-blob/polyfill/File.js',
|
||||
'./node_modules/node_modules/rn-fetch-blob/polyfill/FileReader.js',
|
||||
'./node_modules/node_modules/rn-fetch-blob/polyfill/ProgressEvent.js',
|
||||
'./node_modules/node_modules/rn-fetch-blob/polyfill/XMLHttpRequest.js',
|
||||
'./node_modules/node_modules/rn-fetch-blob/polyfill/XMLHttpRequestEventTarget.js',
|
||||
'./node_modules/node_modules/rn-fetch-blob/polyfill/index.js',
|
||||
'./node_modules/node_modules/rn-fetch-blob/utils/log.js',
|
||||
'./node_modules/node_modules/rn-fetch-blob/utils/unicode.js',
|
||||
'./node_modules/node_modules/rn-fetch-blob/utils/uri.js',
|
||||
'./node_modules/node_modules/rn-fetch-blob/utils/uuid.js',
|
||||
'./node_modules/node_modules/scheduler/cjs/scheduler-tracing.production.min.js',
|
||||
'./node_modules/node_modules/scheduler/cjs/scheduler.production.min.js',
|
||||
'./node_modules/node_modules/scheduler/index.js',
|
||||
|
|
@ -931,7 +906,6 @@ module.exports = [
|
|||
'./node_modules/node_modules/stacktrace-parser/dist/stack-trace-parser.cjs.js',
|
||||
'./node_modules/node_modules/symbol-observable/lib/index.js',
|
||||
'./node_modules/node_modules/symbol-observable/lib/ponyfill.js',
|
||||
'./node_modules/node_modules/tslib/tslib.js',
|
||||
'./node_modules/node_modules/url-parse/index.js',
|
||||
'./node_modules/node_modules/util/support/isBufferBrowser.js',
|
||||
'./node_modules/node_modules/util/support/types.js',
|
||||
|
|
|
|||
47
patches/react-native-mmkv-storage+0.3.1.patch
Normal file
47
patches/react-native-mmkv-storage+0.3.1.patch
Normal 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 = @{
|
||||
14
patches/redux-persist+6.0.0.patch
Normal file
14
patches/redux-persist+6.0.0.patch
Normal 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;
|
||||
}
|
||||
|
|
@ -12,16 +12,17 @@ if [ -n "$jsfiles" ]; then
|
|||
echo "$js"
|
||||
e=$(node_modules/.bin/eslint --quiet --fix $js)
|
||||
if [[ -n "$e" ]]; then
|
||||
echo "$e"
|
||||
echo "ERROR: Check eslint hints."
|
||||
echo "$e"
|
||||
exit 1 # reject
|
||||
fi
|
||||
done
|
||||
echo "Checking TypeScript"
|
||||
tsce=$(npm run tsc)
|
||||
if [[-n "$tsce"]]; then
|
||||
echo "$tsce"
|
||||
|
||||
echo "Checking for TSC"
|
||||
tsc=$(node_modules/.bin/tsc --noEmit)
|
||||
if [[ -n "$tsc" ]]; then
|
||||
echo "ERROR: Check TSC hints."
|
||||
echo "$tsc"
|
||||
exit 1 # reject
|
||||
fi
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -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)}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -35,10 +35,11 @@
|
|||
"@constants/*": ["app/constants/*"],
|
||||
"@constants": ["app/constants/index"],
|
||||
"@i18n": ["app/i18n/index"],
|
||||
"@mm-redux/*": ["app/mm-redux/*"],
|
||||
"@selectors/*": ["app/selectors/*"],
|
||||
"@store/*": ["app/store"],
|
||||
"@telemetry/*": ["/app/telemetry/*"],
|
||||
"@utils/*": ["app/utils/*"],
|
||||
"@mm-redux/*": ["app/mm-redux/*"],
|
||||
"@websocket": ["app/client/websocket"],
|
||||
"*": ["./*", "node_modules/*"]
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue