From 0e2bc65d7033f4e673bb805d26182758c07b42ef Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Thu, 16 Apr 2020 09:02:47 -0400 Subject: [PATCH] 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 --- app/actions/navigation/index.js | 6 +- app/actions/navigation/index.test.js | 20 +- app/actions/views/channel.test.js | 12 +- app/actions/views/post.test.js | 4 +- app/actions/views/root.js | 13 +- app/actions/websocket.test.js | 2 +- app/init/credentials.js | 3 - app/init/emm_provider.js | 22 +- app/init/global_event_handler.js | 50 ++-- app/init/global_event_handler.test.js | 29 +- app/mattermost.js | 53 ++-- app/mm-redux/actions/users.test.js | 2 +- app/mm-redux/index.ts | 3 +- app/mm-redux/store/index.ts | 108 ------- app/mm-redux/store/initial_state.ts | 266 ------------------ app/mm-redux/store/middleware.ts | 34 --- app/mm-redux/store/mmkv_adapter.ts | 142 ---------- app/mm-redux/store/reducer_registry.test.js | 33 --- app/mm-redux/store/reducer_registry.ts | 33 --- app/mm-redux/types/module.d.ts | 5 +- .../push_notifications.android.js | 4 +- .../push_notifications.ios.js | 19 +- .../push_notifications.ios.test.js | 7 +- app/reducers/views/i18n.js | 4 +- app/reducers/views/root.js | 1 + app/selectors/views.js | 2 +- app/store/ephemeral_store.js | 4 +- app/{mm-redux => }/store/helpers.ts | 14 +- app/store/index.js | 10 - app/store/index.ts | 242 ++++++++++++++++ app/{ => store}/initial_state.js | 0 .../{middleware.js => middlewares/helpers.js} | 175 +----------- app/store/middlewares/index.ts | 35 +++ app/store/middlewares/ios_extension.js | 72 +++++ app/store/middlewares/message_retention.js | 84 ++++++ .../{ => middlewares}/middleware.test.js | 8 +- .../middlewares/sentry.js} | 2 +- app/store/middlewares/thunk.js | 39 +++ app/store/mmkv_adapter.ts | 98 +++++++ app/store/store.js | 148 ---------- app/store/store.ts | 17 ++ app/store/thunk.js | 41 --- app/store/utils.js | 14 +- app/store/utils.test.js | 4 +- app/telemetry/telemetry.android.js | 6 +- app/utils/push_notifications.js | 31 +- babel.config.js | 5 +- ios/Mattermost.xcodeproj/project.pbxproj | 201 ------------- ios/MattermostTests/Info.plist | 24 -- ios/MattermostTests/MattermostTests.m | 70 ----- ios/Podfile | 5 - ios/Podfile.lock | 16 +- package-lock.json | 19 +- package.json | 5 +- packager/moduleNames.js | 177 +++++------- packager/modulePaths.js | 182 +++++------- patches/react-native-mmkv-storage+0.3.1.patch | 47 ++++ patches/redux-persist+6.0.0.patch | 14 + share_extension/android/index.js | 34 ++- test/test_store.js | 7 +- tsconfig.json | 3 +- 61 files changed, 1053 insertions(+), 1677 deletions(-) delete mode 100644 app/mm-redux/store/index.ts delete mode 100644 app/mm-redux/store/initial_state.ts delete mode 100644 app/mm-redux/store/middleware.ts delete mode 100644 app/mm-redux/store/mmkv_adapter.ts delete mode 100644 app/mm-redux/store/reducer_registry.test.js delete mode 100644 app/mm-redux/store/reducer_registry.ts rename app/{mm-redux => }/store/helpers.ts (85%) delete mode 100644 app/store/index.js create mode 100644 app/store/index.ts rename app/{ => store}/initial_state.js (100%) rename app/store/{middleware.js => middlewares/helpers.js} (68%) create mode 100644 app/store/middlewares/index.ts create mode 100644 app/store/middlewares/ios_extension.js create mode 100644 app/store/middlewares/message_retention.js rename app/store/{ => middlewares}/middleware.test.js (98%) rename app/{utils/sentry/middleware.js => store/middlewares/sentry.js} (96%) create mode 100644 app/store/middlewares/thunk.js create mode 100644 app/store/mmkv_adapter.ts delete mode 100644 app/store/store.js create mode 100644 app/store/store.ts delete mode 100644 app/store/thunk.js delete mode 100644 ios/MattermostTests/Info.plist delete mode 100644 ios/MattermostTests/MattermostTests.m create mode 100644 patches/react-native-mmkv-storage+0.3.1.patch create mode 100644 patches/redux-persist+6.0.0.patch diff --git a/app/actions/navigation/index.js b/app/actions/navigation/index.js index 6a64c3a3c..d7571c2e5 100644 --- a/app/actions/navigation/index.js +++ b/app/actions/navigation/index.js @@ -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); } diff --git a/app/actions/navigation/index.test.js b/app/actions/navigation/index.test.js index 67ad379a7..ac737cc12 100644 --- a/app/actions/navigation/index.test.js +++ b/app/actions/navigation/index.test.js @@ -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'; diff --git a/app/actions/views/channel.test.js b/app/actions/views/channel.test.js index d084e21af..0ef5ee9ef 100644 --- a/app/actions/views/channel.test.js +++ b/app/actions/views/channel.test.js @@ -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'; diff --git a/app/actions/views/post.test.js b/app/actions/views/post.test.js index 727cf82fc..f2c41c1c0 100644 --- a/app/actions/views/post.test.js +++ b/app/actions/views/post.test.js @@ -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'; diff --git a/app/actions/views/root.js b/app/actions/views/root.js index 2bd227a01..188469b61 100644 --- a/app/actions/views/root.js +++ b/app/actions/views/root.js @@ -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); }; diff --git a/app/actions/websocket.test.js b/app/actions/websocket.test.js index 336da5226..55f331bff 100644 --- a/app/actions/websocket.test.js +++ b/app/actions/websocket.test.js @@ -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; diff --git a/app/init/credentials.js b/app/init/credentials.js index 67569f6d2..309fbf3fc 100644 --- a/app/init/credentials.js +++ b/app/init/credentials.js @@ -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, diff --git a/app/init/emm_provider.js b/app/init/emm_provider.js index 5bf258dfc..89df193bc 100644 --- a/app/init/emm_provider.js +++ b/app/init/emm_provider.js @@ -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)); diff --git a/app/init/global_event_handler.js b/app/init/global_event_handler.js index fc9733ff8..eedfba24b 100644 --- a/app/init/global_event_handler.js +++ b/app/init/global_event_handler.js @@ -29,11 +29,12 @@ import {NavigationTypes, ViewTypes} from '@constants'; import {getTranslations, resetMomentLocale} from '@i18n'; import PushNotifications from 'app/push_notifications'; import {getCurrentLocale} from '@selectors/i18n'; +import initialState from '@store/initial_state'; +import Store from '@store/store'; import {t} from '@utils/i18n'; import {deleteFileCache} from '@utils/file'; import {getDeviceTimezoneAsync} from '@utils/timezone'; -import initialState from 'app/initial_state'; import mattermostBucket from 'app/mattermost_bucket'; import mattermostManaged from 'app/mattermost_managed'; import LocalConfig from 'assets/config'; @@ -66,7 +67,7 @@ class GlobalEventHandler { // Once the app becomes active we check if the device needs to have a passcode set const prompt = emmProvider.inBackgroundSince && authExpired; // if more than 5 minutes have passed prompt for passcode - await emmProvider.handleAuthentication(this.store, prompt); + await emmProvider.handleAuthentication(prompt); } emmProvider.inBackgroundSince = null; /* eslint-disable-line require-atomic-updates */ @@ -75,7 +76,7 @@ class GlobalEventHandler { appInactive = () => { this.turnOffInAppNotificationHandling(); - const {dispatch} = this.store; + const {dispatch} = Store.redux; // When the app is sent to the background we set the time when that happens // and perform a data clean up to improve on performance @@ -85,7 +86,6 @@ class GlobalEventHandler { }; configure = (opts) => { - this.store = opts.store; this.launchApp = opts.launchApp; // onAppStateChange may be called by the AppState listener before we @@ -107,7 +107,7 @@ class GlobalEventHandler { } this.JavascriptAndNativeErrorHandler = require('app/utils/error_handling').default; - this.JavascriptAndNativeErrorHandler.initializeErrorHandling(this.store); + this.JavascriptAndNativeErrorHandler.initializeErrorHandling(Store.redux); mattermostManaged.addEventListener('managedConfigDidChange', this.onManagedConfigurationChange); }; @@ -126,8 +126,8 @@ class GlobalEventHandler { const isActive = appState === 'active'; const isBackground = appState === 'background'; - if (this.store) { - this.store.dispatch(setAppState(isActive)); + if (Store.redux) { + Store.redux.dispatch(setAppState(isActive)); if (isActive && (!emmProvider.enabled || emmProvider.previousAppState === 'background')) { this.appActive(); @@ -142,12 +142,12 @@ class GlobalEventHandler { onDeepLink = (event) => { const {url} = event; if (url) { - this.store.dispatch(setDeepLinkURL(url)); + Store.redux.dispatch(setDeepLinkURL(url)); } }; onManagedConfigurationChange = () => { - emmProvider.handleManagedConfig(this.store, true); + emmProvider.handleManagedConfig(true); }; onServerConfigChanged = (config) => { @@ -155,9 +155,9 @@ class GlobalEventHandler { }; onLogout = async () => { - this.store.dispatch(closeWebSocket(false)); - this.store.dispatch(setServerVersion('')); - this.resetState(); + Store.redux.dispatch(closeWebSocket(false)); + Store.redux.dispatch(setServerVersion('')); + await this.resetState(); removeAppCredentials(); deleteFileCache(); resetMomentLocale(); @@ -190,8 +190,8 @@ class GlobalEventHandler { }; onOrientationChange = (dimensions) => { - if (this.store) { - const {dispatch, getState} = this.store; + if (Store.redux) { + const {dispatch, getState} = Store.redux; const deviceState = getState().device; if (DeviceInfo.isTablet()) { @@ -215,7 +215,7 @@ class GlobalEventHandler { }; onRestartApp = async () => { - const {dispatch, getState} = this.store; + const {dispatch, getState} = Store.redux; const state = getState(); const {currentUserId} = state.entities.users; const user = getUser(state, currentUserId); @@ -236,7 +236,7 @@ class GlobalEventHandler { }; onServerVersionChanged = async (serverVersion) => { - const {dispatch, getState} = this.store; + const {dispatch, getState} = Store.redux; const state = getState(); const match = serverVersion && serverVersion.match(/^[0-9]*.[0-9]*.[0-9]*(-[a-zA-Z0-9.-]*)?/g); const version = match && match[0]; @@ -263,17 +263,17 @@ class GlobalEventHandler { }; onStatusBarHeightChange = (nextStatusBarHeight) => { - this.store.dispatch(setStatusBarHeight(nextStatusBarHeight)); + Store.redux.dispatch(setStatusBarHeight(nextStatusBarHeight)); }; onSwitchToDefaultChannel = (teamId) => { - this.store.dispatch(selectDefaultChannel(teamId)); + Store.redux.dispatch(selectDefaultChannel(teamId)); }; resetState = async () => { try { await AsyncStorage.clear(); - const state = this.store.getState(); + const state = Store.redux.getState(); const newState = { ...initialState, app: { @@ -298,19 +298,23 @@ class GlobalEventHandler { serverUrl: state.views.selectServer.serverUrl, }, }, + _persist: { + rehydrated: true, + }, }; - this.store.dispatch({ + return Store.redux.dispatch({ type: General.OFFLINE_STORE_PURGE, state: newState, }); } catch (e) { // clear error + return e; } } serverUpgradeNeeded = async () => { - const {dispatch} = this.store; + const {dispatch} = Store.redux; dispatch(setServerVersion('')); Client4.serverVersion = ''; @@ -332,7 +336,7 @@ class GlobalEventHandler { handleInAppNotification = (notification) => { const {data} = notification; - const {getState} = this.store; + const {getState} = Store.redux; const state = getState(); const currentChannelId = getCurrentChannelId(state); @@ -348,7 +352,7 @@ class GlobalEventHandler { }; setUserTimezone = async () => { - const {dispatch, getState} = this.store; + const {dispatch, getState} = Store.redux; const state = getState(); const currentUserId = getCurrentUserId(state); diff --git a/app/init/global_event_handler.test.js b/app/init/global_event_handler.test.js index 53c9b6ea7..49047b957 100644 --- a/app/init/global_event_handler.test.js +++ b/app/init/global_event_handler.test.js @@ -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'); diff --git a/app/mattermost.js b/app/mattermost.js index 73c25854b..939d4cc7d 100644 --- a/app/mattermost.js +++ b/app/mattermost.js @@ -7,34 +7,38 @@ import {Provider} from 'react-redux'; import EventEmitter from '@mm-redux/utils/event_emitter'; -import {loadMe} from 'app/actions/views/user'; - -import {resetToChannel, resetToSelectServer} from 'app/actions/navigation'; -import {setDeepLinkURL} from 'app/actions/views/root'; -import {NavigationTypes} from 'app/constants'; -import {getAppCredentials} from 'app/init/credentials'; -import emmProvider from 'app/init/emm_provider'; -import 'app/init/device'; -import 'app/init/fetch'; -import globalEventHandler from 'app/init/global_event_handler'; -import {registerScreens} from 'app/screens'; -import store, {persistor} from 'app/store'; -import {waitForHydration} from 'app/store/utils'; -import EphemeralStore from 'app/store/ephemeral_store'; +import {resetToChannel, resetToSelectServer} from '@actions/navigation'; +import {setDeepLinkURL} from '@actions/views/root'; +import {loadMe, logout} from '@actions/views/user'; import telemetry from 'app/telemetry'; -import {validatePreviousVersion} from 'app/utils/general'; -import pushNotificationsUtils from 'app/utils/push_notifications'; +import {NavigationTypes} from '@constants'; +import {getAppCredentials} from '@init/credentials'; +import emmProvider from '@init/emm_provider'; +import '@init/device'; +import '@init/fetch'; +import globalEventHandler from '@init/global_event_handler'; +import {registerScreens} from '@screens'; +import configureStore from '@store'; +import EphemeralStore from '@store/ephemeral_store'; +import getStorage from '@store/mmkv_adapter'; +import Store from '@store/store'; +import {waitForHydration} from '@store/utils'; +import {validatePreviousVersion} from '@utils/general'; +import pushNotificationsUtils from '@utils/push_notifications'; const init = async () => { const credentials = await getAppCredentials(); + const dt = Date.now(); + const MMKVStorage = await getStorage(); + + const {store} = configureStore(MMKVStorage); if (EphemeralStore.appStarted) { launchApp(credentials); return; } - pushNotificationsUtils.configure(store); + pushNotificationsUtils.configure(); globalEventHandler.configure({ - store, launchApp, }); @@ -52,10 +56,9 @@ const launchApp = (credentials) => { ]); if (credentials) { - waitForHydration(store, () => { - const valid = validatePreviousVersion(store); - if (valid) { - store.dispatch(loadMe()); + waitForHydration(Store.redux, async () => { + if (validatePreviousVersion(Store.redux, EphemeralStore.prevAppVersion)) { + Store.redux.dispatch(loadMe()); resetToChannel({skipMetrics: true}); } }); @@ -68,13 +71,13 @@ const launchApp = (credentials) => { Linking.getInitialURL().then((url) => { if (url) { - store.dispatch(setDeepLinkURL(url)); + Store.redux.dispatch(setDeepLinkURL(url)); } }); }; const launchAppAndAuthenticateIfNeeded = async (credentials) => { - await emmProvider.handleManagedConfig(store); + await emmProvider.handleManagedConfig(); await launchApp(credentials); if (emmProvider.enabled) { @@ -83,7 +86,7 @@ const launchAppAndAuthenticateIfNeeded = async (credentials) => { } if (emmProvider.inAppPinCode) { - await emmProvider.handleAuthentication(store); + await emmProvider.handleAuthentication(); } } }; diff --git a/app/mm-redux/actions/users.test.js b/app/mm-redux/actions/users.test.js index a05e2d5f9..f0a3e9d6d 100644 --- a/app/mm-redux/actions/users.test.js +++ b/app/mm-redux/actions/users.test.js @@ -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}; diff --git a/app/mm-redux/index.ts b/app/mm-redux/index.ts index f0632bcf7..a37ea728a 100644 --- a/app/mm-redux/index.ts +++ b/app/mm-redux/index.ts @@ -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}; diff --git a/app/mm-redux/store/index.ts b/app/mm-redux/store/index.ts deleted file mode 100644 index 5fa3b4f01..000000000 --- a/app/mm-redux/store/index.ts +++ /dev/null @@ -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; - 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}; -} diff --git a/app/mm-redux/store/initial_state.ts b/app/mm-redux/store/initial_state.ts deleted file mode 100644 index b00da8f99..000000000 --- a/app/mm-redux/store/initial_state.ts +++ /dev/null @@ -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; diff --git a/app/mm-redux/store/middleware.ts b/app/mm-redux/store/middleware.ts deleted file mode 100644 index 39990dbc7..000000000 --- a/app/mm-redux/store/middleware.ts +++ /dev/null @@ -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; -} diff --git a/app/mm-redux/store/mmkv_adapter.ts b/app/mm-redux/store/mmkv_adapter.ts deleted file mode 100644 index 2fe360a8e..000000000 --- a/app/mm-redux/store/mmkv_adapter.ts +++ /dev/null @@ -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; - -type MultiGetCallbackFunction = ( - errors: ReadonlyArray | null | undefined, - result: ReadonlyArray | null | undefined, -) => void; - -type MultiRequest = { - keys: ReadonlyArray; - callback: MultiGetCallbackFunction | null | undefined; - keyIndex: number; - resolve: ( - result?: Promise | 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, - _getKeys: [] as Array, - _immediate: null as number | null | undefined, - - getItem: ( - key: string, - callback?: ( - error: Error | null | undefined, - result: string | null, - ) => void | null | undefined, - ): Promise => { - 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 => { - 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 => { - checkValidInput(key); - if (callback) { - callback(null); - } - return MMKV.removeItem(key); - }, - - clear: (callback?: (error: Error | null | undefined) => void | null | undefined): Promise => { - if (callback) { - callback(null); - } - - return MMKV.clearStore(); - }, - - getAllKeys: ( - callback?: ( - error: Error | null | undefined, - keys: ReadOnlyArrayString | null | undefined - ) => void, - ): Promise => { - return new Promise((resolve, reject) => { - MMKV.getKeysAsync().then((keys: Array) => { - if (callback) { - callback(null, keys); - } - resolve(keys); - }).catch((error) => { - if (callback) { - callback(error, null); - } - reject(error); - }); - }); - }, -}; - -export default MMKVStorageAdapter; \ No newline at end of file diff --git a/app/mm-redux/store/reducer_registry.test.js b/app/mm-redux/store/reducer_registry.test.js deleted file mode 100644 index bd8ac9017..000000000 --- a/app/mm-redux/store/reducer_registry.test.js +++ /dev/null @@ -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); - }); -}); - diff --git a/app/mm-redux/store/reducer_registry.ts b/app/mm-redux/store/reducer_registry.ts deleted file mode 100644 index f046e4d07..000000000 --- a/app/mm-redux/store/reducer_registry.ts +++ /dev/null @@ -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 = {}; - - setReducers = (reducers: Dictionary) => { - 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; diff --git a/app/mm-redux/types/module.d.ts b/app/mm-redux/types/module.d.ts index 37097c51a..aa595ea4b 100644 --- a/app/mm-redux/types/module.d.ts +++ b/app/mm-redux/types/module.d.ts @@ -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'; \ No newline at end of file +declare module 'redux-action-buffer'; +declare module 'redux-reset'; \ No newline at end of file diff --git a/app/push_notifications/push_notifications.android.js b/app/push_notifications/push_notifications.android.js index 4502ab50f..ac8109622 100644 --- a/app/push_notifications/push_notifications.android.js +++ b/app/push_notifications/push_notifications.android.js @@ -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); } } diff --git a/app/push_notifications/push_notifications.ios.js b/app/push_notifications/push_notifications.ios.js index fd8d22410..86388c5db 100644 --- a/app/push_notifications/push_notifications.ios.js +++ b/app/push_notifications/push_notifications.ios.js @@ -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; } diff --git a/app/push_notifications/push_notifications.ios.test.js b/app/push_notifications/push_notifications.ios.test.js index 67947a246..4dc219191 100644 --- a/app/push_notifications/push_notifications.ios.test.js +++ b/app/push_notifications/push_notifications.ios.test.js @@ -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'); diff --git a/app/reducers/views/i18n.js b/app/reducers/views/i18n.js index 96f0ec830..2bcd39d22 100644 --- a/app/reducers/views/i18n.js +++ b/app/reducers/views/i18n.js @@ -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; + } } } diff --git a/app/reducers/views/root.js b/app/reducers/views/root.js index 6a45aaa6c..185532725 100644 --- a/app/reducers/views/root.js +++ b/app/reducers/views/root.js @@ -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: diff --git a/app/selectors/views.js b/app/selectors/views.js index 13e393446..ae398d6a0 100644 --- a/app/selectors/views.js +++ b/app/selectors/views.js @@ -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( diff --git a/app/store/ephemeral_store.js b/app/store/ephemeral_store.js index f4aae52c7..8cb405be3 100644 --- a/app/store/ephemeral_store.js +++ b/app/store/ephemeral_store.js @@ -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, diff --git a/app/mm-redux/store/helpers.ts b/app/store/helpers.ts similarity index 85% rename from app/mm-redux/store/helpers.ts rename to app/store/helpers.ts index c578c0094..c4b2c6841 100644 --- a/app/mm-redux/store/helpers.ts +++ b/app/store/helpers.ts @@ -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 = {}; let storeKeys: Array = []; diff --git a/app/store/index.js b/app/store/index.js deleted file mode 100644 index 1d32a81cd..000000000 --- a/app/store/index.js +++ /dev/null @@ -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; diff --git a/app/store/index.ts b/app/store/index.ts new file mode 100644 index 000000000..06ad1a59c --- /dev/null +++ b/app/store/index.ts @@ -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; + 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 = 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 = 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 = { + 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 = 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}; +} diff --git a/app/initial_state.js b/app/store/initial_state.js similarity index 100% rename from app/initial_state.js rename to app/store/initial_state.js diff --git a/app/store/middleware.js b/app/store/middlewares/helpers.js similarity index 68% rename from app/store/middleware.js rename to app/store/middlewares/helpers.js index 0c901f4d1..b762c55c4 100644 --- a/app/store/middleware.js +++ b/app/store/middlewares/helpers.js @@ -1,172 +1,11 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {Platform} from 'react-native'; import DeviceInfo from 'react-native-device-info'; -import {REHYDRATE} from 'redux-persist'; -import {ViewTypes} from 'app/constants'; -import initialState from 'app/initial_state'; -import {throttle} from 'app/utils/general'; +import initialState from '@store/initial_state'; -import mattermostBucket from 'app/mattermost_bucket'; - -import EphemeralStore from './ephemeral_store'; - -import { - captureException, - LOGGER_JAVASCRIPT_WARNING, -} from 'app/utils/sentry'; - -const SAVE_STATE_ACTIONS = [ - 'CONNECTION_CHANGED', - 'DATA_CLEANUP', - 'LOGIN', - 'Offline/STATUS_CHANGED', - 'persist/REHYDRATE', - 'RECEIVED_APP_STATE', - 'WEBSOCKET_CLOSED', - 'WEBSOCKET_SUCCESS', -]; - -export const middlewares = () => { - const middlewareFunctions = [ - messageRetention, - ]; - - if (Platform.OS === 'ios') { - middlewareFunctions.push(saveShareExtensionState); - } - - return middlewareFunctions; -}; - -// This middleware stores key parts of state entities into a file (in the App Group container) on certain actions. -// iOS only. Allows the share extension to work, without having access available to the redux store object. -// Remove this middleware if/when state is moved to a persisted solution. -function saveShareExtensionState(store) { - return (next) => (action) => { - if (SAVE_STATE_ACTIONS.includes(action.type)) { - throttle(saveStateToFile(store)); - } - return next(action); - }; -} - -async function saveStateToFile(store) { - const state = store.getState(); - - if (state.entities) { - const channelsInTeam = {...state.entities.channels.channelsInTeam}; - Object.keys(channelsInTeam).forEach((teamId) => { - channelsInTeam[teamId] = Array.from(channelsInTeam[teamId]); - }); - - const profilesInChannel = {...state.entities.users.profilesInChannel}; - Object.keys(profilesInChannel).forEach((channelId) => { - profilesInChannel[channelId] = Array.from(profilesInChannel[channelId]); - }); - - let url; - if (state.entities.users.currentUserId) { - url = state.entities.general.credentials.url || state.views.selectServer.serverUrl; - } - - const entities = { - ...state.entities, - general: { - ...state.entities.general, - credentials: { - url, - }, - }, - channels: { - ...state.entities.channels, - channelsInTeam, - }, - users: { - ...state.entities.users, - profilesInChannel, - profilesNotInTeam: [], - profilesWithoutTeam: [], - profilesNotInChannel: [], - }, - }; - - mattermostBucket.writeToFile('entities', JSON.stringify(entities)); - } -} - -function messageRetention(store) { - return (next) => (action) => { - if (action.type === REHYDRATE) { - // On first run payload is not set (when installed) - if (!action.payload) { - action.payload = { - app: { - build: DeviceInfo.getBuildNumber(), - version: DeviceInfo.getVersion(), - }, - views: { - root: { - hydrationComplete: true, - }, - }, - }; - } - - const {app} = action.payload; - const {entities, views} = action.payload; - - if (!entities || !views) { - const version = DeviceInfo.getVersion(); - EphemeralStore.prevAppVersion = version; - action.payload = { - ...action.payload, - app: { - build: DeviceInfo.getBuildNumber(), - version, - }, - }; - return next(action); - } - - EphemeralStore.prevAppVersion = app?.version; - if (app?.version !== DeviceInfo.getVersion() || app?.build !== DeviceInfo.getBuildNumber()) { - // When a new version of the app has been detected - return next(resetStateForNewVersion(action)); - } - - // Keep only the last 60 messages for the last 5 viewed channels in each team - // and apply data retention on those posts if applies - try { - action.payload = cleanUpState(action.payload); - } catch (e) { - // Sometimes, the payload is incomplete so log the error to Sentry and skip the cleanup - console.warn(e); // eslint-disable-line no-console - captureException(e, LOGGER_JAVASCRIPT_WARNING, store); - } - - return next(action); - } else if (action.type === ViewTypes.DATA_CLEANUP) { - action.payload = cleanUpState(action.payload, true); - return next(action); - } - - /* Uncomment the following lines to log the actions being dispatched */ - // if (action.type === 'BATCHING_REDUCER.BATCH') { - // action.payload.forEach((p) => { - // console.log('BATCHED ACTIONS', p.type); - // }); - // } else { - // console.log('ACTION', action.type); - // } - - return next(action); - }; -} - -function resetStateForNewVersion(payload) { +export function resetStateForNewVersion(payload) { const lastChannelForTeam = getLastChannelForTeam(payload); let general = initialState.entities.general; @@ -258,6 +97,9 @@ function resetStateForNewVersion(payload) { } const nextState = { + _persist: { + rehydrated: true, + }, app: { build: DeviceInfo.getBuildNumber(), version: DeviceInfo.getVersion(), @@ -295,7 +137,7 @@ function resetStateForNewVersion(payload) { return nextState; } -function getLastChannelForTeam(payload) { +export function getLastChannelForTeam(payload) { if (payload?.views?.team?.lastChannelForTeam) { const lastChannelForTeam = {...payload.views.team.lastChannelForTeam}; const convertLastChannelForTeam = Object.values(lastChannelForTeam).some((value) => !Array.isArray(value)); @@ -444,9 +286,6 @@ export function cleanUpState(payload, keepCurrent = false) { ...resetPayload.views.channel, ...payload.views.channel, }, - root: { - hydrationComplete: true, - }, }, websocket: { lastConnectAt: payload.websocket?.lastConnectAt, @@ -536,4 +375,4 @@ function removePendingPost(pendingPostIds, id) { if (pendingIndex !== -1) { pendingPostIds.splice(pendingIndex, 1); } -} +} \ No newline at end of file diff --git a/app/store/middlewares/index.ts b/app/store/middlewares/index.ts new file mode 100644 index 000000000..01d76d5e6 --- /dev/null +++ b/app/store/middlewares/index.ts @@ -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; +} \ No newline at end of file diff --git a/app/store/middlewares/ios_extension.js b/app/store/middlewares/ios_extension.js new file mode 100644 index 000000000..421723cc0 --- /dev/null +++ b/app/store/middlewares/ios_extension.js @@ -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)); + } +} \ No newline at end of file diff --git a/app/store/middlewares/message_retention.js b/app/store/middlewares/message_retention.js new file mode 100644 index 000000000..567c55583 --- /dev/null +++ b/app/store/middlewares/message_retention.js @@ -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); + }; +} \ No newline at end of file diff --git a/app/store/middleware.test.js b/app/store/middlewares/middleware.test.js similarity index 98% rename from app/store/middleware.test.js rename to app/store/middlewares/middleware.test.js index c02db6a5d..39057c83b 100644 --- a/app/store/middleware.test.js +++ b/app/store/middlewares/middleware.test.js @@ -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 = [ { diff --git a/app/utils/sentry/middleware.js b/app/store/middlewares/sentry.js similarity index 96% rename from app/utils/sentry/middleware.js rename to app/store/middlewares/sentry.js index 3956ede24..d4f21efdb 100644 --- a/app/utils/sentry/middleware.js +++ b/app/store/middlewares/sentry.js @@ -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'); } diff --git a/app/store/middlewares/thunk.js b/app/store/middlewares/thunk.js new file mode 100644 index 000000000..25a132aa9 --- /dev/null +++ b/app/store/middlewares/thunk.js @@ -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); +}; diff --git a/app/store/mmkv_adapter.ts b/app/store/mmkv_adapter.ts new file mode 100644 index 000000000..72a3ff369 --- /dev/null +++ b/app/store/mmkv_adapter.ts @@ -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 => { + 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 => { + 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 => { + checkValidInput(key); + if (callback) { + callback(null); + } + + return MMKV.removeItem(key); + }, + }; +} \ No newline at end of file diff --git a/app/store/store.js b/app/store/store.js deleted file mode 100644 index 9c34b51b6..000000000 --- a/app/store/store.js +++ /dev/null @@ -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); -} diff --git a/app/store/store.ts b/app/store/store.ts new file mode 100644 index 000000000..200f07b72 --- /dev/null +++ b/app/store/store.ts @@ -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(); \ No newline at end of file diff --git a/app/store/thunk.js b/app/store/thunk.js deleted file mode 100644 index 351d305b4..000000000 --- a/app/store/thunk.js +++ /dev/null @@ -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); - }; -} diff --git a/app/store/utils.js b/app/store/utils.js index b36a22dd6..72f3e2012 100644 --- a/app/store/utils.js +++ b/app/store/utils.js @@ -46,14 +46,21 @@ export function transformSet(incoming, setTransforms, toStorage = true) { export function waitForHydration(store, callback) { let executed = false; // this is to prevent a race condition when subcription runs before unsubscribed - if (store.getState().views?.root?.hydrationComplete && !executed) { + let state = store.getState(); + let root = state.views?.root; + let persist = state._persist; //eslint-disable-line no-underscore-dangle + + if (root?.hydrationComplete && !executed) { if (callback && typeof callback === 'function') { executed = true; callback(); } } else { const subscription = () => { - if (store.getState().views?.root?.hydrationComplete && !executed) { + state = store.getState(); + root = state.views?.root; + persist = state._persist; //eslint-disable-line no-underscore-dangle + if (root?.hydrationComplete && !executed) { unsubscribeFromStore(); if (callback && typeof callback === 'function') { executed = true; @@ -106,6 +113,9 @@ export function getStateForReset(initialState, currentState) { hydrationComplete: true, }, }, + _persist: { + rehydrated: true, + }, }); return resetState; diff --git a/app/store/utils.test.js b/app/store/utils.test.js index 6183a44c2..3c1e85a5c 100644 --- a/app/store/utils.test.js +++ b/app/store/utils.test.js @@ -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; diff --git a/app/telemetry/telemetry.android.js b/app/telemetry/telemetry.android.js index 903d6543b..81ae31134 100644 --- a/app/telemetry/telemetry.android.js +++ b/app/telemetry/telemetry.android.js @@ -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; diff --git a/app/utils/push_notifications.js b/app/utils/push_notifications.js index c455161b3..87a465480 100644 --- a/app/utils/push_notifications.js +++ b/app/utils/push_notifications.js @@ -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)); }); diff --git a/babel.config.js b/babel.config.js index 632bec942..a045ed23a 100644 --- a/babel.config.js +++ b/babel.config.js @@ -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', }, }], ], diff --git a/ios/Mattermost.xcodeproj/project.pbxproj b/ios/Mattermost.xcodeproj/project.pbxproj index cc3c29622..ac88a7667 100644 --- a/ios/Mattermost.xcodeproj/project.pbxproj +++ b/ios/Mattermost.xcodeproj/project.pbxproj @@ -7,7 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - 00E356F31AD99517003FC87E /* MattermostTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* MattermostTests.m */; }; 0111A42B7F264BCF8CBDE3ED /* OpenSans-ExtraBoldItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = FBBEC29EE2D3418D9AC33BD5 /* OpenSans-ExtraBoldItalic.ttf */; }; 0C0D24F53F254F75869E5951 /* OpenSans-Italic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 41F3AFE83AAF4B74878AB78A /* OpenSans-Italic.ttf */; }; 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; }; @@ -61,7 +60,6 @@ A08D512E7ADC40CCAD055A9E /* OpenSans-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = BC977883E2624E05975CA65B /* OpenSans-Regular.ttf */; }; AA9605CFDA8E4E7CB8A041BF /* Roboto-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = C5BD64DE829E455A997DCAD5 /* Roboto-Regular.ttf */; }; ABF5F93B1D0A47BAACEAC376 /* Roboto-Light.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 34B20A903038487E8D7DEA1E /* Roboto-Light.ttf */; }; - B9899621C7F80C4B5C51BB8B /* libPods-MattermostTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5BB993883BB9CFFBD2BEB9FC /* libPods-MattermostTests.a */; }; C99BB3F4E4564F01A531FBEA /* Roboto-Medium.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 09FA716C56874BDDA30FE8C4 /* Roboto-Medium.ttf */; }; D719A67137964F08BE47A5FC /* OpenSans-ExtraBold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 3647DF63D6764CF093375861 /* OpenSans-ExtraBold.ttf */; }; DDE492F7425D451884DAA088 /* Roboto-Black.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 6EFF13DD24CE4E26953E598A /* Roboto-Black.ttf */; }; @@ -72,13 +70,6 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - 00E356F41AD99517003FC87E /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 13B07F861A680F5B00A75B9A; - remoteInfo = Mattermost; - }; 7F240A21220D3A2300637665 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */; @@ -149,7 +140,6 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 00E356EE1AD99517003FC87E /* MattermostTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MattermostTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 00E356F21AD99517003FC87E /* MattermostTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MattermostTests.m; sourceTree = ""; }; 031EF04FB2D14EEFAACBAA1A /* Roboto-Italic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Roboto-Italic.ttf"; path = "../assets/fonts/Roboto-Italic.ttf"; sourceTree = ""; }; @@ -165,7 +155,6 @@ 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = Mattermost/main.m; sourceTree = ""; }; 25BF2BACE89201DE6E585B7E /* Pods-Mattermost.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mattermost.release.xcconfig"; path = "Target Support Files/Pods-Mattermost/Pods-Mattermost.release.xcconfig"; sourceTree = ""; }; 263D389521BE459684618177 /* Octicons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Octicons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Octicons.ttf"; sourceTree = ""; }; - 297AAFCCF0BD99FC109DA2BC /* Pods-MattermostTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MattermostTests.release.xcconfig"; path = "Target Support Files/Pods-MattermostTests/Pods-MattermostTests.release.xcconfig"; sourceTree = ""; }; 2CD0ACABF8EE4E1A94982CC8 /* Zocial.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Zocial.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Zocial.ttf"; sourceTree = ""; }; 32AC3D4EA79E44738A6E9766 /* OpenSans-BoldItalic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "OpenSans-BoldItalic.ttf"; path = "../assets/fonts/OpenSans-BoldItalic.ttf"; sourceTree = ""; }; 34B20A903038487E8D7DEA1E /* Roboto-Light.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Roboto-Light.ttf"; path = "../assets/fonts/Roboto-Light.ttf"; sourceTree = ""; }; @@ -189,8 +178,6 @@ 536CC6C123E79287002C478C /* RNNotificationEventHandler+HandleReplyAction.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "RNNotificationEventHandler+HandleReplyAction.m"; path = "Mattermost/RNNotificationEventHandler+HandleReplyAction.m"; sourceTree = ""; }; 536CC6C223E79287002C478C /* RNNotificationEventHandler+HandleReplyAction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "RNNotificationEventHandler+HandleReplyAction.h"; path = "Mattermost/RNNotificationEventHandler+HandleReplyAction.h"; sourceTree = ""; }; 563B800AC53A447FA18F47D3 /* FontAwesome5_Solid.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = FontAwesome5_Solid.ttf; path = "../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Solid.ttf"; sourceTree = ""; }; - 57CB4735B7E57B50D0B50E16 /* Pods-MattermostTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MattermostTests.debug.xcconfig"; path = "Target Support Files/Pods-MattermostTests/Pods-MattermostTests.debug.xcconfig"; sourceTree = ""; }; - 5BB993883BB9CFFBD2BEB9FC /* libPods-MattermostTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-MattermostTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 6561AEAC21CC40B8A72ABB93 /* OpenSans-Light.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "OpenSans-Light.ttf"; path = "../assets/fonts/OpenSans-Light.ttf"; sourceTree = ""; }; 6BAF8296411D4657B5A0E8F8 /* libRNReactNativeDocViewer.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNReactNativeDocViewer.a; sourceTree = ""; }; 6EFF13DD24CE4E26953E598A /* Roboto-Black.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Roboto-Black.ttf"; path = "../assets/fonts/Roboto-Black.ttf"; sourceTree = ""; }; @@ -272,14 +259,6 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - 00E356EB1AD99517003FC87E /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - B9899621C7F80C4B5C51BB8B /* libPods-MattermostTests.a in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; 13B07F8C1A680F5B00A75B9A /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -412,8 +391,6 @@ children = ( EB4F0DF36537B0B21BE962FB /* Pods-Mattermost.debug.xcconfig */, 25BF2BACE89201DE6E585B7E /* Pods-Mattermost.release.xcconfig */, - 57CB4735B7E57B50D0B50E16 /* Pods-MattermostTests.debug.xcconfig */, - 297AAFCCF0BD99FC109DA2BC /* Pods-MattermostTests.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -448,7 +425,6 @@ 7F43D6051F6BF9EB001FC614 /* libPods-Mattermost.a */, 81061F4CBB31484A94D5A8EE /* libz.tbd */, 8DEEFB3ED6175724A2653247 /* libPods-Mattermost.a */, - 5BB993883BB9CFFBD2BEB9FC /* libPods-MattermostTests.a */, ); name = Frameworks; sourceTree = ""; @@ -533,7 +509,6 @@ isa = PBXGroup; children = ( 13B07F961A680F5B00A75B9A /* Mattermost.app */, - 00E356EE1AD99517003FC87E /* MattermostTests.xctest */, 7F240A19220D3A2300637665 /* MattermostShare.appex */, 7F581D32221ED5C60099E66B /* NotificationService.appex */, ); @@ -543,25 +518,6 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 00E356ED1AD99517003FC87E /* MattermostTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "MattermostTests" */; - buildPhases = ( - C540C9BFE580D99F0700EC9D /* [CP] Check Pods Manifest.lock */, - 00E356EA1AD99517003FC87E /* Sources */, - 00E356EB1AD99517003FC87E /* Frameworks */, - 00E356EC1AD99517003FC87E /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 00E356F51AD99517003FC87E /* PBXTargetDependency */, - ); - name = MattermostTests; - productName = MattermostTests; - productReference = 00E356EE1AD99517003FC87E /* MattermostTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; 13B07F861A680F5B00A75B9A /* Mattermost */ = { isa = PBXNativeTarget; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "Mattermost" */; @@ -634,10 +590,6 @@ LastUpgradeCheck = 820; ORGANIZATIONNAME = Facebook; TargetAttributes = { - 00E356ED1AD99517003FC87E = { - CreatedOnToolsVersion = 6.2; - TestTargetID = 13B07F861A680F5B00A75B9A; - }; 13B07F861A680F5B00A75B9A = { DevelopmentTeam = UQ8HT4Q2XM; LastSwiftMigration = 1010; @@ -703,7 +655,6 @@ projectRoot = ""; targets = ( 13B07F861A680F5B00A75B9A /* Mattermost */, - 00E356ED1AD99517003FC87E /* MattermostTests */, 7F240A18220D3A2300637665 /* MattermostShare */, 7F581D31221ED5C60099E66B /* NotificationService */, ); @@ -721,13 +672,6 @@ /* End PBXReferenceProxy section */ /* Begin PBXResourcesBuildPhase section */ - 00E356EC1AD99517003FC87E /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; 13B07F8E1A680F5B00A75B9A /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -820,58 +764,6 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 1D22DC6FC0A37F7F657B8274 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${SRCROOT}/Pods/Target Support Files/Pods-Mattermost/Pods-Mattermost-resources.sh", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/AntDesign.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Entypo.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Feather.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Brands.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Regular.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Solid.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Foundation.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Ionicons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialIcons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Octicons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Zocial.ttf", - "${PODS_ROOT}/../../node_modules/react-native-youtube/assets/YTPlayerView-iframe-player.html", - ); - name = "[CP] Copy Pods Resources"; - outputFileListPaths = ( - ); - outputPaths = ( - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AntDesign.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Entypo.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EvilIcons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Feather.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Brands.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Regular.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Solid.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Foundation.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Ionicons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialCommunityIcons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialIcons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Octicons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SimpleLineIcons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Zocial.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/YTPlayerView-iframe-player.html", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Mattermost/Pods-Mattermost-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; AE4769B235D14E6C9C64EA78 /* Upload Debug Symbols to Sentry */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -886,28 +778,6 @@ shellPath = /bin/sh; shellScript = "./uploadDebugSymbols.sh\n"; }; - C540C9BFE580D99F0700EC9D /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-MattermostTests-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; ED4C644925C525E30315E09E /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -961,14 +831,6 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ - 00E356EA1AD99517003FC87E /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 00E356F31AD99517003FC87E /* MattermostTests.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; 13B07F871A680F5B00A75B9A /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -1015,11 +877,6 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - 00E356F51AD99517003FC87E /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 13B07F861A680F5B00A75B9A /* Mattermost */; - targetProxy = 00E356F41AD99517003FC87E /* PBXContainerItemProxy */; - }; 7F240A22220D3A2300637665 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 7F240A18220D3A2300637665 /* MattermostShare */; @@ -1059,55 +916,6 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ - 00E356F61AD99517003FC87E /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 57CB4735B7E57B50D0B50E16 /* Pods-MattermostTests.debug.xcconfig */; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - BUNDLE_LOADER = "$(TEST_HOST)"; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - HEADER_SEARCH_PATHS = "$(inherited)"; - INFOPLIST_FILE = MattermostTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - LIBRARY_SEARCH_PATHS = "$(inherited)"; - OTHER_LDFLAGS = ( - "$(inherited)", - "-ObjC", - "-lc++", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.mattermost.rnbeta; - PRODUCT_NAME = "$(TARGET_NAME)"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Mattermost.app/Mattermost"; - }; - name = Debug; - }; - 00E356F71AD99517003FC87E /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 297AAFCCF0BD99FC109DA2BC /* Pods-MattermostTests.release.xcconfig */; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - BUNDLE_LOADER = "$(TEST_HOST)"; - COPY_PHASE_STRIP = NO; - HEADER_SEARCH_PATHS = "$(inherited)"; - INFOPLIST_FILE = MattermostTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - LIBRARY_SEARCH_PATHS = "$(inherited)"; - OTHER_LDFLAGS = ( - "$(inherited)", - "-ObjC", - "-lc++", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.mattermost.rnbeta; - PRODUCT_NAME = "$(TARGET_NAME)"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Mattermost.app/Mattermost"; - }; - name = Release; - }; 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = EB4F0DF36537B0B21BE962FB /* Pods-Mattermost.debug.xcconfig */; @@ -1428,15 +1236,6 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "MattermostTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 00E356F61AD99517003FC87E /* Debug */, - 00E356F71AD99517003FC87E /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "Mattermost" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/ios/MattermostTests/Info.plist b/ios/MattermostTests/Info.plist deleted file mode 100644 index d2b50f205..000000000 --- a/ios/MattermostTests/Info.plist +++ /dev/null @@ -1,24 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - BNDL - CFBundleShortVersionString - 1.30.0 - CFBundleSignature - ???? - CFBundleVersion - 282 - - diff --git a/ios/MattermostTests/MattermostTests.m b/ios/MattermostTests/MattermostTests.m deleted file mode 100644 index 32b40bbb4..000000000 --- a/ios/MattermostTests/MattermostTests.m +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -#import -#import - -#import -#import - -#define TIMEOUT_SECONDS 600 -#define TEXT_TO_LOOK_FOR @"Welcome to React Native!" - -@interface MattermostTests : XCTestCase - -@end - -@implementation MattermostTests - -- (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test -{ - if (test(view)) { - return YES; - } - for (UIView *subview in [view subviews]) { - if ([self findSubviewInView:subview matching:test]) { - return YES; - } - } - return NO; -} - -- (void)testRendersWelcomeScreen -{ - UIViewController *vc = [[[[UIApplication sharedApplication] delegate] window] rootViewController]; - NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; - BOOL foundElement = NO; - - __block NSString *redboxError = nil; - RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { - if (level >= RCTLogLevelError) { - redboxError = message; - } - }); - - while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { - [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; - [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; - - foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) { - if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { - return YES; - } - return NO; - }]; - } - - RCTSetLogFunction(RCTDefaultLogFunction); - - XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); - XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); -} - - -@end diff --git a/ios/Podfile b/ios/Podfile index d6209e63f..26341e78e 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -42,10 +42,5 @@ target 'Mattermost' do pod 'XCDYouTubeKit', '2.8.2' pod 'Swime', '3.0.6' - target 'MattermostTests' do - inherit! :search_paths - # Pods for testing - end - use_native_modules! end diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 968b1f9d4..e1fc468d6 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -32,7 +32,9 @@ PODS: - libwebp/mux (1.1.0): - libwebp/demux - libwebp/webp (1.1.0) - - MMKV (1.0.24) + - MMKV (1.1.0): + - MMKVCore (~> 1.1.0) + - MMKVCore (1.1.0) - Permission-Camera (2.0.10): - RNPermissions - Permission-PhotoLibrary (2.0.10): @@ -213,8 +215,8 @@ PODS: - React - react-native-image-picker (2.3.1): - React - - react-native-mmkv-storage (0.2.2): - - MMKV (= 1.0.24) + - react-native-mmkv-storage (0.3.1): + - MMKV (= 1.1.0) - React - react-native-netinfo (4.4.0): - React @@ -384,6 +386,7 @@ SPEC REPOS: - boost-for-react-native - libwebp - MMKV + - MMKVCore - SDWebImage - SDWebImageWebPCoder - Sentry @@ -517,7 +520,8 @@ SPEC CHECKSUMS: glog: 1f3da668190260b06b429bb211bfbee5cd790c28 jail-monkey: d7c5048b2336f22ee9c9e0efa145f1f917338ea9 libwebp: 946cb3063cea9236285f7e9a8505d806d30e07f3 - MMKV: 758b2edee46b08bdd958db4169191afb9a6d4ebd + MMKV: 7bb6c30f9ff2ea45bc2398c86e66dd1cb63cfe20 + MMKVCore: f1e0aad4fc330e7cbfbdff16720017bf0973db5a Permission-Camera: 8f0e5decca5f28f70f28a8dc31f012c9bad40ad8 Permission-PhotoLibrary: b209bf23b784c9e1409a57d81c6d11ab1d3079c1 RCTRequired: b153add4da6e7dbc44aebf93f3cf4fcae392ddf1 @@ -535,7 +539,7 @@ SPEC CHECKSUMS: react-native-document-picker: 0573c02d742d4bef38a5d16b5f039754cfa69888 react-native-hw-keyboard-event: b517cefb8d5c659a38049c582de85ff43337dc53 react-native-image-picker: 668e72d0277dc8c12ae90e835507c1eddd2e4f85 - react-native-mmkv-storage: 84162ebe353ecf7476d235c47becade29789ae2c + react-native-mmkv-storage: d413e1e00b1f410a744e1d547a912a1f87e67260 react-native-netinfo: 892a5130be97ff8bb69c523739c424a2ffc296d1 react-native-notifications: d5cb54ef8bf3004dcb56c887650dea08ecbddee7 react-native-passcode-status: 88c4f6e074328bc278bd127646b6c694ad5a530a @@ -575,6 +579,6 @@ SPEC CHECKSUMS: Yoga: f2a7cd4280bfe2cca5a7aed98ba0eb3d1310f18b YoutubePlayer-in-WKWebView: af2f5929fc78882d94bfdfeea999b661b78d9717 -PODFILE CHECKSUM: 8199a7b8e5d4cc8c741ea6292c067c33ad835da9 +PODFILE CHECKSUM: 77806e4d5b8185ba5cb04fc8d698bcc9123cb578 COCOAPODS: 1.7.5 diff --git a/package-lock.json b/package-lock.json index 1b90efd6c..280e2b63b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4855,6 +4855,11 @@ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.8.24.tgz", "integrity": "sha512-bImQZbBv86zcOWOq6fLg7r4aqMx8fScdmykA7cSh+gH1Yh8AM0Dbw0gHYrsOrza6oBBnkK+/OaR+UAa9UsMrDw==" }, + "debounce": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.0.tgz", + "integrity": "sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg==" + }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -15241,9 +15246,9 @@ "integrity": "sha512-36cYGZGCG82pMiVJbQa5WMA93khP4v5JqLutFkMyB/eRpCULHmojNIBlbUPIY9SCeN4sg5VBRFTVGCtTg2r2kA==" }, "react-native-mmkv-storage": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/react-native-mmkv-storage/-/react-native-mmkv-storage-0.2.2.tgz", - "integrity": "sha512-QBMQzz7wpS2DGjOn5kIppqiD26p+jOJaJ0Am4DG2PImcUGe9iub5mkamz02mlqjyhq+8EhUes533uLK98APtmw==" + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/react-native-mmkv-storage/-/react-native-mmkv-storage-0.3.1.tgz", + "integrity": "sha512-rNFgtgzvCsDsXU5MnKz5PA5GMkq8TyyixnGSXBZ3tIatwN/3Lq09OJA0YR7UEwsQOuIcnPsEtGEf7suBHo/SwA==" }, "react-native-navigation": { "version": "6.3.0", @@ -15330,6 +15335,14 @@ "hoist-non-react-statics": "^2.3.1" } }, + "react-native-screens": { + "version": "1.0.0-alpha.23", + "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-1.0.0-alpha.23.tgz", + "integrity": "sha512-tOxHGQUN83MTmQB4ghoQkibqOdGiX4JQEmeyEv96MKWO/x8T2PJv84ECUos9hD3blPRQwVwSpAid1PPPhrVEaw==", + "requires": { + "debounce": "^1.2.0" + } + }, "react-native-section-list-get-item-layout": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/react-native-section-list-get-item-layout/-/react-native-section-list-get-item-layout-2.2.3.tgz", diff --git a/package.json b/package.json index 62a351611..cd1b99ff5 100644 --- a/package.json +++ b/package.json @@ -50,12 +50,13 @@ "react-native-keychain": "4.0.5", "react-native-linear-gradient": "2.5.6", "react-native-local-auth": "1.6.0", - "react-native-mmkv-storage": "0.2.2", + "react-native-mmkv-storage": "0.3.1", "react-native-navigation": "6.3.0", "react-native-notifications": "2.0.6", "react-native-passcode-status": "1.1.2", "react-native-permissions": "2.0.10", "react-native-safe-area": "0.5.1", + "react-native-screens": "1.0.0-alpha.23", "react-native-section-list-get-item-layout": "2.2.3", "react-native-slider": "0.11.0", "react-native-status-bar-size": "0.3.3", @@ -143,7 +144,7 @@ "check": "eslint --ignore-path .gitignore --ignore-pattern node_modules --quiet . && npm run tsc", "fix": "eslint --ignore-path .gitignore --ignore-pattern node_modules --quiet . --fix", "postinstall": "make post-install", - "tsc": "tsc --noEmit --skipLibCheck", + "tsc": "NODE_OPTIONS=--max_old_space_size=12000 tsc --noEmit", "test": "jest --forceExit --detectOpenHandles", "test:watch": "jest --watch", "test:coverage": "jest --coverage", diff --git a/packager/moduleNames.js b/packager/moduleNames.js index 277d28d06..002c28f53 100644 --- a/packager/moduleNames.js +++ b/packager/moduleNames.js @@ -6,8 +6,8 @@ module.exports = [ 'app/actions/helpers/channels.ts', 'app/actions/navigation/index.js', 'app/actions/views/channel.js', + 'app/actions/views/emoji.js', 'app/actions/views/post.js', - 'app/actions/views/login.js', 'app/actions/views/root.js', 'app/actions/views/select_server.js', 'app/actions/views/user.js', @@ -27,62 +27,26 @@ module.exports = [ 'app/init/emm_provider.js', 'app/init/fetch.js', 'app/init/global_event_handler.js', - 'app/initial_state.js', 'app/mattermost.js', 'app/mattermost_bucket/index.js', 'app/mattermost_managed/index.js', 'app/mattermost_managed/mattermost-managed.android.js', - 'app/push_notifications/index.js', - 'app/push_notifications/push_notifications.android.js', - 'app/reducers/app/build.js', - 'app/reducers/app/index.js', - 'app/reducers/app/version.js', - 'app/reducers/device/connection.js', - 'app/reducers/device/dimension.js', - 'app/reducers/device/index.js', - 'app/reducers/device/is_tablet.js', - 'app/reducers/device/orientation.js', - 'app/reducers/device/status_bar.js', - 'app/reducers/index.js', - 'app/reducers/navigation/index.js', - 'app/reducers/views/announcement.js', - 'app/reducers/views/channel.js', - 'app/reducers/views/client_upgrade.js', - 'app/reducers/views/emoji.js', - 'app/reducers/views/extension.js', - 'app/reducers/views/i18n.js', - 'app/reducers/views/index.js', - 'app/reducers/views/post.js', - 'app/reducers/views/recent_emojis.js', - 'app/reducers/views/root.js', - 'app/reducers/views/search.js', - 'app/reducers/views/select_server.js', - 'app/reducers/views/team.js', - 'app/reducers/views/thread.js', - 'app/reducers/views/user.js', 'app/mm-redux/action_types/bots.ts', 'app/mm-redux/action_types/channels.ts', 'app/mm-redux/action_types/emojis.ts', 'app/mm-redux/action_types/errors.ts', 'app/mm-redux/action_types/files.ts', 'app/mm-redux/action_types/general.ts', - 'app/mm-redux/action_types/gifs.ts', 'app/mm-redux/action_types/groups.ts', 'app/mm-redux/action_types/index.ts', - 'app/mm-redux/action_types/integrations.ts', - 'app/mm-redux/action_types/jobs.ts', - 'app/mm-redux/action_types/plugins.ts', 'app/mm-redux/action_types/posts.ts', 'app/mm-redux/action_types/preferences.ts', 'app/mm-redux/action_types/roles.ts', 'app/mm-redux/action_types/schemes.ts', - 'app/mm-redux/action_types/search.ts', 'app/mm-redux/action_types/teams.ts', 'app/mm-redux/action_types/users.ts', 'app/mm-redux/actions/channels.ts', 'app/mm-redux/actions/emojis.ts', - 'app/mm-redux/actions/errors.ts', - 'app/mm-redux/actions/files.ts', 'app/mm-redux/actions/general.ts', 'app/mm-redux/actions/helpers.ts', 'app/mm-redux/actions/posts.ts', @@ -95,19 +59,19 @@ module.exports = [ 'app/mm-redux/client/fetch_etag.ts', 'app/mm-redux/client/index.ts', 'app/mm-redux/constants/emoji.ts', - 'app/mm-redux/constants/files.ts', 'app/mm-redux/constants/general.ts', 'app/mm-redux/constants/groups.ts', 'app/mm-redux/constants/index.ts', 'app/mm-redux/constants/permissions.ts', - 'app/mm-redux/constants/plugins.ts', 'app/mm-redux/constants/posts.ts', 'app/mm-redux/constants/preferences.ts', 'app/mm-redux/constants/request_status.ts', + 'app/mm-redux/constants/roles.ts', 'app/mm-redux/constants/stats.ts', 'app/mm-redux/constants/teams.ts', 'app/mm-redux/constants/users.ts', 'app/mm-redux/reducers/entities/bots.ts', + 'app/mm-redux/reducers/entities/channel_categories.ts', 'app/mm-redux/reducers/entities/channels.ts', 'app/mm-redux/reducers/entities/emojis.ts', 'app/mm-redux/reducers/entities/files.ts', @@ -151,10 +115,6 @@ module.exports = [ 'app/mm-redux/selectors/entities/teams.ts', 'app/mm-redux/selectors/entities/timezone.ts', 'app/mm-redux/selectors/entities/users.ts', - 'app/mm-redux/store/helpers.ts', - 'app/mm-redux/store/index.ts', - 'app/mm-redux/store/initial_state.ts', - 'app/mm-redux/store/middleware.ts', 'app/mm-redux/types/actions.ts', 'app/mm-redux/utils/channel_utils.ts', 'app/mm-redux/utils/emoji_utils.ts', @@ -163,19 +123,54 @@ module.exports = [ 'app/mm-redux/utils/helpers.ts', 'app/mm-redux/utils/i18n_utils.ts', 'app/mm-redux/utils/key_mirror.ts', + 'app/mm-redux/utils/post_list.ts', 'app/mm-redux/utils/post_utils.ts', 'app/mm-redux/utils/preference_utils.ts', 'app/mm-redux/utils/sentry.ts', 'app/mm-redux/utils/team_utils.ts', + 'app/mm-redux/utils/timezone_utils.ts', 'app/mm-redux/utils/user_utils.ts', + 'app/push_notifications/index.js', + 'app/push_notifications/push_notifications.android.js', + 'app/reducers/app/build.js', + 'app/reducers/app/index.js', + 'app/reducers/app/version.js', + 'app/reducers/device/connection.js', + 'app/reducers/device/dimension.js', + 'app/reducers/device/index.js', + 'app/reducers/device/is_tablet.js', + 'app/reducers/device/orientation.js', + 'app/reducers/device/status_bar.js', + 'app/reducers/index.js', + 'app/reducers/views/announcement.js', + 'app/reducers/views/channel.js', + 'app/reducers/views/client_upgrade.js', + 'app/reducers/views/emoji.js', + 'app/reducers/views/extension.js', + 'app/reducers/views/i18n.js', + 'app/reducers/views/index.js', + 'app/reducers/views/post.js', + 'app/reducers/views/recent_emojis.js', + 'app/reducers/views/root.js', + 'app/reducers/views/search.js', + 'app/reducers/views/select_server.js', + 'app/reducers/views/team.js', + 'app/reducers/views/thread.js', + 'app/reducers/views/user.js', 'app/screens/index.js', 'app/selectors/channel.js', 'app/selectors/i18n.js', 'app/store/ephemeral_store.js', - 'app/store/index.js', - 'app/store/middleware.js', - 'app/store/store.js', - 'app/store/thunk.js', + 'app/store/helpers.ts', + 'app/store/index.ts', + 'app/store/initial_state.js', + 'app/store/middlewares/helpers.js', + 'app/store/middlewares/index.ts', + 'app/store/middlewares/message_retention.js', + 'app/store/middlewares/sentry.js', + 'app/store/middlewares/thunk.js', + 'app/store/mmkv_adapter.ts', + 'app/store/store.ts', 'app/store/utils.js', 'app/telemetry/index.js', 'app/telemetry/telemetry.android.js', @@ -184,13 +179,11 @@ module.exports = [ 'app/utils/file.js', 'app/utils/general.js', 'app/utils/i18n.js', - 'app/utils/image_cache_manager.js', 'app/utils/preferences.js', 'app/utils/push_notifications.js', 'app/utils/security.js', 'app/utils/segment.js', 'app/utils/sentry/index.js', - 'app/utils/sentry/middleware.js', 'app/utils/time_tracker.js', 'app/utils/timezone.js', 'dist/assets/config.json', @@ -223,6 +216,7 @@ module.exports = [ 'node_modules/@babel/runtime/helpers/toConsumableArray.js', 'node_modules/@babel/runtime/helpers/typeof.js', 'node_modules/@babel/runtime/helpers/wrapNativeSuper.js', + 'node_modules/@babel/runtime/node_modules/regenerator-runtime/runtime.js', 'node_modules/@babel/runtime/regenerator/index.js', 'node_modules/@react-native-community/async-storage/lib/AsyncStorage.js', 'node_modules/@react-native-community/async-storage/lib/hooks.js', @@ -258,10 +252,8 @@ module.exports = [ 'node_modules/fbjs/lib/performance.js', 'node_modules/fbjs/lib/performanceNow.js', 'node_modules/fbjs/lib/warning.js', - 'node_modules/get-params/index.js', 'node_modules/harmony-reflect/reflect.js', 'node_modules/hoist-non-react-statics/dist/hoist-non-react-statics.cjs.js', - 'node_modules/ieee754/index.js', 'node_modules/inherits/inherits_browser.js', 'node_modules/intl-format-cache/dist/index.js', 'node_modules/intl-format-cache/index.js', @@ -285,16 +277,9 @@ module.exports = [ 'node_modules/intl/index.js', 'node_modules/intl/lib/core.js', 'node_modules/intl/locale-data/complete.js', + 'node_modules/invariant/browser.js', 'node_modules/is-arguments/index.js', 'node_modules/is-generator-function/index.js', - 'node_modules/jsan/index.js', - 'node_modules/jsan/lib/cycle.js', - 'node_modules/jsan/lib/index.js', - 'node_modules/jsan/lib/path-getter.js', - 'node_modules/jsan/lib/utils.js', - 'node_modules/json-stringify-safe/stringify.js', - 'node_modules/linked-list/_source/linked-list.js', - 'node_modules/linked-list/index.js', 'node_modules/lodash.clonedeep/index.js', 'node_modules/lodash.forin/index.js', 'node_modules/lodash.get/index.js', @@ -317,8 +302,6 @@ module.exports = [ 'node_modules/lodash/_apply.js', 'node_modules/lodash/_arrayEach.js', 'node_modules/lodash/_arrayFilter.js', - 'node_modules/lodash/_arrayIncludes.js', - 'node_modules/lodash/_arrayIncludesWith.js', 'node_modules/lodash/_arrayLikeKeys.js', 'node_modules/lodash/_arrayMap.js', 'node_modules/lodash/_arrayPush.js', @@ -332,9 +315,7 @@ module.exports = [ 'node_modules/lodash/_baseClamp.js', 'node_modules/lodash/_baseClone.js', 'node_modules/lodash/_baseCreate.js', - 'node_modules/lodash/_baseDifference.js', 'node_modules/lodash/_baseEach.js', - 'node_modules/lodash/_baseFindIndex.js', 'node_modules/lodash/_baseFlatten.js', 'node_modules/lodash/_baseFor.js', 'node_modules/lodash/_baseForOwn.js', @@ -342,13 +323,11 @@ module.exports = [ 'node_modules/lodash/_baseGetAllKeys.js', 'node_modules/lodash/_baseGetTag.js', 'node_modules/lodash/_baseHasIn.js', - 'node_modules/lodash/_baseIndexOf.js', 'node_modules/lodash/_baseIsArguments.js', 'node_modules/lodash/_baseIsEqual.js', 'node_modules/lodash/_baseIsEqualDeep.js', 'node_modules/lodash/_baseIsMap.js', 'node_modules/lodash/_baseIsMatch.js', - 'node_modules/lodash/_baseIsNaN.js', 'node_modules/lodash/_baseIsNative.js', 'node_modules/lodash/_baseIsSet.js', 'node_modules/lodash/_baseIsTypedArray.js', @@ -368,7 +347,6 @@ module.exports = [ 'node_modules/lodash/_baseTimes.js', 'node_modules/lodash/_baseToString.js', 'node_modules/lodash/_baseUnary.js', - 'node_modules/lodash/_baseUniq.js', 'node_modules/lodash/_baseUnset.js', 'node_modules/lodash/_cacheHas.js', 'node_modules/lodash/_castFunction.js', @@ -454,14 +432,12 @@ module.exports = [ 'node_modules/lodash/_stackGet.js', 'node_modules/lodash/_stackHas.js', 'node_modules/lodash/_stackSet.js', - 'node_modules/lodash/_strictIndexOf.js', 'node_modules/lodash/_stringToPath.js', 'node_modules/lodash/_toKey.js', 'node_modules/lodash/_toSource.js', 'node_modules/lodash/clone.js', 'node_modules/lodash/cloneDeep.js', 'node_modules/lodash/constant.js', - 'node_modules/lodash/difference.js', 'node_modules/lodash/endsWith.js', 'node_modules/lodash/eq.js', 'node_modules/lodash/forEach.js', @@ -490,7 +466,6 @@ module.exports = [ 'node_modules/lodash/last.js', 'node_modules/lodash/lodash.js', 'node_modules/lodash/map.js', - 'node_modules/lodash/mapValues.js', 'node_modules/lodash/memoize.js', 'node_modules/lodash/merge.js', 'node_modules/lodash/noop.js', @@ -502,7 +477,6 @@ module.exports = [ 'node_modules/lodash/toNumber.js', 'node_modules/lodash/toPlainObject.js', 'node_modules/lodash/toString.js', - 'node_modules/lodash/union.js', 'node_modules/lodash/uniqueId.js', 'node_modules/lodash/unset.js', 'node_modules/metro/src/lib/bundle-modules/HMRClient.js', @@ -512,7 +486,6 @@ module.exports = [ 'node_modules/moment-timezone/index.js', 'node_modules/moment-timezone/moment-timezone.js', 'node_modules/moment/moment.js', - 'node_modules/nanoid/non-secure/index.js', 'node_modules/nullthrows/nullthrows.js', 'node_modules/object-assign/index.js', 'node_modules/promise/setimmediate/core.js', @@ -524,9 +497,6 @@ module.exports = [ 'node_modules/prop-types/factoryWithTypeCheckers.js', 'node_modules/prop-types/index.js', 'node_modules/prop-types/lib/ReactPropTypesSecret.js', - 'node_modules/querystring/decode.js', - 'node_modules/querystring/encode.js', - 'node_modules/querystring/index.js', 'node_modules/querystringify/index.js', 'node_modules/react-intl/lib/index.js', 'node_modules/react-intl/locale-data/en.js', @@ -536,10 +506,6 @@ module.exports = [ 'node_modules/react-lifecycles-compat/react-lifecycles-compat.cjs.js', 'node_modules/react-native-cookies/index.js', 'node_modules/react-native-device-info/deviceinfo.js', - 'node_modules/react-native-gesture-handler/Directions.js', - 'node_modules/react-native-gesture-handler/DrawerLayout.js', - 'node_modules/react-native-gesture-handler/GestureButtons.js', - 'node_modules/react-native-gesture-handler/GestureComponents.js', 'node_modules/react-native-gesture-handler/GestureHandler.js', 'node_modules/react-native-gesture-handler/GestureHandlerButton.js', 'node_modules/react-native-gesture-handler/GestureHandlerPropTypes.js', @@ -549,19 +515,24 @@ module.exports = [ 'node_modules/react-native-gesture-handler/PlatformConstants.js', 'node_modules/react-native-gesture-handler/RNGestureHandlerModule.js', 'node_modules/react-native-gesture-handler/State.js', - 'node_modules/react-native-gesture-handler/Swipeable.js', 'node_modules/react-native-gesture-handler/createHandler.js', 'node_modules/react-native-gesture-handler/createNativeWrapper.js', 'node_modules/react-native-gesture-handler/gestureHandlerRootHOC.js', 'node_modules/react-native-gesture-handler/index.js', - 'node_modules/react-native-gesture-handler/touchables/GenericTouchable.js', - 'node_modules/react-native-gesture-handler/touchables/TouchableHighlight.js', - 'node_modules/react-native-gesture-handler/touchables/TouchableNativeFeedback.android.js', - 'node_modules/react-native-gesture-handler/touchables/TouchableOpacity.js', - 'node_modules/react-native-gesture-handler/touchables/TouchableWithoutFeedback.js', - 'node_modules/react-native-gesture-handler/touchables/index.js', 'node_modules/react-native-haptic-feedback/index.js', 'node_modules/react-native-keychain/index.js', + 'node_modules/react-native-mmkv-storage/index.js', + 'node_modules/react-native-mmkv-storage/src/api.js', + 'node_modules/react-native-mmkv-storage/src/encryption.js', + 'node_modules/react-native-mmkv-storage/src/indexer/arrays.js', + 'node_modules/react-native-mmkv-storage/src/indexer/booleans.js', + 'node_modules/react-native-mmkv-storage/src/indexer/indexer.js', + 'node_modules/react-native-mmkv-storage/src/indexer/maps.js', + 'node_modules/react-native-mmkv-storage/src/indexer/numbers.js', + 'node_modules/react-native-mmkv-storage/src/indexer/strings.js', + 'node_modules/react-native-mmkv-storage/src/keygen.js', + 'node_modules/react-native-mmkv-storage/src/loader.js', + 'node_modules/react-native-mmkv-storage/src/utils.js', 'node_modules/react-native-navigation/lib/dist/Navigation.js', 'node_modules/react-native-navigation/lib/dist/adapters/AppRegistryService.js', 'node_modules/react-native-navigation/lib/dist/adapters/AssetResolver.js', @@ -829,25 +800,25 @@ module.exports = [ 'node_modules/react-redux/lib/utils/verifyPlainObject.js', 'node_modules/react-redux/lib/utils/warning.js', 'node_modules/react-redux/node_modules/hoist-non-react-statics/dist/hoist-non-react-statics.cjs.js', - 'node_modules/react-refresh/cjs/react-refresh-runtime.production.js', + 'node_modules/react-refresh/cjs/react-refresh-runtime.production.min.js', 'node_modules/react-refresh/runtime.js', - 'node_modules/react/cjs/react.production.js', + 'node_modules/react/cjs/react.production.min.js', 'node_modules/react/index.js', 'node_modules/redux-action-buffer/index.js', 'node_modules/redux-batched-actions/lib/index.js', 'node_modules/redux-persist-transform-filter/dist/index.js', - 'node_modules/redux-persist/lib/autoRehydrate.js', 'node_modules/redux-persist/lib/constants.js', - 'node_modules/redux-persist/lib/createPersistor.js', + 'node_modules/redux-persist/lib/createMigrate.js', + 'node_modules/redux-persist/lib/createPersistoid.js', 'node_modules/redux-persist/lib/createTransform.js', - 'node_modules/redux-persist/lib/defaults/asyncLocalStorage.js', 'node_modules/redux-persist/lib/getStoredState.js', 'node_modules/redux-persist/lib/index.js', + 'node_modules/redux-persist/lib/persistCombineReducers.js', + 'node_modules/redux-persist/lib/persistReducer.js', 'node_modules/redux-persist/lib/persistStore.js', 'node_modules/redux-persist/lib/purgeStoredState.js', - 'node_modules/redux-persist/lib/utils/isStatePlainEnough.js', - 'node_modules/redux-persist/lib/utils/setImmediate.js', - 'node_modules/redux-thunk/lib/index.js', + 'node_modules/redux-persist/lib/stateReconciler/autoMergeLevel1.js', + 'node_modules/redux-reset/lib/index.js', 'node_modules/redux/lib/redux.js', 'node_modules/requires-port/index.js', 'node_modules/reselect/lib/index.js', @@ -858,13 +829,18 @@ module.exports = [ 'node_modules/rn-fetch-blob/ios.js', 'node_modules/rn-fetch-blob/json-stream.js', 'node_modules/rn-fetch-blob/polyfill/Fetch.js', - 'node_modules/rn-host-detect/index.js', - 'node_modules/sc-channel/index.js', - 'node_modules/sc-errors/decycle.js', - 'node_modules/sc-errors/index.js', - 'node_modules/sc-formatter/index.js', - 'node_modules/scheduler/cjs/scheduler-tracing.production.js', - 'node_modules/scheduler/cjs/scheduler.production.js', + 'node_modules/rn-fetch-blob/polyfill/File.js', + 'node_modules/rn-fetch-blob/polyfill/FileReader.js', + 'node_modules/rn-fetch-blob/polyfill/ProgressEvent.js', + 'node_modules/rn-fetch-blob/polyfill/XMLHttpRequest.js', + 'node_modules/rn-fetch-blob/polyfill/XMLHttpRequestEventTarget.js', + 'node_modules/rn-fetch-blob/polyfill/index.js', + 'node_modules/rn-fetch-blob/utils/log.js', + 'node_modules/rn-fetch-blob/utils/unicode.js', + 'node_modules/rn-fetch-blob/utils/uri.js', + 'node_modules/rn-fetch-blob/utils/uuid.js', + 'node_modules/scheduler/cjs/scheduler-tracing.production.min.js', + 'node_modules/scheduler/cjs/scheduler.production.min.js', 'node_modules/scheduler/index.js', 'node_modules/scheduler/tracing.js', 'node_modules/semver/classes/comparator.js', @@ -914,7 +890,6 @@ module.exports = [ 'node_modules/stacktrace-parser/dist/stack-trace-parser.cjs.js', 'node_modules/symbol-observable/lib/index.js', 'node_modules/symbol-observable/lib/ponyfill.js', - 'node_modules/tslib/tslib.js', 'node_modules/url-parse/index.js', 'node_modules/util/support/isBufferBrowser.js', 'node_modules/util/support/types.js', diff --git a/packager/modulePaths.js b/packager/modulePaths.js index 554863609..87c5a74f3 100644 --- a/packager/modulePaths.js +++ b/packager/modulePaths.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', @@ -26,62 +27,26 @@ module.exports = [ './node_modules/app/init/emm_provider.js', './node_modules/app/init/fetch.js', './node_modules/app/init/global_event_handler.js', - './node_modules/app/initial_state.js', './node_modules/app/mattermost.js', './node_modules/app/mattermost_bucket/index.js', './node_modules/app/mattermost_managed/index.js', './node_modules/app/mattermost_managed/mattermost-managed.android.js', - './node_modules/app/push_notifications/index.js', - './node_modules/app/push_notifications/push_notifications.android.js', - './node_modules/app/reducers/app/build.js', - './node_modules/app/reducers/app/index.js', - './node_modules/app/reducers/app/version.js', - './node_modules/app/reducers/device/connection.js', - './node_modules/app/reducers/device/dimension.js', - './node_modules/app/reducers/device/index.js', - './node_modules/app/reducers/device/is_tablet.js', - './node_modules/app/reducers/device/orientation.js', - './node_modules/app/reducers/device/status_bar.js', - './node_modules/app/reducers/index.js', - './node_modules/app/reducers/navigation/index.js', - './node_modules/app/reducers/views/announcement.js', - './node_modules/app/reducers/views/channel.js', - './node_modules/app/reducers/views/client_upgrade.js', - './node_modules/app/reducers/views/emoji.js', - './node_modules/app/reducers/views/extension.js', - './node_modules/app/reducers/views/i18n.js', - './node_modules/app/reducers/views/index.js', - './node_modules/app/reducers/views/post.js', - './node_modules/app/reducers/views/recent_emojis.js', - './node_modules/app/reducers/views/root.js', - './node_modules/app/reducers/views/search.js', - './node_modules/app/reducers/views/select_server.js', - './node_modules/app/reducers/views/team.js', - './node_modules/app/reducers/views/thread.js', - './node_modules/app/reducers/views/user.js', './node_modules/app/mm-redux/action_types/bots.ts', './node_modules/app/mm-redux/action_types/channels.ts', './node_modules/app/mm-redux/action_types/emojis.ts', './node_modules/app/mm-redux/action_types/errors.ts', './node_modules/app/mm-redux/action_types/files.ts', './node_modules/app/mm-redux/action_types/general.ts', - './node_modules/app/mm-redux/action_types/gifs.ts', './node_modules/app/mm-redux/action_types/groups.ts', './node_modules/app/mm-redux/action_types/index.ts', - './node_modules/app/mm-redux/action_types/integrations.ts', - './node_modules/app/mm-redux/action_types/jobs.ts', - './node_modules/app/mm-redux/action_types/plugins.ts', './node_modules/app/mm-redux/action_types/posts.ts', './node_modules/app/mm-redux/action_types/preferences.ts', './node_modules/app/mm-redux/action_types/roles.ts', './node_modules/app/mm-redux/action_types/schemes.ts', - './node_modules/app/mm-redux/action_types/search.ts', './node_modules/app/mm-redux/action_types/teams.ts', './node_modules/app/mm-redux/action_types/users.ts', './node_modules/app/mm-redux/actions/channels.ts', './node_modules/app/mm-redux/actions/emojis.ts', - './node_modules/app/mm-redux/actions/errors.ts', - './node_modules/app/mm-redux/actions/files.ts', './node_modules/app/mm-redux/actions/general.ts', './node_modules/app/mm-redux/actions/helpers.ts', './node_modules/app/mm-redux/actions/posts.ts', @@ -94,19 +59,19 @@ module.exports = [ './node_modules/app/mm-redux/client/fetch_etag.ts', './node_modules/app/mm-redux/client/index.ts', './node_modules/app/mm-redux/constants/emoji.ts', - './node_modules/app/mm-redux/constants/files.ts', './node_modules/app/mm-redux/constants/general.ts', './node_modules/app/mm-redux/constants/groups.ts', './node_modules/app/mm-redux/constants/index.ts', './node_modules/app/mm-redux/constants/permissions.ts', - './node_modules/app/mm-redux/constants/plugins.ts', './node_modules/app/mm-redux/constants/posts.ts', './node_modules/app/mm-redux/constants/preferences.ts', './node_modules/app/mm-redux/constants/request_status.ts', + './node_modules/app/mm-redux/constants/roles.ts', './node_modules/app/mm-redux/constants/stats.ts', './node_modules/app/mm-redux/constants/teams.ts', './node_modules/app/mm-redux/constants/users.ts', './node_modules/app/mm-redux/reducers/entities/bots.ts', + './node_modules/app/mm-redux/reducers/entities/channel_categories.ts', './node_modules/app/mm-redux/reducers/entities/channels.ts', './node_modules/app/mm-redux/reducers/entities/emojis.ts', './node_modules/app/mm-redux/reducers/entities/files.ts', @@ -150,10 +115,6 @@ module.exports = [ './node_modules/app/mm-redux/selectors/entities/teams.ts', './node_modules/app/mm-redux/selectors/entities/timezone.ts', './node_modules/app/mm-redux/selectors/entities/users.ts', - './node_modules/app/mm-redux/store/helpers.ts', - './node_modules/app/mm-redux/store/index.ts', - './node_modules/app/mm-redux/store/initial_state.ts', - './node_modules/app/mm-redux/store/middleware.ts', './node_modules/app/mm-redux/types/actions.ts', './node_modules/app/mm-redux/utils/channel_utils.ts', './node_modules/app/mm-redux/utils/emoji_utils.ts', @@ -162,19 +123,54 @@ module.exports = [ './node_modules/app/mm-redux/utils/helpers.ts', './node_modules/app/mm-redux/utils/i18n_utils.ts', './node_modules/app/mm-redux/utils/key_mirror.ts', + './node_modules/app/mm-redux/utils/post_list.ts', './node_modules/app/mm-redux/utils/post_utils.ts', './node_modules/app/mm-redux/utils/preference_utils.ts', './node_modules/app/mm-redux/utils/sentry.ts', './node_modules/app/mm-redux/utils/team_utils.ts', + './node_modules/app/mm-redux/utils/timezone_utils.ts', './node_modules/app/mm-redux/utils/user_utils.ts', + './node_modules/app/push_notifications/index.js', + './node_modules/app/push_notifications/push_notifications.android.js', + './node_modules/app/reducers/app/build.js', + './node_modules/app/reducers/app/index.js', + './node_modules/app/reducers/app/version.js', + './node_modules/app/reducers/device/connection.js', + './node_modules/app/reducers/device/dimension.js', + './node_modules/app/reducers/device/index.js', + './node_modules/app/reducers/device/is_tablet.js', + './node_modules/app/reducers/device/orientation.js', + './node_modules/app/reducers/device/status_bar.js', + './node_modules/app/reducers/index.js', + './node_modules/app/reducers/views/announcement.js', + './node_modules/app/reducers/views/channel.js', + './node_modules/app/reducers/views/client_upgrade.js', + './node_modules/app/reducers/views/emoji.js', + './node_modules/app/reducers/views/extension.js', + './node_modules/app/reducers/views/i18n.js', + './node_modules/app/reducers/views/index.js', + './node_modules/app/reducers/views/post.js', + './node_modules/app/reducers/views/recent_emojis.js', + './node_modules/app/reducers/views/root.js', + './node_modules/app/reducers/views/search.js', + './node_modules/app/reducers/views/select_server.js', + './node_modules/app/reducers/views/team.js', + './node_modules/app/reducers/views/thread.js', + './node_modules/app/reducers/views/user.js', './node_modules/app/screens/index.js', './node_modules/app/selectors/channel.js', './node_modules/app/selectors/i18n.js', './node_modules/app/store/ephemeral_store.js', - './node_modules/app/store/index.js', - './node_modules/app/store/middleware.js', - './node_modules/app/store/store.js', - './node_modules/app/store/thunk.js', + './node_modules/app/store/helpers.ts', + './node_modules/app/store/index.ts', + './node_modules/app/store/initial_state.js', + './node_modules/app/store/middlewares/helpers.js', + './node_modules/app/store/middlewares/index.ts', + './node_modules/app/store/middlewares/message_retention.js', + './node_modules/app/store/middlewares/sentry.js', + './node_modules/app/store/middlewares/thunk.js', + './node_modules/app/store/mmkv_adapter.ts', + './node_modules/app/store/store.ts', './node_modules/app/store/utils.js', './node_modules/app/telemetry/index.js', './node_modules/app/telemetry/telemetry.android.js', @@ -183,13 +179,11 @@ module.exports = [ './node_modules/app/utils/file.js', './node_modules/app/utils/general.js', './node_modules/app/utils/i18n.js', - './node_modules/app/utils/image_cache_manager.js', './node_modules/app/utils/preferences.js', './node_modules/app/utils/push_notifications.js', './node_modules/app/utils/security.js', './node_modules/app/utils/segment.js', './node_modules/app/utils/sentry/index.js', - './node_modules/app/utils/sentry/middleware.js', './node_modules/app/utils/time_tracker.js', './node_modules/app/utils/timezone.js', './node_modules/index.js', @@ -220,19 +214,11 @@ module.exports = [ './node_modules/node_modules/@babel/runtime/helpers/toConsumableArray.js', './node_modules/node_modules/@babel/runtime/helpers/typeof.js', './node_modules/node_modules/@babel/runtime/helpers/wrapNativeSuper.js', + './node_modules/node_modules/@babel/runtime/node_modules/regenerator-runtime/runtime.js', './node_modules/node_modules/@babel/runtime/regenerator/index.js', './node_modules/node_modules/@react-native-community/async-storage/lib/AsyncStorage.js', './node_modules/node_modules/@react-native-community/async-storage/lib/hooks.js', './node_modules/node_modules/@react-native-community/async-storage/lib/index.js', - './node_modules/node_modules/@react-native-community/netinfo/src/index.ts', - './node_modules/node_modules/@react-native-community/netinfo/src/internal/deprecatedState.ts', - './node_modules/node_modules/@react-native-community/netinfo/src/internal/deprecatedTypes.ts', - './node_modules/node_modules/@react-native-community/netinfo/src/internal/deprecatedUtils.ts', - './node_modules/node_modules/@react-native-community/netinfo/src/internal/internetReachability.ts', - './node_modules/node_modules/@react-native-community/netinfo/src/internal/nativeInterface.ts', - './node_modules/node_modules/@react-native-community/netinfo/src/internal/state.ts', - './node_modules/node_modules/@react-native-community/netinfo/src/internal/types.ts', - './node_modules/node_modules/@react-native-community/netinfo/src/internal/utils.ts', './node_modules/node_modules/base-64/base64.js', './node_modules/node_modules/base64-js/index.js', './node_modules/node_modules/buffer/index.js', @@ -255,10 +241,8 @@ module.exports = [ './node_modules/node_modules/fbjs/lib/performance.js', './node_modules/node_modules/fbjs/lib/performanceNow.js', './node_modules/node_modules/fbjs/lib/warning.js', - './node_modules/node_modules/get-params/index.js', './node_modules/node_modules/harmony-reflect/reflect.js', './node_modules/node_modules/hoist-non-react-statics/dist/hoist-non-react-statics.cjs.js', - './node_modules/node_modules/ieee754/index.js', './node_modules/node_modules/inherits/inherits_browser.js', './node_modules/node_modules/intl-format-cache/dist/index.js', './node_modules/node_modules/intl-format-cache/index.js', @@ -282,16 +266,9 @@ module.exports = [ './node_modules/node_modules/intl/index.js', './node_modules/node_modules/intl/lib/core.js', './node_modules/node_modules/intl/locale-data/complete.js', + './node_modules/node_modules/invariant/browser.js', './node_modules/node_modules/is-arguments/index.js', './node_modules/node_modules/is-generator-function/index.js', - './node_modules/node_modules/jsan/index.js', - './node_modules/node_modules/jsan/lib/cycle.js', - './node_modules/node_modules/jsan/lib/index.js', - './node_modules/node_modules/jsan/lib/path-getter.js', - './node_modules/node_modules/jsan/lib/utils.js', - './node_modules/node_modules/json-stringify-safe/stringify.js', - './node_modules/node_modules/linked-list/_source/linked-list.js', - './node_modules/node_modules/linked-list/index.js', './node_modules/node_modules/lodash.clonedeep/index.js', './node_modules/node_modules/lodash.forin/index.js', './node_modules/node_modules/lodash.get/index.js', @@ -314,8 +291,6 @@ module.exports = [ './node_modules/node_modules/lodash/_apply.js', './node_modules/node_modules/lodash/_arrayEach.js', './node_modules/node_modules/lodash/_arrayFilter.js', - './node_modules/node_modules/lodash/_arrayIncludes.js', - './node_modules/node_modules/lodash/_arrayIncludesWith.js', './node_modules/node_modules/lodash/_arrayLikeKeys.js', './node_modules/node_modules/lodash/_arrayMap.js', './node_modules/node_modules/lodash/_arrayPush.js', @@ -329,9 +304,7 @@ module.exports = [ './node_modules/node_modules/lodash/_baseClamp.js', './node_modules/node_modules/lodash/_baseClone.js', './node_modules/node_modules/lodash/_baseCreate.js', - './node_modules/node_modules/lodash/_baseDifference.js', './node_modules/node_modules/lodash/_baseEach.js', - './node_modules/node_modules/lodash/_baseFindIndex.js', './node_modules/node_modules/lodash/_baseFlatten.js', './node_modules/node_modules/lodash/_baseFor.js', './node_modules/node_modules/lodash/_baseForOwn.js', @@ -339,13 +312,11 @@ module.exports = [ './node_modules/node_modules/lodash/_baseGetAllKeys.js', './node_modules/node_modules/lodash/_baseGetTag.js', './node_modules/node_modules/lodash/_baseHasIn.js', - './node_modules/node_modules/lodash/_baseIndexOf.js', './node_modules/node_modules/lodash/_baseIsArguments.js', './node_modules/node_modules/lodash/_baseIsEqual.js', './node_modules/node_modules/lodash/_baseIsEqualDeep.js', './node_modules/node_modules/lodash/_baseIsMap.js', './node_modules/node_modules/lodash/_baseIsMatch.js', - './node_modules/node_modules/lodash/_baseIsNaN.js', './node_modules/node_modules/lodash/_baseIsNative.js', './node_modules/node_modules/lodash/_baseIsSet.js', './node_modules/node_modules/lodash/_baseIsTypedArray.js', @@ -365,7 +336,6 @@ module.exports = [ './node_modules/node_modules/lodash/_baseTimes.js', './node_modules/node_modules/lodash/_baseToString.js', './node_modules/node_modules/lodash/_baseUnary.js', - './node_modules/node_modules/lodash/_baseUniq.js', './node_modules/node_modules/lodash/_baseUnset.js', './node_modules/node_modules/lodash/_cacheHas.js', './node_modules/node_modules/lodash/_castFunction.js', @@ -451,14 +421,12 @@ module.exports = [ './node_modules/node_modules/lodash/_stackGet.js', './node_modules/node_modules/lodash/_stackHas.js', './node_modules/node_modules/lodash/_stackSet.js', - './node_modules/node_modules/lodash/_strictIndexOf.js', './node_modules/node_modules/lodash/_stringToPath.js', './node_modules/node_modules/lodash/_toKey.js', './node_modules/node_modules/lodash/_toSource.js', './node_modules/node_modules/lodash/clone.js', './node_modules/node_modules/lodash/cloneDeep.js', './node_modules/node_modules/lodash/constant.js', - './node_modules/node_modules/lodash/difference.js', './node_modules/node_modules/lodash/endsWith.js', './node_modules/node_modules/lodash/eq.js', './node_modules/node_modules/lodash/forEach.js', @@ -487,7 +455,6 @@ module.exports = [ './node_modules/node_modules/lodash/last.js', './node_modules/node_modules/lodash/lodash.js', './node_modules/node_modules/lodash/map.js', - './node_modules/node_modules/lodash/mapValues.js', './node_modules/node_modules/lodash/memoize.js', './node_modules/node_modules/lodash/merge.js', './node_modules/node_modules/lodash/noop.js', @@ -499,7 +466,6 @@ module.exports = [ './node_modules/node_modules/lodash/toNumber.js', './node_modules/node_modules/lodash/toPlainObject.js', './node_modules/node_modules/lodash/toString.js', - './node_modules/node_modules/lodash/union.js', './node_modules/node_modules/lodash/uniqueId.js', './node_modules/node_modules/lodash/unset.js', './node_modules/node_modules/metro/src/lib/bundle-modules/HMRClient.js', @@ -508,7 +474,6 @@ module.exports = [ './node_modules/node_modules/moment-timezone/index.js', './node_modules/node_modules/moment-timezone/moment-timezone.js', './node_modules/node_modules/moment/moment.js', - './node_modules/node_modules/nanoid/non-secure/index.js', './node_modules/node_modules/nullthrows/nullthrows.js', './node_modules/node_modules/object-assign/index.js', './node_modules/node_modules/promise/setimmediate/core.js', @@ -520,22 +485,15 @@ module.exports = [ './node_modules/node_modules/prop-types/factoryWithTypeCheckers.js', './node_modules/node_modules/prop-types/index.js', './node_modules/node_modules/prop-types/lib/ReactPropTypesSecret.js', - './node_modules/node_modules/querystring/decode.js', - './node_modules/node_modules/querystring/encode.js', - './node_modules/node_modules/querystring/index.js', './node_modules/node_modules/querystringify/index.js', './node_modules/node_modules/react-intl/lib/index.js', './node_modules/node_modules/react-intl/locale-data/en.js', './node_modules/node_modules/react-intl/locale-data/index.js', - './node_modules/node_modules/react-is/cjs/react-is.production.min.js', + './node_modules/node_modules/react-is/cjs/react-is.production.js', './node_modules/node_modules/react-is/index.js', './node_modules/node_modules/react-lifecycles-compat/react-lifecycles-compat.cjs.js', './node_modules/node_modules/react-native-cookies/index.js', './node_modules/node_modules/react-native-device-info/deviceinfo.js', - './node_modules/node_modules/react-native-gesture-handler/Directions.js', - './node_modules/node_modules/react-native-gesture-handler/DrawerLayout.js', - './node_modules/node_modules/react-native-gesture-handler/GestureButtons.js', - './node_modules/node_modules/react-native-gesture-handler/GestureComponents.js', './node_modules/node_modules/react-native-gesture-handler/GestureHandler.js', './node_modules/node_modules/react-native-gesture-handler/GestureHandlerButton.js', './node_modules/node_modules/react-native-gesture-handler/GestureHandlerPropTypes.js', @@ -545,19 +503,24 @@ module.exports = [ './node_modules/node_modules/react-native-gesture-handler/PlatformConstants.js', './node_modules/node_modules/react-native-gesture-handler/RNGestureHandlerModule.js', './node_modules/node_modules/react-native-gesture-handler/State.js', - './node_modules/node_modules/react-native-gesture-handler/Swipeable.js', './node_modules/node_modules/react-native-gesture-handler/createHandler.js', './node_modules/node_modules/react-native-gesture-handler/createNativeWrapper.js', './node_modules/node_modules/react-native-gesture-handler/gestureHandlerRootHOC.js', './node_modules/node_modules/react-native-gesture-handler/index.js', - './node_modules/node_modules/react-native-gesture-handler/touchables/GenericTouchable.js', - './node_modules/node_modules/react-native-gesture-handler/touchables/TouchableHighlight.js', - './node_modules/node_modules/react-native-gesture-handler/touchables/TouchableNativeFeedback.android.js', - './node_modules/node_modules/react-native-gesture-handler/touchables/TouchableOpacity.js', - './node_modules/node_modules/react-native-gesture-handler/touchables/TouchableWithoutFeedback.js', - './node_modules/node_modules/react-native-gesture-handler/touchables/index.js', './node_modules/node_modules/react-native-haptic-feedback/index.js', './node_modules/node_modules/react-native-keychain/index.js', + './node_modules/node_modules/react-native-mmkv-storage/index.js', + './node_modules/node_modules/react-native-mmkv-storage/src/api.js', + './node_modules/node_modules/react-native-mmkv-storage/src/encryption.js', + './node_modules/node_modules/react-native-mmkv-storage/src/indexer/arrays.js', + './node_modules/node_modules/react-native-mmkv-storage/src/indexer/booleans.js', + './node_modules/node_modules/react-native-mmkv-storage/src/indexer/indexer.js', + './node_modules/node_modules/react-native-mmkv-storage/src/indexer/maps.js', + './node_modules/node_modules/react-native-mmkv-storage/src/indexer/numbers.js', + './node_modules/node_modules/react-native-mmkv-storage/src/indexer/strings.js', + './node_modules/node_modules/react-native-mmkv-storage/src/keygen.js', + './node_modules/node_modules/react-native-mmkv-storage/src/loader.js', + './node_modules/node_modules/react-native-mmkv-storage/src/utils.js', './node_modules/node_modules/react-native-navigation/lib/dist/Navigation.js', './node_modules/node_modules/react-native-navigation/lib/dist/adapters/AppRegistryService.js', './node_modules/node_modules/react-native-navigation/lib/dist/adapters/AssetResolver.js', @@ -743,6 +706,7 @@ module.exports = [ './node_modules/node_modules/react-native/Libraries/ReactPrivate/ReactNativePrivateInitializeCore.js', './node_modules/node_modules/react-native/Libraries/ReactPrivate/ReactNativePrivateInterface.js', './node_modules/node_modules/react-native/Libraries/Renderer/implementations/ReactNativeRenderer-prod.js', + './node_modules/node_modules/react-native/Libraries/Renderer/shims/NativeMethodsMixin.js', './node_modules/node_modules/react-native/Libraries/Renderer/shims/ReactNative.js', './node_modules/node_modules/react-native/Libraries/Renderer/shims/ReactNativeViewConfigRegistry.js', './node_modules/node_modules/react-native/Libraries/Renderer/shims/createReactNativeComponentClass.js', @@ -830,18 +794,18 @@ module.exports = [ './node_modules/node_modules/redux-action-buffer/index.js', './node_modules/node_modules/redux-batched-actions/lib/index.js', './node_modules/node_modules/redux-persist-transform-filter/dist/index.js', - './node_modules/node_modules/redux-persist/lib/autoRehydrate.js', './node_modules/node_modules/redux-persist/lib/constants.js', - './node_modules/node_modules/redux-persist/lib/createPersistor.js', + './node_modules/node_modules/redux-persist/lib/createMigrate.js', + './node_modules/node_modules/redux-persist/lib/createPersistoid.js', './node_modules/node_modules/redux-persist/lib/createTransform.js', - './node_modules/node_modules/redux-persist/lib/defaults/asyncLocalStorage.js', './node_modules/node_modules/redux-persist/lib/getStoredState.js', './node_modules/node_modules/redux-persist/lib/index.js', + './node_modules/node_modules/redux-persist/lib/persistCombineReducers.js', + './node_modules/node_modules/redux-persist/lib/persistReducer.js', './node_modules/node_modules/redux-persist/lib/persistStore.js', './node_modules/node_modules/redux-persist/lib/purgeStoredState.js', - './node_modules/node_modules/redux-persist/lib/utils/isStatePlainEnough.js', - './node_modules/node_modules/redux-persist/lib/utils/setImmediate.js', - './node_modules/node_modules/redux-thunk/lib/index.js', + './node_modules/node_modules/redux-persist/lib/stateReconciler/autoMergeLevel1.js', + './node_modules/node_modules/redux-reset/lib/index.js', './node_modules/node_modules/redux/lib/redux.js', './node_modules/node_modules/requires-port/index.js', './node_modules/node_modules/reselect/lib/index.js', @@ -852,11 +816,16 @@ module.exports = [ './node_modules/node_modules/rn-fetch-blob/ios.js', './node_modules/node_modules/rn-fetch-blob/json-stream.js', './node_modules/node_modules/rn-fetch-blob/polyfill/Fetch.js', - './node_modules/node_modules/rn-host-detect/index.js', - './node_modules/node_modules/sc-channel/index.js', - './node_modules/node_modules/sc-errors/decycle.js', - './node_modules/node_modules/sc-errors/index.js', - './node_modules/node_modules/sc-formatter/index.js', + './node_modules/node_modules/rn-fetch-blob/polyfill/File.js', + './node_modules/node_modules/rn-fetch-blob/polyfill/FileReader.js', + './node_modules/node_modules/rn-fetch-blob/polyfill/ProgressEvent.js', + './node_modules/node_modules/rn-fetch-blob/polyfill/XMLHttpRequest.js', + './node_modules/node_modules/rn-fetch-blob/polyfill/XMLHttpRequestEventTarget.js', + './node_modules/node_modules/rn-fetch-blob/polyfill/index.js', + './node_modules/node_modules/rn-fetch-blob/utils/log.js', + './node_modules/node_modules/rn-fetch-blob/utils/unicode.js', + './node_modules/node_modules/rn-fetch-blob/utils/uri.js', + './node_modules/node_modules/rn-fetch-blob/utils/uuid.js', './node_modules/node_modules/scheduler/cjs/scheduler-tracing.production.min.js', './node_modules/node_modules/scheduler/cjs/scheduler.production.min.js', './node_modules/node_modules/scheduler/index.js', @@ -908,7 +877,6 @@ module.exports = [ './node_modules/node_modules/stacktrace-parser/dist/stack-trace-parser.cjs.js', './node_modules/node_modules/symbol-observable/lib/index.js', './node_modules/node_modules/symbol-observable/lib/ponyfill.js', - './node_modules/node_modules/tslib/tslib.js', './node_modules/node_modules/url-parse/index.js', './node_modules/node_modules/util/support/isBufferBrowser.js', './node_modules/node_modules/util/support/types.js', diff --git a/patches/react-native-mmkv-storage+0.3.1.patch b/patches/react-native-mmkv-storage+0.3.1.patch new file mode 100644 index 000000000..bda317d1e --- /dev/null +++ b/patches/react-native-mmkv-storage+0.3.1.patch @@ -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 = @{ diff --git a/patches/redux-persist+6.0.0.patch b/patches/redux-persist+6.0.0.patch new file mode 100644 index 000000000..b4bc60d47 --- /dev/null +++ b/patches/redux-persist+6.0.0.patch @@ -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) => Promise; + debug?: boolean; +- serialize?: boolean; ++ serialize?: boolean | Function; ++ deserialize?: boolean | Function; + timeout?: number; + writeFailHandler?: (err: Error) => void; + } diff --git a/share_extension/android/index.js b/share_extension/android/index.js index cf6e88b5b..4263b334a 100644 --- a/share_extension/android/index.js +++ b/share_extension/android/index.js @@ -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 ( - + ({}), {enableBuffer: false}); + const {store} = configureStore(null, preloadedState, persistConfig, {enableBuffer: false}); const wait = () => new Promise((resolve) => setTimeout(resolve), 300); //eslint-disable-line await wait(); diff --git a/tsconfig.json b/tsconfig.json index b9640e6f5..3cccf85ff 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -34,10 +34,11 @@ "@constants/*": ["app/constants/*"], "@constants": ["app/constants/index"], "@i18n": ["app/i18n/index"], + "@mm-redux/*": ["app/mm-redux/*"], "@selectors/*": ["app/selectors/*"], + "@store/*": ["app/store"], "@telemetry/*": ["/app/telemetry/*"], "@utils/*": ["app/utils/*"], - "@mm-redux/*": ["app/mm-redux/*"], "@websocket": ["app/client/websocket"], "*": ["./*", "node_modules/*"] }