From 7afa9aed01df38f94c88305caa82d54fd9d78c36 Mon Sep 17 00:00:00 2001 From: Miguel Alatzar Date: Wed, 8 Apr 2020 13:44:54 -0700 Subject: [PATCH] [MM-23698] [MM-19559] Remove redux-offline and updated redux-persist (#4120) * Remove redux-offline and configure redux-persist * Fix typo * Fix configure store * Upgrade to redux-persist 6.0.0 * Add migration from redux-persist v4 to v6 * Replace AsyncStorage with MMKVStorage to boost storage speed * Mock RNFastStorage * Fix reactions test * Fix clearing the store on logout * Remove the need for LOGOUT_SUCCESS * No need to pass persistConfig to middlewares() * Remove unused imports * Export connection Accidentally removed this export. * Add batch action name Co-Authored-By: Elias Nahum * Add batch action name Co-Authored-By: Elias Nahum * Add batch action name Co-Authored-By: Elias Nahum * Add batch action name Co-Authored-By: Elias Nahum * Fix delete post * Fix leave channel Co-authored-by: Elias Nahum --- .gitignore | 1 + NOTICE.txt | 35 -- app/actions/device/index.js | 13 +- app/actions/views/login.test.js | 5 +- app/actions/views/root.js | 63 +++- app/actions/views/user.js | 7 +- app/components/reactions/reactions.test.js | 6 +- .../main/channels_list/channel_item/index.js | 2 +- app/init/global_event_handler.js | 67 ++-- app/initial_state.js | 9 +- app/mattermost.js | 2 +- app/mm-redux/action_types/users.ts | 1 - app/mm-redux/actions/bots.test.js | 19 - app/mm-redux/actions/channels.test.js | 69 ++-- app/mm-redux/actions/channels.ts | 35 +- app/mm-redux/actions/helpers.test.js | 22 -- app/mm-redux/actions/helpers.ts | 7 +- app/mm-redux/actions/posts.test.js | 7 + app/mm-redux/actions/posts.ts | 140 ++++--- app/mm-redux/actions/preferences.test.js | 13 +- app/mm-redux/actions/preferences.ts | 61 ++-- app/mm-redux/actions/users.test.js | 106 ++---- app/mm-redux/actions/users.ts | 17 - app/mm-redux/constants/general.ts | 3 +- app/mm-redux/reducers/entities/bots.ts | 4 +- .../reducers/entities/channel_categories.ts | 6 +- .../reducers/entities/channels.test.js | 12 - app/mm-redux/reducers/entities/channels.ts | 17 - app/mm-redux/reducers/entities/emojis.ts | 4 +- app/mm-redux/reducers/entities/files.ts | 8 +- app/mm-redux/reducers/entities/general.ts | 12 - .../reducers/entities/integrations.ts | 12 +- app/mm-redux/reducers/entities/posts.ts | 29 +- app/mm-redux/reducers/entities/preferences.ts | 3 - app/mm-redux/reducers/entities/roles.ts | 7 +- app/mm-redux/reducers/entities/schemes.ts | 5 +- app/mm-redux/reducers/entities/search.test.js | 25 -- app/mm-redux/reducers/entities/search.ts | 11 +- app/mm-redux/reducers/entities/teams.ts | 11 - app/mm-redux/reducers/entities/users.test.js | 36 -- app/mm-redux/reducers/entities/users.ts | 31 -- app/mm-redux/reducers/requests/users.ts | 30 +- app/mm-redux/reducers/websocket.ts | 4 - app/mm-redux/store/configureStore.dev.ts | 102 ------ app/mm-redux/store/configureStore.prod.ts | 57 --- app/mm-redux/store/helpers.ts | 56 ++- app/mm-redux/store/index.ts | 108 +++++- app/mm-redux/store/initial_state.ts | 4 - app/mm-redux/store/middleware.ts | 13 +- app/mm-redux/store/mmkv_adapter.ts | 142 ++++++++ app/mm-redux/types/module.d.ts | 2 - app/mm-redux/types/requests.ts | 1 - app/reducers/device/connection.js | 6 +- app/reducers/device/dimension.js | 5 +- app/reducers/index.js | 2 - app/reducers/navigation/index.js | 18 - app/reducers/views/announcement.js | 6 +- app/reducers/views/channel.js | 8 +- app/reducers/views/emoji.js | 5 +- app/reducers/views/i18n.js | 2 - app/reducers/views/post.js | 2 - app/reducers/views/search.js | 5 +- app/store/index.js | 3 +- app/store/middleware.js | 247 +++++-------- app/store/middleware.test.js | 34 +- app/store/store.js | 73 +--- app/store/utils.js | 14 +- app/store/utils.test.js | 5 + app/utils/push_notifications.js | 8 +- babel.config.js | 1 + ios/Podfile.lock | 10 + package-lock.json | 342 +++++------------- package.json | 6 +- packager/moduleNames.js | 15 - packager/modulePaths.js | 15 - test/setup.js | 4 + test/test_store.js | 31 +- tsconfig.json | 1 + 78 files changed, 876 insertions(+), 1454 deletions(-) delete mode 100644 app/mm-redux/store/configureStore.dev.ts delete mode 100644 app/mm-redux/store/configureStore.prod.ts create mode 100644 app/mm-redux/store/mmkv_adapter.ts delete mode 100644 app/reducers/navigation/index.js diff --git a/.gitignore b/.gitignore index 474bbfc02..77db043b0 100644 --- a/.gitignore +++ b/.gitignore @@ -87,6 +87,7 @@ ios/sentry.properties # Testing .nyc_output coverage +.tmp # Bundle artifact *.jsbundle diff --git a/NOTICE.txt b/NOTICE.txt index 5e32b8b45..3b3c63bae 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -2410,41 +2410,6 @@ SOFTWARE. --- -## redux-offline - -This product contains 'redux-offline' by redux-offline. - -Build Offline-First Apps for Web and React Native - -* HOMEPAGE: - * https://github.com/redux-offline/redux-offline - -* LICENSE: MIT - -MIT License - -Copyright (c) 2017 Jani Eväkallio - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - ---- - ## redux-persist This product contains 'redux-persist' by Zack Story. diff --git a/app/actions/device/index.js b/app/actions/device/index.js index 87778046e..4fc3cf4bc 100644 --- a/app/actions/device/index.js +++ b/app/actions/device/index.js @@ -1,21 +1,16 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {networkStatusChangedAction} from 'redux-offline'; -import {batchActions} from 'redux-batched-actions'; - import {DeviceTypes} from 'app/constants'; export function connection(isOnline) { return async (dispatch, getState) => { const state = getState(); if (isOnline !== undefined && isOnline !== state.device.connection) { //eslint-disable-line no-undefined - dispatch(batchActions([ - networkStatusChangedAction(isOnline), { - type: DeviceTypes.CONNECTION_CHANGED, - data: isOnline, - }, - ], 'BATCH_CONNECTION_CHANGED')); + dispatch({ + type: DeviceTypes.CONNECTION_CHANGED, + data: isOnline, + }); } }; } diff --git a/app/actions/views/login.test.js b/app/actions/views/login.test.js index 92cc6e00f..dd2a6028f 100644 --- a/app/actions/views/login.test.js +++ b/app/actions/views/login.test.js @@ -4,6 +4,7 @@ import configureStore from 'redux-mock-store'; import thunk from 'redux-thunk'; +import {Client4} from '@mm-redux/client'; import * as GeneralActions from '@mm-redux/actions/general'; import {handleSuccessfulLogin} from 'app/actions/views/login'; @@ -46,8 +47,8 @@ describe('Actions.Views.Login', () => { }); test('handleSuccessfulLogin gets config and license ', async () => { - const getClientConfig = jest.spyOn(GeneralActions, 'getClientConfig'); - const getLicenseConfig = jest.spyOn(GeneralActions, 'getLicenseConfig'); + const getClientConfig = jest.spyOn(Client4, 'getClientConfigOld'); + const getLicenseConfig = jest.spyOn(Client4, 'getClientLicenseOld'); await store.dispatch(handleSuccessfulLogin()); expect(getClientConfig).toHaveBeenCalled(); diff --git a/app/actions/views/root.js b/app/actions/views/root.js index 04ca28283..2bd227a01 100644 --- a/app/actions/views/root.js +++ b/app/actions/views/root.js @@ -4,15 +4,19 @@ import {batchActions} from 'redux-batched-actions'; import {ChannelTypes, GeneralTypes, TeamTypes} from '@mm-redux/action_types'; -import {Client4} from '@mm-redux/client'; -import {General} from '@mm-redux/constants'; import {fetchMyChannelsAndMembers} from '@mm-redux/actions/channels'; import {getClientConfig, getDataRetentionPolicy, getLicenseConfig} from '@mm-redux/actions/general'; import {receivedNewPost} from '@mm-redux/actions/posts'; import {getMyTeams, getMyTeamMembers} from '@mm-redux/actions/teams'; +import {Client4} from '@mm-redux/client'; +import {General} from '@mm-redux/constants'; +import EventEmitter from '@mm-redux/utils/event_emitter'; -import {ViewTypes} from 'app/constants'; +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 {markChannelViewedAndRead} from './channel'; @@ -29,24 +33,36 @@ export function startDataCleanup() { export function loadConfigAndLicense() { return async (dispatch, getState) => { const {currentUserId} = getState().entities.users; - const [configData, licenseData] = await Promise.all([ - getClientConfig()(dispatch, getState), - getLicenseConfig()(dispatch, getState), - ]); - const config = configData.data || {}; - const license = licenseData.data || {}; + try { + const [config, license] = await Promise.all([ + Client4.getClientConfigOld(), + Client4.getClientLicenseOld(), + ]); - if (currentUserId) { - if (config.DataRetentionEnableMessageDeletion && config.DataRetentionEnableMessageDeletion === 'true' && - license.IsLicensed === 'true' && license.DataRetention === 'true') { - dispatch(getDataRetentionPolicy()); - } else { - dispatch({type: GeneralTypes.RECEIVED_DATA_RETENTION_POLICY, data: {}}); + const actions = [{ + type: GeneralTypes.CLIENT_CONFIG_RECEIVED, + data: config, + }, { + type: GeneralTypes.CLIENT_LICENSE_RECEIVED, + data: license, + }]; + + if (currentUserId) { + if (config.DataRetentionEnableMessageDeletion && config.DataRetentionEnableMessageDeletion === 'true' && + license.IsLicensed === 'true' && license.DataRetention === 'true') { + dispatch(getDataRetentionPolicy()); + } else { + actions.push({type: GeneralTypes.RECEIVED_DATA_RETENTION_POLICY, data: {}}); + } } - } - return {config, license}; + dispatch(batchActions(actions, 'BATCH_LOAD_CONFIG_AND_LICENSE')); + + return {config, license}; + } catch (error) { + return {error}; + } }; } @@ -126,7 +142,18 @@ export function handleSelectTeamAndChannel(teamId, channelId) { } export function purgeOfflineStore() { - return {type: General.OFFLINE_STORE_PURGE}; + 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); + }; } // A non-optimistic version of the createPost action in app/mm-redux with the file handling diff --git a/app/actions/views/user.js b/app/actions/views/user.js index d841fe82d..27b6d8464 100644 --- a/app/actions/views/user.js +++ b/app/actions/views/user.js @@ -3,12 +3,14 @@ import {batchActions} from 'redux-batched-actions'; +import {NavigationTypes} from 'app/constants'; import {GeneralTypes, RoleTypes, UserTypes} from '@mm-redux/action_types'; import {getDataRetentionPolicy} from '@mm-redux/actions/general'; import * as HelperActions from '@mm-redux/actions/helpers'; import {autoUpdateTimezone} from '@mm-redux/actions/timezone'; import {Client4} from '@mm-redux/client'; import {General} from '@mm-redux/constants'; +import EventEmitter from '@mm-redux/utils/event_emitter'; import {getConfig, getLicense} from '@mm-redux/selectors/entities/general'; import {isTimezoneEnabled} from '@mm-redux/selectors/entities/timezone'; import {getCurrentUserId, getStatusForUserId} from '@mm-redux/selectors/entities/users'; @@ -194,7 +196,7 @@ export function ssoLogin(token) { } export function logout(skipServerLogout = false) { - return async (dispatch) => { + return async () => { if (!skipServerLogout) { try { Client4.logout(); @@ -203,7 +205,8 @@ export function logout(skipServerLogout = false) { } } - dispatch({type: UserTypes.LOGOUT_SUCCESS}); + EventEmitter.emit(NavigationTypes.NAVIGATION_RESET); + return {data: true}; }; } diff --git a/app/components/reactions/reactions.test.js b/app/components/reactions/reactions.test.js index 48a2a0da3..28021283a 100644 --- a/app/components/reactions/reactions.test.js +++ b/app/components/reactions/reactions.test.js @@ -2,7 +2,7 @@ // See LICENSE.txt for license information. import React from 'react'; -import {shallow} from 'enzyme'; +import {shallowWithIntl} from 'test/intl-test-helper.js'; import Reactions from './reactions'; @@ -31,12 +31,12 @@ describe('Reactions', () => { }; test('should match snapshot', () => { - const wrapper = shallow(); + const wrapper = shallowWithIntl(); expect(wrapper.getElement()).toMatchSnapshot(); }); test('should match snapshot with canAddReaction = false', () => { - const wrapper = shallow( + const wrapper = shallowWithIntl( { const botsResult = state.entities.bots.accounts[bot.user_id]; assert.equal(bot.owner_id, botsResult.owner_id); }); - - it('logout', async () => { - // Fill redux store with somthing - const bot = TestHelper.fakeBot(); - nock(Client4.getBaseRoute()). - post('/bots'). - reply(200, bot); - await store.dispatch(BotActions.createBot(bot)); - - // Should be cleared by logout - nock(Client4.getUsersRoute()). - post('/logout'). - reply(200, {status: 'OK'}); - await store.dispatch(UserActions.logout()); - - // Check is clear - const state = store.getState(); - assert.equal(0, Object.keys(state.entities.bots.accounts).length); - }); }); diff --git a/app/mm-redux/actions/channels.test.js b/app/mm-redux/actions/channels.test.js index 1febe3b97..4c339c354 100644 --- a/app/mm-redux/actions/channels.test.js +++ b/app/mm-redux/actions/channels.test.js @@ -23,7 +23,14 @@ describe('Actions.Channels', () => { }); beforeEach(async () => { - store = await configureStore(); + const initialState = { + entities: { + users: { + currentUserId: TestHelper.basicUser.id, + }, + }, + }; + store = await configureStore(initialState); }); afterAll(async () => { @@ -1954,52 +1961,38 @@ describe('Actions.Channels', () => { assert.deepEqual(channel.purpose, purpose); }); - it('leaveChannel', (done) => { - async function test() { - TestHelper.mockLogin(); - await store.dispatch(login(TestHelper.basicUser.email, 'password1')); - nock(Client4.getBaseRoute()). - get(`/channels/${TestHelper.basicChannel.id}`). - reply(200, TestHelper.basicChannel); + it('leaveChannel', async () => { + TestHelper.mockLogin(); + await store.dispatch(login(TestHelper.basicUser.email, 'password1')); + nock(Client4.getBaseRoute()). + get(`/channels/${TestHelper.basicChannel.id}`). + reply(200, TestHelper.basicChannel); - nock(Client4.getBaseRoute()). - post(`/channels/${TestHelper.basicChannel.id}/members`). - reply(201, {channel_id: TestHelper.basicChannel.id, roles: 'channel_user', user_id: TestHelper.basicUser.id}); + nock(Client4.getBaseRoute()). + post(`/channels/${TestHelper.basicChannel.id}/members`). + reply(201, {channel_id: TestHelper.basicChannel.id, roles: 'channel_user', user_id: TestHelper.basicUser.id}); - await store.dispatch(Actions.joinChannel(TestHelper.basicUser.id, TestHelper.basicTeam.id, TestHelper.basicChannel.id)); + await store.dispatch(Actions.joinChannel(TestHelper.basicUser.id, TestHelper.basicTeam.id, TestHelper.basicChannel.id)); - const {channels, myMembers} = store.getState().entities.channels; - assert.ok(channels[TestHelper.basicChannel.id]); - assert.ok(myMembers[TestHelper.basicChannel.id]); + let channelsState = store.getState().entities.channels; + assert.ok(channelsState.channels[TestHelper.basicChannel.id]); + assert.ok(channelsState.myMembers[TestHelper.basicChannel.id]); - nock(Client4.getBaseRoute()). - delete(`/channels/${TestHelper.basicChannel.id}/members/${TestHelper.basicUser.id}`). - reply(400, {}); + nock(Client4.getBaseRoute()). + delete(`/channels/${TestHelper.basicChannel.id}/members/${TestHelper.basicUser.id}`). + reply(200, OK_RESPONSE); - nock(Client4.getBaseRoute()). - delete(`/channels/${TestHelper.basicChannel.id}/members/${TestHelper.basicUser.id}`). - reply(200, OK_RESPONSE); + await store.dispatch(Actions.leaveChannel(TestHelper.basicChannel.id)); - // This action will retry after 1000ms - await store.dispatch(Actions.leaveChannel(TestHelper.basicChannel.id)); + channelsState = store.getState().entities.channels; - setTimeout(test2, 300); - } - - async function test2() { - // retry will have completed and should have left the channel successfully - const {channels, myMembers} = store.getState().entities.channels; - - assert.ok(channels[TestHelper.basicChannel.id]); - assert.ifError(myMembers[TestHelper.basicChannel.id]); - done(); - } - - test(); + assert.ok(channelsState.channels[TestHelper.basicChannel.id]); + assert.ifError(channelsState.myMembers[TestHelper.basicChannel.id]); }); it('leave private channel', async () => { const newChannel = { + ...TestHelper.fakeChannelWithId(TestHelper.basicChannel.id), team_id: TestHelper.basicTeam.id, name: 'redux-test-private', display_name: 'Redux Test', @@ -2010,14 +2003,14 @@ describe('Actions.Channels', () => { nock(Client4.getBaseRoute()). post('/channels'). - reply(201, {...TestHelper.fakeChannelWithId(TestHelper.basicTeam.id), ...newChannel}); + reply(201, newChannel); const {data: channel} = await store.dispatch(Actions.createChannel(newChannel, TestHelper.basicUser.id)); let channels = store.getState().entities.channels.channels; assert.ok(channels[channel.id]); nock(Client4.getBaseRoute()). - delete(`/channels/${TestHelper.basicChannel.id}/members/${TestHelper.basicUser.id}`). + delete(`/channels/${channel.id}/members/${TestHelper.basicUser.id}`). reply(200, OK_RESPONSE); await store.dispatch(Actions.leaveChannel(channel.id)); diff --git a/app/mm-redux/actions/channels.ts b/app/mm-redux/actions/channels.ts index e22bfd4b0..b652c9b04 100644 --- a/app/mm-redux/actions/channels.ts +++ b/app/mm-redux/actions/channels.ts @@ -636,26 +636,23 @@ export function leaveChannel(channelId: string): ActionFunc { team_id: channel.team_id, type: channel.type, }, - meta: { - offline: { - effect: () => Client4.removeFromChannel(currentUserId, channelId), - commit: {type: 'do_nothing'}, // redux-offline always needs to dispatch something on commit - rollback: () => { - dispatch(batchActions([ - { - type: ChannelTypes.RECEIVED_CHANNEL, - data: channel, - }, - { - type: ChannelTypes.RECEIVED_MY_CHANNEL_MEMBER, - data: member, - }, - ])); - }, - }, - }, }); + try { + await Client4.removeFromChannel(currentUserId, channelId); + } catch (error) { + dispatch(batchActions([ + { + type: ChannelTypes.RECEIVED_CHANNEL, + data: channel, + }, + { + type: ChannelTypes.RECEIVED_MY_CHANNEL_MEMBER, + data: member, + }, + ])); + } + return {data: true}; }; } @@ -1399,7 +1396,7 @@ export function favoriteChannel(channelId: string): ActionFunc { Client4.trackEvent('action', 'action_channels_favorite'); - return savePreferences(currentUserId, [preference])(dispatch); + return dispatch(savePreferences(currentUserId, [preference])); }; } diff --git a/app/mm-redux/actions/helpers.test.js b/app/mm-redux/actions/helpers.test.js index d70a97c6a..fa7b44160 100644 --- a/app/mm-redux/actions/helpers.test.js +++ b/app/mm-redux/actions/helpers.test.js @@ -60,28 +60,6 @@ describe('Actions.Helpers', () => { assert.deepEqual(dispatch.actions, []); }); - it('should trigger logout when passed a 401 server error', async () => { - const store = await configureStore({ - entities: { - users: { - currentUserId: 'user', - }, - }, - }); - const dispatch = mockDispatch(store.dispatch); - - const error = new ClientError(Client4.getUrl(), { - message: 'Failed to do something', - status_code: 401, - url: '/api/v4/foo/bar', - }); - - forceLogoutIfNecessary(error, dispatch, store.getState); - - assert.notEqual(Client4.token, token); - assert.deepEqual(dispatch.actions, [{type: UserTypes.LOGOUT_SUCCESS, data: {}}]); - }); - it('should do nothing when failing to log in', async () => { const store = await configureStore({ entities: { diff --git a/app/mm-redux/actions/helpers.ts b/app/mm-redux/actions/helpers.ts index 3f20887c1..1070ec753 100644 --- a/app/mm-redux/actions/helpers.ts +++ b/app/mm-redux/actions/helpers.ts @@ -1,7 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {Client4} from '@mm-redux/client'; -import {UserTypes} from '@mm-redux/action_types'; + +import {logout} from '@actions/views/user'; import {Client4Error} from '@mm-redux/types/client4'; import {batchActions, Action, ActionFunc, GenericAction, DispatchFunc, GetStateFunc} from '@mm-redux/types/actions'; @@ -13,8 +13,7 @@ export function forceLogoutIfNecessary(err: Client4Error, dispatch: DispatchFunc const {currentUserId} = getState().entities.users; if ('status_code' in err && err.status_code === HTTP_UNAUTHORIZED && err.url && err.url.indexOf('/login') === -1 && currentUserId) { - Client4.setToken(''); - dispatch({type: UserTypes.LOGOUT_SUCCESS, data: {}}); + dispatch(logout(false)); } } diff --git a/app/mm-redux/actions/posts.test.js b/app/mm-redux/actions/posts.test.js index a6694d246..409b43ac7 100644 --- a/app/mm-redux/actions/posts.test.js +++ b/app/mm-redux/actions/posts.test.js @@ -206,6 +206,10 @@ describe('Actions.Posts', () => { await Actions.createPost(TestHelper.fakePost(channelId))(store.dispatch, store.getState); const initialPosts = store.getState().entities.posts; const postId = Object.keys(initialPosts.posts)[0]; + + nock(Client4.getBaseRoute()). + delete(`/posts/${postId}`). + reply(200, {}); await Actions.deletePost(initialPosts.posts[postId])(store.dispatch, store.getState); const state = store.getState(); @@ -243,6 +247,9 @@ describe('Actions.Posts', () => { assert.ok(reactions[post1.id]); assert.ok(reactions[post1.id][TestHelper.basicUser.id + '-' + emojiName]); + nock(Client4.getBaseRoute()). + delete(`/posts/${post1.id}`). + reply(200, {}); await Actions.deletePost(post1)(store.dispatch, store.getState); reactions = store.getState().entities.posts.reactions; diff --git a/app/mm-redux/actions/posts.ts b/app/mm-redux/actions/posts.ts index d5dde5a3d..8930679ce 100644 --- a/app/mm-redux/actions/posts.ts +++ b/app/mm-redux/actions/posts.ts @@ -2,7 +2,7 @@ // See LICENSE.txt for license information. import {Client4, DEFAULT_LIMIT_AFTER, DEFAULT_LIMIT_BEFORE} from '@mm-redux/client'; -import {General, Preferences, Posts} from '../constants'; +import {General, Preferences, Posts} from '@mm-redux/constants'; import {WebsocketEvents} from '@constants'; import {PostTypes, ChannelTypes, FileTypes, IntegrationTypes} from '@mm-redux/action_types'; @@ -33,7 +33,6 @@ import {Action, ActionResult, batchActions, DispatchFunc, GetStateFunc, GenericA import {ChannelUnread} from '@mm-redux/types/channels'; import {GlobalState} from '@mm-redux/types/store'; import {Post} from '@mm-redux/types/posts'; -import {Error} from '@mm-redux/types/errors'; import {Reaction} from '@mm-redux/types/reactions'; import {UserProfile} from '@mm-redux/types/users'; import {Dictionary} from '@mm-redux/types/utilities'; @@ -169,9 +168,9 @@ export function createPost(post: Post, files: any[] = []) { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { const state = getState(); const currentUserId = state.entities.users.currentUserId; - const timestamp = Date.now(); const pendingPostId = post.pending_post_id || `${currentUserId}:${timestamp}`; + let actions: Array = []; if (Selectors.isPostIdSending(state, pendingPostId)) { return {data: true}; @@ -197,79 +196,78 @@ export function createPost(post: Post, files: any[] = []) { file_ids: fileIds, }; - dispatch({ + actions.push({ type: FileTypes.RECEIVED_FILES_FOR_POST, postId: pendingPostId, data: files, }); } - dispatch({ + actions.push({ type: PostTypes.RECEIVED_NEW_POST, data: { id: pendingPostId, ...newPost, }, - meta: { - offline: { - effect: () => Client4.createPost({...newPost, create_at: 0}), - commit: (result: any, payload: any) => { - const actions: Action[] = [ - receivedPost(payload), - { - type: PostTypes.CREATE_POST_SUCCESS, - }, - { - type: ChannelTypes.INCREMENT_TOTAL_MSG_COUNT, - data: { - channelId: newPost.channel_id, - amount: 1, - }, - }, - { - type: ChannelTypes.DECREMENT_UNREAD_MSG_COUNT, - data: { - channelId: newPost.channel_id, - amount: 1, - }, - }, - ]; + }); - if (files) { - actions.push({ - type: FileTypes.RECEIVED_FILES_FOR_POST, - postId: payload.id, - data: files, - }); - } + dispatch(batchActions(actions, 'BATCH_CREATE_POST_INIT')); - dispatch(batchActions(actions)); - }, - maxRetry: 0, - offlineRollback: true, - rollback: (result: any, error: Error) => { - const data = { - ...newPost, - id: pendingPostId, - failed: true, - update_at: Date.now(), - }; - dispatch({type: PostTypes.CREATE_POST_FAILURE, error}); + try { + const created = await Client4.createPost({...newPost, create_at: 0}); - // If the failure was because: the root post was deleted or - // TownSquareIsReadOnly=true then remove the post - if (error.server_error_id === 'api.post.create_post.root_id.app_error' || - error.server_error_id === 'api.post.create_post.town_square_read_only' || - error.server_error_id === 'plugin.message_will_be_posted.dismiss_post' - ) { - dispatch(removePost(data) as any); - } else { - dispatch(receivedPost(data)); - } + actions = [ + receivedPost(created), + { + type: PostTypes.CREATE_POST_SUCCESS, + }, + { + type: ChannelTypes.INCREMENT_TOTAL_MSG_COUNT, + data: { + channelId: newPost.channel_id, + amount: 1, }, }, - }, - }); + { + type: ChannelTypes.DECREMENT_UNREAD_MSG_COUNT, + data: { + channelId: newPost.channel_id, + amount: 1, + }, + }, + ]; + + if (files) { + actions.push({ + type: FileTypes.RECEIVED_FILES_FOR_POST, + postId: created.id, + data: files, + }); + } + + dispatch(batchActions(actions, 'BATCH_CREATE_POST')); + } catch (error) { + const data = { + ...newPost, + id: pendingPostId, + failed: true, + update_at: Date.now(), + }; + actions = [{type: PostTypes.CREATE_POST_FAILURE, error}]; + + // If the failure was because: the root post was deleted or + // TownSquareIsReadOnly=true then remove the post + if (error.server_error_id === 'api.post.create_post.root_id.app_error' || + error.server_error_id === 'api.post.create_post.town_square_read_only' || + error.server_error_id === 'plugin.message_will_be_posted.dismiss_post' + ) { + actions.push(removePost(data) as any); + } else { + actions.push(receivedPost(data)); + } + + dispatch(batchActions(actions, 'BATCH_CREATE_POST_FAILED')); + } return {data: true}; }; @@ -363,11 +361,10 @@ export function resetCreatePostRequest() { } export function deletePost(post: ExtendedPost) { - return (dispatch: DispatchFunc, getState: GetStateFunc) => { + return async (dispatch: DispatchFunc, getState: GetStateFunc) => { const state = getState(); - const delPost = {...post}; - if (delPost.type === Posts.POST_TYPES.COMBINED_USER_ACTIVITY && delPost.system_post_ids) { - delPost.system_post_ids.forEach((systemPostId) => { + if (post.type === Posts.POST_TYPES.COMBINED_USER_ACTIVITY && post.system_post_ids) { + post.system_post_ids.forEach((systemPostId) => { const systemPost = Selectors.getPost(state, systemPostId); if (systemPost) { dispatch(deletePost(systemPost)); @@ -376,15 +373,14 @@ export function deletePost(post: ExtendedPost) { } else { dispatch({ type: PostTypes.POST_DELETED, - data: delPost, - meta: { - offline: { - effect: () => Client4.deletePost(post.id), - commit: {type: 'do_nothing'}, // redux-offline always needs to dispatch something on commit - rollback: receivedPost(delPost), - }, - }, + data: post, }); + + try { + await Client4.deletePost(post.id); + } catch (error) { + dispatch(receivedPost(post)); + } } return {data: true}; diff --git a/app/mm-redux/actions/preferences.test.js b/app/mm-redux/actions/preferences.test.js index d2954f9c1..a24dd3de1 100644 --- a/app/mm-redux/actions/preferences.test.js +++ b/app/mm-redux/actions/preferences.test.js @@ -21,7 +21,14 @@ describe('Actions.Preferences', () => { }); beforeEach(async () => { - store = await configureStore(); + const initialState = { + entities: { + users: { + currentUserId: TestHelper.basicUser.id, + }, + }, + }; + store = await configureStore(initialState); }); afterAll(async () => { @@ -241,7 +248,7 @@ describe('Actions.Preferences', () => { nock(Client4.getUsersRoute()). get('/me/preferences'). reply(200, existingPreferences); - await Actions.getMyPreferences()(store.dispatch, store.getState); + await store.dispatch(Actions.getMyPreferences()); const newTheme = { type: 'Mattermost Dark', @@ -249,7 +256,7 @@ describe('Actions.Preferences', () => { nock(Client4.getUsersRoute()). put(`/${TestHelper.basicUser.id}/preferences`). reply(200, OK_RESPONSE); - await Actions.saveTheme(team.id, newTheme)(store.dispatch, store.getState); + await store.dispatch(Actions.saveTheme(team.id, newTheme)); const state = store.getState(); const {myPreferences} = state.entities.preferences; diff --git a/app/mm-redux/actions/preferences.ts b/app/mm-redux/actions/preferences.ts index aa566a5d1..78e72c706 100644 --- a/app/mm-redux/actions/preferences.ts +++ b/app/mm-redux/actions/preferences.ts @@ -14,28 +14,26 @@ import {PreferenceType} from '@mm-redux/types/preferences'; import {bindClientFunc} from './helpers'; import {getProfilesByIds, getProfilesInChannel} from './users'; import {getChannelAndMyMember, getMyChannelMember} from './channels'; + export function deletePreferences(userId: string, preferences: Array): ActionFunc { return async (dispatch: DispatchFunc, getState: GetStateFunc) => { const state = getState(); const myPreferences = getMyPreferencesSelector(state); const currentPreferences = preferences.map((pref) => myPreferences[getPreferenceKey(pref.category, pref.name)]); - dispatch({ - type: PreferenceTypes.DELETED_PREFERENCES, - data: preferences, - meta: { - offline: { - effect: () => Client4.deletePreferences(userId, preferences), - commit: { - type: PreferenceTypes.DELETED_PREFERENCES, - }, - rollback: { - type: PreferenceTypes.RECEIVED_PREFERENCES, - data: currentPreferences, - }, - }, - }, - }); + try { + dispatch({ + type: PreferenceTypes.DELETED_PREFERENCES, + data: preferences, + }); + + await Client4.deletePreferences(userId, preferences); + } catch { + dispatch({ + type: PreferenceTypes.RECEIVED_PREFERENCES, + data: currentPreferences, + }); + } return {data: true}; }; @@ -104,22 +102,21 @@ export function makeGroupMessageVisibleIfNecessary(channelId: string): ActionFun export function savePreferences(userId: string, preferences: Array) { return async (dispatch: DispatchFunc) => { - dispatch({ - type: PreferenceTypes.RECEIVED_PREFERENCES, - data: preferences, - meta: { - offline: { - effect: () => Client4.savePreferences(userId, preferences), - commit: { - type: PreferenceTypes.RECEIVED_PREFERENCES, - }, - rollback: { - type: PreferenceTypes.DELETED_PREFERENCES, - data: preferences, - }, - }, - }, - }); + try { + // Optimistic action + dispatch({ + type: PreferenceTypes.RECEIVED_PREFERENCES, + data: preferences, + }); + + await Client4.savePreferences(userId, preferences); + } catch (error) { + dispatch({ + type: PreferenceTypes.DELETED_PREFERENCES, + data: preferences, + }); + return {error}; + } return {data: true}; }; diff --git a/app/mm-redux/actions/users.test.js b/app/mm-redux/actions/users.test.js index d524337b4..a05e2d5f9 100644 --- a/app/mm-redux/actions/users.test.js +++ b/app/mm-redux/actions/users.test.js @@ -5,14 +5,20 @@ import assert from 'assert'; import nock from 'nock'; import fs from 'fs'; +import {NavigationTypes} from '@constants'; +import {logout} from '@actions/views/user'; import * as Actions from '@mm-redux/actions/users'; import {Client4} from '@mm-redux/client'; import {RequestStatus} from '../constants'; import TestHelper from 'test/test_helper'; 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'; const OK_RESPONSE = {status: 'OK'}; +const UNAUTHORIZED = {status_code: 401}; describe('Actions.Users', () => { let store; @@ -21,7 +27,17 @@ describe('Actions.Users', () => { }); beforeEach(async () => { - store = await configureStore(); + const initial = { + ...initialState, + entities: { + ...initialState.entities, + users: { + ...initialState.entities.users, + currentUserId: 'current-user-id', + }, + }, + }; + store = await configureStore(initial); }); afterAll(async () => { @@ -171,50 +187,13 @@ describe('Actions.Users', () => { }); it('logout', async () => { + const emit = jest.spyOn(EventEmitter, 'emit'); nock(Client4.getBaseRoute()). post('/users/logout'). reply(200, OK_RESPONSE); - await Actions.logout()(store.dispatch, store.getState); - - const state = store.getState(); - const logoutRequest = state.requests.users.logout; - const general = state.entities.general; - const users = state.entities.users; - const teams = state.entities.teams; - const channels = state.entities.channels; - const posts = state.entities.posts; - const preferences = state.entities.preferences; - - if (logoutRequest.status === RequestStatus.FAILURE) { - throw new Error(JSON.stringify(logoutRequest.error)); - } - - assert.deepStrictEqual(general.config, {}, 'config not empty'); - assert.deepStrictEqual(general.license, {}, 'license not empty'); - assert.strictEqual(users.currentUserId, '', 'current user id not empty'); - assert.deepStrictEqual(users.mySessions, [], 'user sessions not empty'); - assert.deepStrictEqual(users.myAudits, [], 'user audits not empty'); - assert.deepStrictEqual(users.profiles, {}, 'user profiles not empty'); - assert.deepStrictEqual(users.profilesInTeam, {}, 'users profiles in team not empty'); - assert.deepStrictEqual(users.profilesInChannel, {}, 'users profiles in channel not empty'); - assert.deepStrictEqual(users.profilesNotInChannel, {}, 'users profiles NOT in channel not empty'); - assert.deepStrictEqual(users.statuses, {}, 'users statuses not empty'); - assert.strictEqual(teams.currentTeamId, '', 'current team id is not empty'); - assert.deepStrictEqual(teams.teams, {}, 'teams is not empty'); - assert.deepStrictEqual(teams.myMembers, {}, 'team members is not empty'); - assert.deepStrictEqual(teams.membersInTeam, {}, 'members in team is not empty'); - assert.deepStrictEqual(teams.stats, {}, 'team stats is not empty'); - assert.strictEqual(channels.currentChannelId, '', 'current channel id is not empty'); - assert.deepStrictEqual(channels.channels, {}, 'channels is not empty'); - assert.deepStrictEqual(channels.channelsInTeam, {}, 'channelsInTeam is not empty'); - assert.deepStrictEqual(channels.myMembers, {}, 'channel members is not empty'); - assert.deepStrictEqual(channels.stats, {}, 'channel stats is not empty'); - assert.strictEqual(posts.selectedPostId, '', 'selected post id is not empty'); - assert.strictEqual(posts.currentFocusedPostId, '', 'current focused post id is not empty'); - assert.deepStrictEqual(posts.posts, {}, 'posts is not empty'); - assert.deepStrictEqual(posts.postsInChannel, {}, 'posts by channel is not empty'); - assert.deepStrictEqual(preferences.myPreferences, {}, 'user preferences not empty'); + await store.dispatch(logout(false)); + expect(emit).toHaveBeenCalledWith(NavigationTypes.NAVIGATION_RESET); nock(Client4.getBaseRoute()). post('/users/login'). @@ -625,7 +604,7 @@ describe('Actions.Users', () => { assert.strictEqual(sessions.length, 0); TestHelper.mockLogin(); - await Actions.loginById(user.id, 'password1')(store.dispatch, store.getState); + await store.dispatch(Actions.loginById(user.id, 'password1')); nock(Client4.getBaseRoute()). post('/users/login'). @@ -635,7 +614,7 @@ describe('Actions.Users', () => { nock(Client4.getBaseRoute()). get(`/users/${user.id}/sessions`). reply(200, [{id: TestHelper.generateId(), create_at: 1507756921338, expires_at: 1510348921338, last_activity_at: 1507821125630, user_id: TestHelper.basicUser.id, device_id: '', roles: 'system_admin system_user'}, {id: TestHelper.generateId(), create_at: 1507756921338, expires_at: 1510348921338, last_activity_at: 1507821125630, user_id: TestHelper.basicUser.id, device_id: '', roles: 'system_admin system_user'}]); - await Actions.getSessions(user.id)(store.dispatch, store.getState); + await store.dispatch(Actions.getSessions(user.id)); sessions = store.getState().entities.users.mySessions; assert.ok(sessions.length > 1); @@ -643,23 +622,14 @@ describe('Actions.Users', () => { nock(Client4.getBaseRoute()). post(`/users/${user.id}/sessions/revoke/all`). reply(200, OK_RESPONSE); - const {data} = await Actions.revokeAllSessionsForUser(user.id)(store.dispatch, store.getState); + const {data} = await store.dispatch(Actions.revokeAllSessionsForUser(user.id)); assert.deepEqual(data, true); nock(Client4.getBaseRoute()). get('/users'). query(true). - reply(401, {}); - await Actions.getProfiles(0)(store.dispatch, store.getState); - - const logoutRequest = store.getState().requests.users.logout; - if (logoutRequest.status === RequestStatus.FAILURE) { - throw new Error(JSON.stringify(logoutRequest.error)); - } - - sessions = store.getState().entities.users.mySessions; - - assert.strictEqual(sessions.length, 0); + reply(401, UNAUTHORIZED); + await store.dispatch(Actions.getProfiles(0)); nock(Client4.getBaseRoute()). post('/users/login'). @@ -669,6 +639,11 @@ describe('Actions.Users', () => { it('revokeSessionsForAllUsers', async () => { const user = TestHelper.basicUser; + + nock(Client4.getBaseRoute()). + post('/roles/names'). + reply(200, []); + nock(Client4.getBaseRoute()). post('/users/logout'). reply(200, OK_RESPONSE); @@ -678,7 +653,7 @@ describe('Actions.Users', () => { assert.strictEqual(sessions.length, 0); TestHelper.mockLogin(); - await Actions.loginById(user.id, 'password1')(store.dispatch, store.getState); + await store.dispatch(Actions.loginById(user.id, 'password1')); nock(Client4.getBaseRoute()). post('/users/login'). @@ -688,7 +663,7 @@ describe('Actions.Users', () => { nock(Client4.getBaseRoute()). get(`/users/${user.id}/sessions`). reply(200, [{id: TestHelper.generateId(), create_at: 1507756921338, expires_at: 1510348921338, last_activity_at: 1507821125630, user_id: TestHelper.basicUser.id, device_id: '', roles: 'system_admin system_user'}, {id: TestHelper.generateId(), create_at: 1507756921338, expires_at: 1510348921338, last_activity_at: 1507821125630, user_id: TestHelper.basicUser.id, device_id: '', roles: 'system_admin system_user'}]); - await Actions.getSessions(user.id)(store.dispatch, store.getState); + await store.dispatch(Actions.getSessions(user.id)); sessions = store.getState().entities.users.mySessions; assert.ok(sessions.length > 1); @@ -696,24 +671,9 @@ describe('Actions.Users', () => { nock(Client4.getBaseRoute()). post('/users/sessions/revoke/all'). reply(200, OK_RESPONSE); - const {data} = await Actions.revokeSessionsForAllUsers(user.id)(store.dispatch, store.getState); + const {data} = await store.dispatch(Actions.revokeSessionsForAllUsers(user.id)); assert.deepEqual(data, true); - nock(Client4.getBaseRoute()). - get('/users'). - query(true). - reply(401, {}); - await Actions.getProfiles(0)(store.dispatch, store.getState); - - const logoutRequest = store.getState().requests.users.logout; - if (logoutRequest.status === RequestStatus.FAILURE) { - throw new Error(JSON.stringify(logoutRequest.error)); - } - - sessions = store.getState().entities.users.mySessions; - - assert.strictEqual(sessions.length, 0); - nock(Client4.getBaseRoute()). post('/users/login'). reply(200, TestHelper.basicUser); diff --git a/app/mm-redux/actions/users.ts b/app/mm-redux/actions/users.ts index 67e25c532..260105dc4 100644 --- a/app/mm-redux/actions/users.ts +++ b/app/mm-redux/actions/users.ts @@ -242,22 +242,6 @@ export function loadMe(): ActionFunc { }; } -export function logout(): ActionFunc { - return async (dispatch: DispatchFunc, getState: GetStateFunc) => { - dispatch({type: UserTypes.LOGOUT_REQUEST, data: null}); - - try { - await Client4.logout(); - } catch (error) { - // nothing to do here - } - - dispatch({type: UserTypes.LOGOUT_SUCCESS, data: null}); - - return {data: true}; - }; -} - export function getTotalUsersStats(): ActionFunc { return bindClientFunc({ clientFunc: Client4.getTotalUsersStats, @@ -1421,7 +1405,6 @@ export default { checkMfa, generateMfaSecret, login, - logout, getProfiles, getProfilesByIds, getProfilesInTeam, diff --git a/app/mm-redux/constants/general.ts b/app/mm-redux/constants/general.ts index ac79ddb4d..0c7a4a09a 100644 --- a/app/mm-redux/constants/general.ts +++ b/app/mm-redux/constants/general.ts @@ -51,7 +51,7 @@ export default { GM_CHANNEL: 'G', PUSH_NOTIFY_APPLE_REACT_NATIVE: 'apple_rn', PUSH_NOTIFY_ANDROID_REACT_NATIVE: 'android_rn', - STORE_REHYDRATION_COMPLETE: 'store_hydation_complete', + STORE_REHYDRATION_COMPLETE: 'store_hydration_complete', OFFLINE_STORE_RESET: 'offline_store_reset', OFFLINE_STORE_PURGE: 'offline_store_purge', TEAMMATE_NAME_DISPLAY: { @@ -68,4 +68,5 @@ export default { DISABLED: 'disabled', DEFAULT_ON: 'default_on', DEFAULT_OFF: 'default_off', + REHYDRATED: 'app/REHYDRATED', }; diff --git a/app/mm-redux/reducers/entities/bots.ts b/app/mm-redux/reducers/entities/bots.ts index 11790a1e9..2dab6547b 100644 --- a/app/mm-redux/reducers/entities/bots.ts +++ b/app/mm-redux/reducers/entities/bots.ts @@ -1,7 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. import {combineReducers} from 'redux'; -import {BotTypes, UserTypes} from '@mm-redux/action_types'; +import {BotTypes} from '@mm-redux/action_types'; import {GenericAction} from '@mm-redux/types/actions'; import {Dictionary} from '@mm-redux/types/utilities'; import {Bot} from '@mm-redux/types/bots'; @@ -22,8 +22,6 @@ function accounts(state: Dictionary = {}, action: GenericAction) { nextState[bot.user_id] = bot; return nextState; } - case UserTypes.LOGOUT_SUCCESS: - return {}; default: return state; } diff --git a/app/mm-redux/reducers/entities/channel_categories.ts b/app/mm-redux/reducers/entities/channel_categories.ts index 1f5e61124..f057eb385 100644 --- a/app/mm-redux/reducers/entities/channel_categories.ts +++ b/app/mm-redux/reducers/entities/channel_categories.ts @@ -5,7 +5,7 @@ import {combineReducers} from 'redux'; import {CategoryTypes} from '../../constants/channel_categories'; -import {ChannelCategoryTypes, TeamTypes, UserTypes} from '@mm-redux/action_types'; +import {ChannelCategoryTypes, TeamTypes} from '@mm-redux/action_types'; import {GenericAction} from '@mm-redux/types/actions'; import {ChannelCategory} from '@mm-redux/types/channel_categories'; @@ -79,8 +79,6 @@ export function byId(state: IDMappedObjects = {}, action: Gener return nextState; } - case UserTypes.LOGOUT_SUCCESS: - return {}; default: return state; } @@ -141,8 +139,6 @@ export function orderByTeam(state: RelationOneToOne[] return nextState; } - case UserTypes.LOGOUT_SUCCESS: - return {}; default: return state; } diff --git a/app/mm-redux/reducers/entities/channels.test.js b/app/mm-redux/reducers/entities/channels.test.js index 31126bb7e..21a710fec 100644 --- a/app/mm-redux/reducers/entities/channels.test.js +++ b/app/mm-redux/reducers/entities/channels.test.js @@ -433,18 +433,6 @@ describe('channels', () => { }); expect(nextState.channel1).toBe(undefined); }); - test('remove all marks if user logs out', () => { - const state = deepFreeze({ - channel1: true, - channel231: false, - }); - const nextState = Reducers.manuallyUnread(state, { - type: UserTypes.LOGOUT_SUCCESS, - data: {}, - }); - expect(nextState.channel1).toBe(undefined); - expect(nextState.channel231).toBe(undefined); - }); }); describe('RECEIVED_CHANNELS', () => { test('should not remove current channel', () => { diff --git a/app/mm-redux/reducers/entities/channels.ts b/app/mm-redux/reducers/entities/channels.ts index b266efb18..2c17cefd7 100644 --- a/app/mm-redux/reducers/entities/channels.ts +++ b/app/mm-redux/reducers/entities/channels.ts @@ -42,8 +42,6 @@ function currentChannelId(state = '', action: GenericAction) { switch (action.type) { case ChannelTypes.SELECT_CHANNEL: return action.data; - case UserTypes.LOGOUT_SUCCESS: - return ''; default: return state; } @@ -182,8 +180,6 @@ function channels(state: IDMappedObjects = {}, action: GenericAction) { return hasNewValues ? nextState : state; } - case UserTypes.LOGOUT_SUCCESS: - return {}; default: return state; } @@ -217,8 +213,6 @@ function channelsInTeam(state: RelationOneToMany = {}, action: Ge }; return channelListToSet(state, values); } - case UserTypes.LOGOUT_SUCCESS: - return {}; default: return state; } @@ -396,8 +390,6 @@ function myMembers(state: RelationOneToOne = {}, act return state; } - case UserTypes.LOGOUT_SUCCESS: - return {}; default: return state; } @@ -462,8 +454,6 @@ function membersInChannel(state: RelationOneToOne = {}, action: Gene return state; } - case UserTypes.LOGOUT_SUCCESS: - return {}; default: return state; } @@ -633,11 +621,6 @@ export function manuallyUnread(state: RelationOneToOne = {}, a } return state; } - case UserTypes.LOGOUT_SUCCESS: { - // user is logging out, remove any reference - return {}; - } - case ChannelTypes.ADD_MANUALLY_UNREAD: case ChannelTypes.POST_UNREAD_SUCCESS: { return {...state, [action.data.channelId]: true}; diff --git a/app/mm-redux/reducers/entities/emojis.ts b/app/mm-redux/reducers/entities/emojis.ts index d0fbb91d3..f4b965553 100644 --- a/app/mm-redux/reducers/entities/emojis.ts +++ b/app/mm-redux/reducers/entities/emojis.ts @@ -1,7 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. import {combineReducers} from 'redux'; -import {EmojiTypes, PostTypes, UserTypes} from '@mm-redux/action_types'; +import {EmojiTypes, PostTypes} from '@mm-redux/action_types'; import {EmojisState, CustomEmoji} from '@mm-redux/types/emojis'; import * as types from '@mm-redux/types'; @@ -25,7 +25,6 @@ export function customEmoji(state: types.utilities.IDMappedObjects return nextState; } case EmojiTypes.CLEAR_CUSTOM_EMOJIS: - case UserTypes.LOGOUT_SUCCESS: return {}; case PostTypes.RECEIVED_NEW_POST: @@ -94,7 +93,6 @@ function nonExistentEmoji(state: Set = new Set(), action: types.actions. return changed ? nextState : state; } case EmojiTypes.CLEAR_CUSTOM_EMOJIS: - case UserTypes.LOGOUT_SUCCESS: return new Set(); default: diff --git a/app/mm-redux/reducers/entities/files.ts b/app/mm-redux/reducers/entities/files.ts index 7c28aab9c..9fd739362 100644 --- a/app/mm-redux/reducers/entities/files.ts +++ b/app/mm-redux/reducers/entities/files.ts @@ -2,7 +2,7 @@ // See LICENSE.txt for license information. import {combineReducers} from 'redux'; -import {FileTypes, PostTypes, UserTypes} from '@mm-redux/action_types'; +import {FileTypes, PostTypes} from '@mm-redux/action_types'; import {GenericAction} from '@mm-redux/types/actions'; import {Post} from '@mm-redux/types/posts'; import {FileInfo} from '@mm-redux/types/files'; @@ -50,8 +50,6 @@ export function files(state: Dictionary = {}, action: GenericAction) { return state; } - case UserTypes.LOGOUT_SUCCESS: - return {}; default: return state; } @@ -109,8 +107,6 @@ export function fileIdsByPostId(state: Dictionary> = {}, action: G return state; } - case UserTypes.LOGOUT_SUCCESS: - return {}; default: return state; } @@ -132,8 +128,6 @@ function filePublicLink(state: {link: string} = {link: ''}, action: GenericActio case FileTypes.RECEIVED_FILE_PUBLIC_LINK: { return action.data; } - case UserTypes.LOGOUT_SUCCESS: - return {link: ''}; default: return state; diff --git a/app/mm-redux/reducers/entities/general.ts b/app/mm-redux/reducers/entities/general.ts index a0fded765..6c3876a23 100644 --- a/app/mm-redux/reducers/entities/general.ts +++ b/app/mm-redux/reducers/entities/general.ts @@ -14,8 +14,6 @@ function config(state: Partial = {}, action: GenericAction) { case GeneralTypes.SET_CONFIG_AND_LICENSE: return Object.assign({}, state, action.data.config); case GeneralTypes.CLIENT_CONFIG_RESET: - case UserTypes.LOGOUT_SUCCESS: - return {}; default: return state; } @@ -40,8 +38,6 @@ function credentials(state: any = {}, action: GenericAction) { return { url: action.data.url, }; - case UserTypes.LOGOUT_SUCCESS: - return {}; default: return state; } @@ -51,8 +47,6 @@ function dataRetentionPolicy(state: any = {}, action: GenericAction) { switch (action.type) { case GeneralTypes.RECEIVED_DATA_RETENTION_POLICY: return action.data; - case UserTypes.LOGOUT_SUCCESS: - return {}; default: return state; } @@ -74,8 +68,6 @@ function license(state: any = {}, action: GenericAction) { case GeneralTypes.SET_CONFIG_AND_LICENSE: return Object.assign({}, state, action.data.license); case GeneralTypes.CLIENT_LICENSE_RESET: - case UserTypes.LOGOUT_SUCCESS: - return {}; default: return state; } @@ -85,8 +77,6 @@ function timezones(state: string[] = [], action: GenericAction) { switch (action.type) { case GeneralTypes.SUPPORTED_TIMEZONES_RECEIVED: return action.data; - case UserTypes.LOGOUT_SUCCESS: - return []; default: return state; } @@ -96,8 +86,6 @@ function serverVersion(state = '', action: GenericAction) { switch (action.type) { case GeneralTypes.RECEIVED_SERVER_VERSION: return action.data; - case UserTypes.LOGOUT_SUCCESS: - return ''; default: return state; } diff --git a/app/mm-redux/reducers/entities/integrations.ts b/app/mm-redux/reducers/entities/integrations.ts index e0285755d..76828e8f5 100644 --- a/app/mm-redux/reducers/entities/integrations.ts +++ b/app/mm-redux/reducers/entities/integrations.ts @@ -2,7 +2,7 @@ // See LICENSE.txt for license information. import {combineReducers} from 'redux'; -import {IntegrationTypes, UserTypes, ChannelTypes} from '@mm-redux/action_types'; +import {IntegrationTypes, ChannelTypes} from '@mm-redux/action_types'; import {GenericAction} from '@mm-redux/types/actions'; import {Command, IncomingWebhook, OutgoingWebhook, OAuthApp} from '@mm-redux/types/integrations'; import {Dictionary, IDMappedObjects} from '@mm-redux/types/utilities'; @@ -42,8 +42,6 @@ function incomingHooks(state: IDMappedObjects = {}, action: Gen return state; } - case UserTypes.LOGOUT_SUCCESS: - return {}; default: return state; @@ -85,8 +83,6 @@ function outgoingHooks(state: IDMappedObjects = {}, action: Gen return state; } - case UserTypes.LOGOUT_SUCCESS: - return {}; default: return state; @@ -131,8 +127,6 @@ function commands(state: IDMappedObjects = {}, action: GenericAction) { Reflect.deleteProperty(nextState, action.data.id); return nextState; } - case UserTypes.LOGOUT_SUCCESS: - return {}; default: return state; @@ -159,8 +153,6 @@ function systemCommands(state: IDMappedObjects = {}, action: GenericAct } return state; - case UserTypes.LOGOUT_SUCCESS: - return {}; default: return state; @@ -186,8 +178,6 @@ function oauthApps(state: IDMappedObjects = {}, action: GenericAction) Reflect.deleteProperty(nextState, action.data.id); return nextState; } - case UserTypes.LOGOUT_SUCCESS: - return {}; default: return state; diff --git a/app/mm-redux/reducers/entities/posts.ts b/app/mm-redux/reducers/entities/posts.ts index 6fdea6e95..9158940b4 100644 --- a/app/mm-redux/reducers/entities/posts.ts +++ b/app/mm-redux/reducers/entities/posts.ts @@ -1,6 +1,6 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {ChannelTypes, GeneralTypes, PostTypes, UserTypes} from '@mm-redux/action_types'; +import {ChannelTypes, GeneralTypes, PostTypes} from '@mm-redux/action_types'; import {Posts} from '../../constants'; import {comparePosts} from '@mm-redux/utils/post_utils'; import {Post, PostsState, PostOrderBlock, MessageHistory} from '@mm-redux/types/posts'; @@ -165,9 +165,6 @@ export function handlePosts(state: RelationOneToOne = {}, action: Ge return nextState; } - - case UserTypes.LOGOUT_SUCCESS: - return {}; default: return state; } @@ -653,9 +650,6 @@ export function postsInChannel(state: Dictionary> = {}, ac return nextState; } - - case UserTypes.LOGOUT_SUCCESS: - return {}; default: return state; } @@ -937,9 +931,6 @@ export function postsInThread(state: RelationOneToMany = {}, action: return nextState; } - - case UserTypes.LOGOUT_SUCCESS: - return {}; default: return state; } @@ -949,8 +940,6 @@ function selectedPostId(state = '', action: GenericAction) { switch (action.type) { case PostTypes.RECEIVED_POST_SELECTED: return action.data; - case UserTypes.LOGOUT_SUCCESS: - return ''; default: return state; } @@ -960,8 +949,6 @@ function currentFocusedPostId(state = '', action: GenericAction) { switch (action.type) { case PostTypes.RECEIVED_FOCUSED_POST: return action.data; - case UserTypes.LOGOUT_SUCCESS: - return ''; default: return state; } @@ -1033,8 +1020,6 @@ export function reactions(state: RelationOneToOne> = return state; } - case UserTypes.LOGOUT_SUCCESS: - return {}; default: return state; } @@ -1079,8 +1064,6 @@ export function openGraph(state: RelationOneToOne = {}, action: Gener return posts.reduce(storeOpenGraphForPost, state); } - case UserTypes.LOGOUT_SUCCESS: - return {}; default: return state; } @@ -1165,16 +1148,6 @@ function messagesHistory(state: Partial = {}, action: GenericAct index: nextIndex, }; } - case UserTypes.LOGOUT_SUCCESS: { - const index: Dictionary = {}; - index[Posts.MESSAGE_TYPES.POST] = -1; - index[Posts.MESSAGE_TYPES.COMMENT] = -1; - - return { - messages: [], - index, - }; - } default: return state; } diff --git a/app/mm-redux/reducers/entities/preferences.ts b/app/mm-redux/reducers/entities/preferences.ts index 3d8f57565..b78afdb4b 100644 --- a/app/mm-redux/reducers/entities/preferences.ts +++ b/app/mm-redux/reducers/entities/preferences.ts @@ -53,9 +53,6 @@ function myPreferences(state: Dictionary = {}, action: GenericAc return nextState; } - - case UserTypes.LOGOUT_SUCCESS: - return {}; default: return state; } diff --git a/app/mm-redux/reducers/entities/roles.ts b/app/mm-redux/reducers/entities/roles.ts index 3e5be7ff4..80482e658 100644 --- a/app/mm-redux/reducers/entities/roles.ts +++ b/app/mm-redux/reducers/entities/roles.ts @@ -2,7 +2,7 @@ // See LICENSE.txt for license information. import {combineReducers} from 'redux'; -import {RoleTypes, UserTypes} from '@mm-redux/action_types'; +import {RoleTypes} from '@mm-redux/action_types'; import {GenericAction} from '@mm-redux/types/actions'; import {Dictionary} from '@mm-redux/types/utilities'; import {Role} from '@mm-redux/types/roles'; @@ -11,8 +11,6 @@ function pending(state: Set = new Set(), action: GenericAction) { switch (action.type) { case RoleTypes.SET_PENDING_ROLES: return action.data; - case UserTypes.LOGOUT_SUCCESS: - return new Set(); default: return state; } @@ -49,9 +47,6 @@ function roles(state: Dictionary = {}, action: GenericAction) { return state; } - - case UserTypes.LOGOUT_SUCCESS: - return {}; default: return state; } diff --git a/app/mm-redux/reducers/entities/schemes.ts b/app/mm-redux/reducers/entities/schemes.ts index ec7985cb1..5957dfd85 100644 --- a/app/mm-redux/reducers/entities/schemes.ts +++ b/app/mm-redux/reducers/entities/schemes.ts @@ -1,7 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. import {combineReducers} from 'redux'; -import {SchemeTypes, UserTypes} from '@mm-redux/action_types'; +import {SchemeTypes} from '@mm-redux/action_types'; import {GenericAction} from '@mm-redux/types/actions'; import {SchemesState, Scheme} from '@mm-redux/types/schemes'; @@ -34,9 +34,6 @@ function schemes(state: { return nextState; } - case UserTypes.LOGOUT_SUCCESS: - return {}; - default: return state; } diff --git a/app/mm-redux/reducers/entities/search.test.js b/app/mm-redux/reducers/entities/search.test.js index 07fa2eca1..509a52965 100644 --- a/app/mm-redux/reducers/entities/search.test.js +++ b/app/mm-redux/reducers/entities/search.test.js @@ -100,17 +100,6 @@ describe('reducers.entities.search', () => { const actualState = reducer({results: inputState}, action); assert.deepEqual(actualState.results, expectedState); }); - - describe('UserTypes.LOGOUT_SUCCESS', () => { - const inputState = ['abcd', 'efgh']; - const action = { - type: UserTypes.LOGOUT_SUCCESS, - }; - const expectedState = []; - - const actualState = reducer({results: inputState}, action); - assert.deepEqual(actualState.results, expectedState); - }); }); describe('matches', () => { @@ -251,20 +240,6 @@ describe('reducers.entities.search', () => { const actualState = reducer({matches: inputState}, action); assert.deepEqual(actualState.matches, expectedState); }); - - describe('UserTypes.LOGOUT_SUCCESS', () => { - const inputState = { - abcd: ['test', 'testing'], - efgh: ['tests'], - }; - const action = { - type: UserTypes.LOGOUT_SUCCESS, - }; - const expectedState = []; - - const actualState = reducer({matches: inputState}, action); - assert.deepEqual(actualState.matches, expectedState); - }); }); describe('pinned', () => { diff --git a/app/mm-redux/reducers/entities/search.ts b/app/mm-redux/reducers/entities/search.ts index a97e67691..f70227937 100644 --- a/app/mm-redux/reducers/entities/search.ts +++ b/app/mm-redux/reducers/entities/search.ts @@ -1,7 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. import {combineReducers} from 'redux'; -import {PostTypes, PreferenceTypes, SearchTypes, UserTypes} from '@mm-redux/action_types'; +import {PostTypes, PreferenceTypes, SearchTypes} from '@mm-redux/action_types'; import {Preferences} from '../../constants'; import {PreferenceType} from '@mm-redux/types/preferences'; import {GenericAction} from '@mm-redux/types/actions'; @@ -28,7 +28,6 @@ function results(state: Array = [], action: GenericAction) { return state; } case SearchTypes.REMOVE_SEARCH_POSTS: - case UserTypes.LOGOUT_SUCCESS: return []; default: @@ -53,7 +52,6 @@ function matches(state: Dictionary> = {}, action: GenericAction) { return newState; } case SearchTypes.REMOVE_SEARCH_POSTS: - case UserTypes.LOGOUT_SUCCESS: return []; default: @@ -115,7 +113,6 @@ function flagged(state: Array = [], action: GenericAction) { return state; } case SearchTypes.REMOVE_SEARCH_POSTS: - case UserTypes.LOGOUT_SUCCESS: return []; default: @@ -187,8 +184,6 @@ function pinned(state: Dictionary> = {}, action: GenericAction) { return state; } - case UserTypes.LOGOUT_SUCCESS: - return []; default: return state; @@ -232,8 +227,6 @@ function recent(state: Dictionary> = {}, action: GenericAction) { return nextState; } - case UserTypes.LOGOUT_SUCCESS: - return {}; default: return state; @@ -254,8 +247,6 @@ function current(state: any = {}, action: GenericAction) { }, }; } - case UserTypes.LOGOUT_SUCCESS: - return {}; default: return state; diff --git a/app/mm-redux/reducers/entities/teams.ts b/app/mm-redux/reducers/entities/teams.ts index 321468f3a..a2cc2f7e1 100644 --- a/app/mm-redux/reducers/entities/teams.ts +++ b/app/mm-redux/reducers/entities/teams.ts @@ -13,8 +13,6 @@ function currentTeamId(state = '', action: GenericAction) { case TeamTypes.SELECT_TEAM: return action.data; - case UserTypes.LOGOUT_SUCCESS: - return ''; default: return state; } @@ -61,9 +59,6 @@ function teams(state: IDMappedObjects = {}, action: GenericAction) { return {...state, [teamId]: {...team, scheme_id: schemeId}}; } - case UserTypes.LOGOUT_SUCCESS: - return {}; - default: return state; } @@ -248,8 +243,6 @@ function myMembers(state: RelationOneToOne = {}, action: G return nextState; } - case UserTypes.LOGOUT_SUCCESS: - return {}; default: return state; } @@ -328,8 +321,6 @@ function membersInTeam(state: RelationOneToOne { const newState = reducer(state, action); assert.deepEqual(newState.profilesInChannel, expectedState.profilesInChannel); }); - - it('UserTypes.LOGOUT_SUCCESS, existing profiles', () => { - const state = { - profilesInChannel: { - id: new Set().add('old_user_id'), - other_id: new Set().add('other_user_id'), - }, - }; - const action = { - type: UserTypes.LOGOUT_SUCCESS, - }; - const expectedState = { - profilesInChannel: {}, - }; - - const newState = reducer(state, action); - assert.deepEqual(newState.profilesInChannel, expectedState.profilesInChannel); - }); }); describe('profilesNotInChannel', () => { @@ -545,23 +527,5 @@ describe('Reducers.users', () => { const newState = reducer(state, action); assert.deepEqual(newState.profilesNotInChannel, expectedState.profilesNotInChannel); }); - - it('UserTypes.LOGOUT_SUCCESS, existing profiles', () => { - const state = { - profilesNotInChannel: { - id: new Set().add('old_user_id'), - other_id: new Set().add('other_user_id'), - }, - }; - const action = { - type: UserTypes.LOGOUT_SUCCESS, - }; - const expectedState = { - profilesNotInChannel: {}, - }; - - const newState = reducer(state, action); - assert.deepEqual(newState.profilesNotInChannel, expectedState.profilesNotInChannel); - }); }); }); diff --git a/app/mm-redux/reducers/entities/users.ts b/app/mm-redux/reducers/entities/users.ts index 210274a02..e23971043 100644 --- a/app/mm-redux/reducers/entities/users.ts +++ b/app/mm-redux/reducers/entities/users.ts @@ -102,10 +102,7 @@ function currentUserId(state = '', action: GenericAction) { return user ? user.id : state; } - case UserTypes.LOGOUT_SUCCESS: - return ''; } - return state; } @@ -139,9 +136,6 @@ function mySessions(state: Array<{id: string}> = [], action: GenericAction) { case UserTypes.REVOKE_SESSIONS_FOR_ALL_USERS_SUCCESS: return []; - case UserTypes.LOGOUT_SUCCESS: - return []; - default: return state; } @@ -152,9 +146,6 @@ function myAudits(state = [], action: GenericAction) { case UserTypes.RECEIVED_AUDITS: return [...action.data]; - case UserTypes.LOGOUT_SUCCESS: - return []; - default: return state; } @@ -212,8 +203,6 @@ function profiles(state: IDMappedObjects = {}, action: GenericActio return state; } - case UserTypes.LOGOUT_SUCCESS: - return {}; case UserTypes.RECEIVED_TERMS_OF_SERVICE_STATUS: { const data = action.data || action.payload; return { @@ -255,9 +244,6 @@ function profilesInTeam(state: RelationOneToMany = {}, action case UserTypes.RECEIVED_PROFILES_LIST_NOT_IN_TEAM: return removeProfileListFromSet(state, action); - case UserTypes.LOGOUT_SUCCESS: - return {}; - case UserTypes.PROFILE_NO_LONGER_VISIBLE: return removeProfileFromTeams(state, action); @@ -283,9 +269,6 @@ function profilesNotInTeam(state: RelationOneToMany = {}, act case UserTypes.RECEIVED_PROFILES_LIST_IN_TEAM: return removeProfileListFromSet(state, action); - case UserTypes.LOGOUT_SUCCESS: - return {}; - case UserTypes.PROFILE_NO_LONGER_VISIBLE: return removeProfileFromTeams(state, action); @@ -312,9 +295,6 @@ function profilesWithoutTeam(state: Set = new Set(), action: GenericActi nextSet.delete(action.data.id); return nextSet; } - case UserTypes.LOGOUT_SUCCESS: - return new Set(); - default: return state; } @@ -342,9 +322,6 @@ function profilesInChannel(state: RelationOneToMany = {}, user_id: action.data.user_id, }}); - case UserTypes.LOGOUT_SUCCESS: - return {}; - case UserTypes.PROFILE_NO_LONGER_VISIBLE: return removeProfileFromTeams(state, action); @@ -396,9 +373,6 @@ function profilesNotInChannel(state: RelationOneToMany = { user_id: action.data.user_id, }}); - case UserTypes.LOGOUT_SUCCESS: - return {}; - case UserTypes.PROFILE_NO_LONGER_VISIBLE: return removeProfileFromTeams(state, action); @@ -424,8 +398,6 @@ function statuses(state: RelationOneToOne = {}, action: Gen return nextState; } - case UserTypes.LOGOUT_SUCCESS: - return {}; case UserTypes.PROFILE_NO_LONGER_VISIBLE: { if (state[action.data.user_id]) { const newState = {...state}; @@ -483,8 +455,6 @@ function isManualStatus(state: RelationOneToOne = {}, acti return nextState; } - case UserTypes.LOGOUT_SUCCESS: - return {}; case UserTypes.PROFILE_NO_LONGER_VISIBLE: { if (state[action.data.user_id]) { const newState = {...state}; @@ -541,7 +511,6 @@ function myUserAccessTokens(state: any = {}, action: GenericAction) { } case UserTypes.CLEAR_MY_USER_ACCESS_TOKENS: - case UserTypes.LOGOUT_SUCCESS: return {}; default: diff --git a/app/mm-redux/reducers/requests/users.ts b/app/mm-redux/reducers/requests/users.ts index c90ca486f..1d08e694c 100644 --- a/app/mm-redux/reducers/requests/users.ts +++ b/app/mm-redux/reducers/requests/users.ts @@ -20,9 +20,6 @@ function checkMfa(state: RequestStatusType = initialRequestState(), action: Gene case UserTypes.CHECK_MFA_FAILURE: return {...state, status: RequestStatus.FAILURE, error: action.error}; - case UserTypes.LOGOUT_SUCCESS: - return {...state, status: RequestStatus.NOT_STARTED, error: null}; - default: return state; } @@ -39,28 +36,6 @@ function login(state: RequestStatusType = initialRequestState(), action: Generic case UserTypes.LOGIN_FAILURE: return {...state, status: RequestStatus.FAILURE, error: action.error}; - case UserTypes.LOGOUT_SUCCESS: - return {...state, status: RequestStatus.NOT_STARTED, error: null}; - - default: - return state; - } -} - -function logout(state: RequestStatusType = initialRequestState(), action: GenericAction): RequestStatusType { - switch (action.type) { - case UserTypes.LOGOUT_REQUEST: - return {...state, status: RequestStatus.STARTED}; - - case UserTypes.LOGOUT_SUCCESS: - return {...state, status: RequestStatus.SUCCESS, error: null}; - - case UserTypes.LOGOUT_FAILURE: - return {...state, status: RequestStatus.FAILURE, error: action.error}; - - case UserTypes.RESET_LOGOUT_STATE: - return initialRequestState(); - default: return state; } @@ -72,7 +47,7 @@ function autocompleteUsers(state: RequestStatusType = initialRequestState(), act UserTypes.AUTOCOMPLETE_USERS_SUCCESS, UserTypes.AUTOCOMPLETE_USERS_FAILURE, state, - action + action, ); } @@ -82,14 +57,13 @@ function updateMe(state: RequestStatusType = initialRequestState(), action: Gene UserTypes.UPDATE_ME_SUCCESS, UserTypes.UPDATE_ME_FAILURE, state, - action + action, ); } export default (combineReducers({ checkMfa, login, - logout, autocompleteUsers, updateMe, }) as (b: UsersRequestsStatuses, a: GenericAction) => UsersRequestsStatuses); diff --git a/app/mm-redux/reducers/websocket.ts b/app/mm-redux/reducers/websocket.ts index 4be89bad2..daa9e1517 100644 --- a/app/mm-redux/reducers/websocket.ts +++ b/app/mm-redux/reducers/websocket.ts @@ -27,9 +27,5 @@ export default function(state = getInitialState(), action: GenericAction) { }; } - if (action.type === UserTypes.LOGOUT_SUCCESS) { - return getInitialState(); - } - return state; } diff --git a/app/mm-redux/store/configureStore.dev.ts b/app/mm-redux/store/configureStore.dev.ts deleted file mode 100644 index 66adff4f6..000000000 --- a/app/mm-redux/store/configureStore.dev.ts +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -/* eslint-disable no-undefined */ -import * as redux from 'redux'; -import {createOfflineReducer, networkStatusChangedAction, offlineCompose} from 'redux-offline'; -import defaultOfflineConfig from 'redux-offline/lib/defaults'; -import reducerRegistry from './reducer_registry'; -import devTools from 'remote-redux-devtools'; - -const globalAny = global as any; -const window = globalAny.window; - -const devToolsEnhancer = typeof window !== 'undefined' && window.__REDUX_DEVTOOLS_EXTENSION__ ? // eslint-disable-line no-underscore-dangle - window.__REDUX_DEVTOOLS_EXTENSION__ : // eslint-disable-line no-underscore-dangle - () => { - return devTools({ - name: 'Mattermost', - hostname: 'localhost', - port: 5678, - realtime: true, - }); - }; -import serviceReducer from '../reducers'; -import deepFreezeAndThrowOnMutation from '@mm-redux/utils/deep_freeze'; -import initialState from './initial_state'; -import {offlineConfig, createReducer} from './helpers'; -import {createMiddleware} from './middleware'; -import {Reducer, Action} from '@mm-redux/types/actions'; -import {GlobalState} from '@mm-redux/types/store'; - -/** - * 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. - * userOfflineConfig - Any additional configuration data to be passed into redux-offline aside from the default values. - * getAppReducer - A function that returns the appReducer as defined above. Only used in development to enable hot reloading. - * clientOptions - An object containing additional options used when configuring the redux store. The following options are available: - * additionalMiddleware - func | array - Allows for single or multiple additional middleware functions to be passed in from the client side. - * enableBuffer - bool - default = true - If true, the store will buffer all actions until offline state rehydration occurs. - * enableThunk - bool - default = true - If true, include the thunk middleware automatically. If false, thunk must be provided as part of additionalMiddleware. - */ -export default function configureServiceStore(preloadedState: any, appReducer: any, userOfflineConfig: any, getAppReducer: any, clientOptions: any) { - const baseOfflineConfig = Object.assign({}, defaultOfflineConfig, offlineConfig, userOfflineConfig); - const baseState = Object.assign({}, initialState, preloadedState); - - const loadReduxDevtools = process.env.NODE_ENV !== 'test'; //eslint-disable-line no-process-env - - const store = redux.createStore( - createOfflineReducer(createDevReducer(baseState, serviceReducer, appReducer)), - baseState, - offlineCompose(baseOfflineConfig)( - createMiddleware(clientOptions), - loadReduxDevtools ? [devToolsEnhancer()] : [] - ) - ); - - reducerRegistry.setChangeListener((reducers: any) => { - store.replaceReducer(createOfflineReducer(createDevReducer(baseState, reducers))); - }); - - // launch store persistor - if (baseOfflineConfig.persist) { - baseOfflineConfig.persist(store, baseOfflineConfig.persistOptions, baseOfflineConfig.persistCallback); - } - - if (baseOfflineConfig.detectNetwork) { - baseOfflineConfig.detectNetwork((online: boolean) => { - store.dispatch(networkStatusChangedAction(online)); - }); - } - - if ((module as any).hot) { - // Enable Webpack hot module replacement for reducers - (module as any).hot.accept(() => { - const nextServiceReducer = require('../reducers').default; // eslint-disable-line global-require - let nextAppReducer; - if (getAppReducer) { - nextAppReducer = getAppReducer(); // eslint-disable-line global-require - } - store.replaceReducer(createDevReducer(baseState, reducerRegistry.getReducers(), nextServiceReducer, nextAppReducer)); - }); - } - - return store; -} - -function createDevReducer(baseState: any, ...reducers: any) { - return enableFreezing(createReducer(baseState, ...reducers)); -} - -function enableFreezing(reducer: Reducer) { - return (state: GlobalState, action: Action) => { - const nextState = reducer(state, action); - - if (nextState !== state) { - deepFreezeAndThrowOnMutation(nextState); - } - - return nextState; - }; -} diff --git a/app/mm-redux/store/configureStore.prod.ts b/app/mm-redux/store/configureStore.prod.ts deleted file mode 100644 index f1d456386..000000000 --- a/app/mm-redux/store/configureStore.prod.ts +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import * as redux from 'redux'; -import reducerRegistry from './reducer_registry'; - -import serviceReducer from '../reducers'; - -import {offlineConfig, createReducer} from './helpers'; -import initialState from './initial_state'; -import {createMiddleware} from './middleware'; - -import {createOfflineReducer, networkStatusChangedAction, offlineCompose} from 'redux-offline'; -import defaultOfflineConfig from 'redux-offline/lib/defaults'; - -/** - * 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. - * userOfflineConfig - Any additional configuration data to be passed into redux-offline aside from the default values. - * getAppReducer - A function that returns the appReducer as defined above. Only used in development to enable hot reloading. - * clientOptions - An object containing additional options used when configuring the redux store. The following options are available: - * additionalMiddleware - func | array - Allows for single or multiple additional middleware functions to be passed in from the client side. - * enableBuffer - bool - default = true - If true, the store will buffer all actions until offline state rehydration occurs. - * enableThunk - bool - default = true - If true, include the thunk middleware automatically. If false, thunk must be provided as part of additionalMiddleware. - */ -export default function configureOfflineServiceStore(preloadedState: any, appReducer: any, userOfflineConfig: any, getAppReducer: any, clientOptions = {}) { - const baseState = Object.assign({}, initialState, preloadedState); - - const baseOfflineConfig = Object.assign({}, defaultOfflineConfig, offlineConfig, userOfflineConfig); - - const store = redux.createStore( - createOfflineReducer(createReducer(baseState, serviceReducer as any, appReducer)), - baseState, - offlineCompose(baseOfflineConfig)( - createMiddleware(clientOptions), - [] - ) - ); - - reducerRegistry.setChangeListener((reducers: any) => { - store.replaceReducer(createOfflineReducer(createReducer(baseState, reducers))); - }); - - // launch store persistor - if (baseOfflineConfig.persist) { - baseOfflineConfig.persist(store, baseOfflineConfig.persistOptions, baseOfflineConfig.persistCallback); - } - - if (baseOfflineConfig.detectNetwork) { - baseOfflineConfig.detectNetwork((online: boolean) => { - store.dispatch(networkStatusChangedAction(online)); - }); - } - - return store; -} diff --git a/app/mm-redux/store/helpers.ts b/app/mm-redux/store/helpers.ts index 4904ffec6..c578c0094 100644 --- a/app/mm-redux/store/helpers.ts +++ b/app/mm-redux/store/helpers.ts @@ -1,42 +1,38 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. + import {combineReducers} from 'redux'; -import {General} from '../constants'; import reducerRegistry from './reducer_registry'; -import {enableBatching, Action, Reducer} from '@mm-redux/types/actions'; -export const offlineConfig = { - effect: (effect: Function, action: Action) => { - if (typeof effect !== 'function') { - throw new Error('Offline Action: effect must be a function.'); - } else if (!('meta' in action && action.meta && action.meta.offline.commit)) { - throw new Error('Offline Action: commit action must be present.'); - } +import {enableBatching, Reducer} from '@mm-redux/types/actions'; +import AsyncStorage from '@react-native-community/async-storage'; - return effect(); - }, - discard: (error: Error, action: Action, retries: number) => { - if ('meta' in action && action.meta && action.meta.offline.hasOwnProperty('maxRetry')) { - return retries >= action.meta.offline.maxRetry; - } +/* eslint-disable no-console */ - return retries > 10; - }, -}; - -export function createReducer(baseState: any, ...reducers: Reducer[]) { +export function createReducer(...reducers: Reducer[]) { reducerRegistry.setReducers(Object.assign({}, ...reducers)); const baseReducer = combineReducers(reducerRegistry.getReducers()); - // Root reducer wrapper that listens for reset events. - // Returns whatever is passed for the data property - // as the new state. - function offlineReducer(state = {}, action: Action) { - if ('type' in action && 'data' in action && action.type === General.OFFLINE_STORE_RESET) { - return baseReducer(action.data || baseState, action); - } + return enableBatching(baseReducer); +} - return baseReducer(state, action as any); +const KEY_PREFIX = 'reduxPersist:'; + +export async function getStoredState() { + const restoredState: Record = {}; + let storeKeys: Array = []; + + try { + const allKeys: Array = await AsyncStorage.getAllKeys(); + storeKeys = allKeys.filter((key) => key.includes(KEY_PREFIX)); + + const values = await AsyncStorage.multiGet(storeKeys); + + values.forEach(([key, data]: [string, string]) => { + restoredState[key.slice(KEY_PREFIX.length)] = JSON.parse(data); + }); + } catch (error) { + console.log('ERROR GETTING FROM AsyncStorage', error); } - return enableBatching(offlineReducer); -} + return {storeKeys, restoredState}; +} \ No newline at end of file diff --git a/app/mm-redux/store/index.ts b/app/mm-redux/store/index.ts index 2c2a0e8d3..5fa3b4f01 100644 --- a/app/mm-redux/store/index.ts +++ b/app/mm-redux/store/index.ts @@ -1,6 +1,108 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -/* eslint-disable global-require, no-process-env */ -const config = process.env.NODE_ENV === 'production' ? require('./configureStore.prod').default : require('./configureStore.dev').default; -export default config; +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 index efac8c741..b00da8f99 100644 --- a/app/mm-redux/store/initial_state.ts +++ b/app/mm-redux/store/initial_state.ts @@ -205,10 +205,6 @@ const state: GlobalState = { status: 'not_started', error: null, }, - logout: { - status: 'not_started', - error: null, - }, autocompleteUsers: { status: 'not_started', error: null, diff --git a/app/mm-redux/store/middleware.ts b/app/mm-redux/store/middleware.ts index 88a1ca420..39990dbc7 100644 --- a/app/mm-redux/store/middleware.ts +++ b/app/mm-redux/store/middleware.ts @@ -3,20 +3,15 @@ import thunk, {ThunkMiddleware} from 'redux-thunk'; import createActionBuffer from 'redux-action-buffer'; -import {REHYDRATE} from 'redux-persist/constants'; +import {PERSIST} from 'redux-persist'; +import {General} from '@mm-redux/constants'; -const defaultOptions = { - additionalMiddleware: [], - enableBuffer: true, - enableThunk: true, -}; export function createMiddleware(clientOptions: any): ThunkMiddleware[] { - const options = Object.assign({}, defaultOptions, clientOptions); const { additionalMiddleware, enableBuffer, enableThunk, - } = options; + } = clientOptions; const middleware: ThunkMiddleware[] = []; if (enableThunk) { @@ -32,7 +27,7 @@ export function createMiddleware(clientOptions: any): ThunkMiddleware[] { } if (enableBuffer) { - middleware.push(createActionBuffer(REHYDRATE)); + 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 new file mode 100644 index 000000000..2fe360a8e --- /dev/null +++ b/app/mm-redux/store/mmkv_adapter.ts @@ -0,0 +1,142 @@ +// 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/types/module.d.ts b/app/mm-redux/types/module.d.ts index 3dd9c11aa..37097c51a 100644 --- a/app/mm-redux/types/module.d.ts +++ b/app/mm-redux/types/module.d.ts @@ -1,8 +1,6 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. declare module 'gfycat-sdk'; -declare module 'redux-offline'; -declare module 'redux-offline/lib/defaults'; declare module 'remote-redux-devtools'; declare module 'redux-persist'; declare module 'redux-persist/constants'; diff --git a/app/mm-redux/types/requests.ts b/app/mm-redux/types/requests.ts index 5e25e17e5..a00ed204e 100644 --- a/app/mm-redux/types/requests.ts +++ b/app/mm-redux/types/requests.ts @@ -34,7 +34,6 @@ export type TeamsRequestsStatuses = { export type UsersRequestsStatuses = { checkMfa: RequestStatusType; login: RequestStatusType; - logout: RequestStatusType; autocompleteUsers: RequestStatusType; updateMe: RequestStatusType; }; diff --git a/app/reducers/device/connection.js b/app/reducers/device/connection.js index a25f75783..4164308e8 100644 --- a/app/reducers/device/connection.js +++ b/app/reducers/device/connection.js @@ -1,16 +1,12 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {UserTypes} from '@mm-redux/action_types'; -import {DeviceTypes} from 'app/constants'; +import {DeviceTypes} from '@constants'; export default function connection(state = true, action) { switch (action.type) { case DeviceTypes.CONNECTION_CHANGED: return action.data; - - case UserTypes.LOGOUT_SUCCESS: - return true; } return state; diff --git a/app/reducers/device/dimension.js b/app/reducers/device/dimension.js index eea7ce361..66a06debe 100644 --- a/app/reducers/device/dimension.js +++ b/app/reducers/device/dimension.js @@ -3,8 +3,7 @@ import {Dimensions} from 'react-native'; -import {UserTypes} from '@mm-redux/action_types'; -import {DeviceTypes} from 'app/constants'; +import {DeviceTypes} from '@constants'; const {height, width} = Dimensions.get('window'); const initialState = { @@ -21,8 +20,6 @@ export default function dimension(state = initialState, action) { } break; } - case UserTypes.LOGOUT_SUCCESS: - return initialState; } return state; diff --git a/app/reducers/index.js b/app/reducers/index.js index 41af49601..d3acc0d17 100644 --- a/app/reducers/index.js +++ b/app/reducers/index.js @@ -3,12 +3,10 @@ import app from './app'; import device from './device'; -import navigation from './navigation'; import views from './views'; export default { app, device, - navigation, views, }; diff --git a/app/reducers/navigation/index.js b/app/reducers/navigation/index.js deleted file mode 100644 index 23b20ef1d..000000000 --- a/app/reducers/navigation/index.js +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import {NavigationTypes} from 'app/constants'; -import {UserTypes} from '@mm-redux/action_types'; -import EventEmitter from '@mm-redux/utils/event_emitter'; - -export default function(state = '', action) { - switch (action.type) { - case UserTypes.LOGOUT_SUCCESS: - setTimeout(() => { - EventEmitter.emit(NavigationTypes.NAVIGATION_RESET); - }); - break; - } - - return state; -} diff --git a/app/reducers/views/announcement.js b/app/reducers/views/announcement.js index e2be3726a..f5f2575e6 100644 --- a/app/reducers/views/announcement.js +++ b/app/reducers/views/announcement.js @@ -1,16 +1,12 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {UserTypes} from '@mm-redux/action_types'; - -import {ViewTypes} from 'app/constants'; +import {ViewTypes} from '@constants'; export default function banner(state = '', action) { switch (action.type) { case ViewTypes.ANNOUNCEMENT_BANNER: return action.data; - case UserTypes.LOGOUT_SUCCESS: - return ''; default: return state; } diff --git a/app/reducers/views/channel.js b/app/reducers/views/channel.js index e365106bd..cc4dae997 100644 --- a/app/reducers/views/channel.js +++ b/app/reducers/views/channel.js @@ -2,15 +2,14 @@ // See LICENSE.txt for license information. import {combineReducers} from 'redux'; + +import {ViewTypes} from '@constants'; import { ChannelTypes, FileTypes, PostTypes, - UserTypes, } from '@mm-redux/action_types'; -import {ViewTypes} from 'app/constants'; - function displayName(state = '', action) { switch (action.type) { case ViewTypes.SET_CHANNEL_DISPLAY_NAME: @@ -355,9 +354,6 @@ function keepChannelIdAsUnread(state = null, action) { } return state; } - - case UserTypes.LOGOUT_SUCCESS: - return null; default: return state; } diff --git a/app/reducers/views/emoji.js b/app/reducers/views/emoji.js index f32a47a89..53c1ed208 100644 --- a/app/reducers/views/emoji.js +++ b/app/reducers/views/emoji.js @@ -2,16 +2,13 @@ // See LICENSE.txt for license information. import {combineReducers} from 'redux'; -import {UserTypes} from '@mm-redux/action_types'; -import {ViewTypes} from 'app/constants'; +import {ViewTypes} from '@constants'; function emojiPickerCustomPage(state = 0, action) { switch (action.type) { case ViewTypes.INCREMENT_EMOJI_PICKER_PAGE: return state + 1; - case UserTypes.LOGOUT_SUCCESS: - return 0; default: return state; } diff --git a/app/reducers/views/i18n.js b/app/reducers/views/i18n.js index d64683e05..96f0ec830 100644 --- a/app/reducers/views/i18n.js +++ b/app/reducers/views/i18n.js @@ -14,8 +14,6 @@ function locale(state = defaultLocale, action) { const data = action.data || action.payload; return data.locale; } - case UserTypes.LOGOUT_SUCCESS: - return defaultLocale; } return state; diff --git a/app/reducers/views/post.js b/app/reducers/views/post.js index 92bbcc96c..e805928b7 100644 --- a/app/reducers/views/post.js +++ b/app/reducers/views/post.js @@ -31,8 +31,6 @@ function submittedMenuActions(state = {}, action) { return nextState; } - case UserTypes.LOGOUT_SUCCESS: - return {}; default: return state; } diff --git a/app/reducers/views/search.js b/app/reducers/views/search.js index a4645cc70..a5ac96802 100644 --- a/app/reducers/views/search.js +++ b/app/reducers/views/search.js @@ -1,16 +1,13 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {ViewTypes} from 'app/constants'; -import {UserTypes} from '@mm-redux/action_types'; +import {ViewTypes} from '@constants'; export default function search(state = '', action) { switch (action.type) { case ViewTypes.SEARCH_DRAFT_CHANGED: { return action.text; } - case UserTypes.LOGOUT_SUCCESS: - return ''; default: return state; diff --git a/app/store/index.js b/app/store/index.js index ec5b0c483..1d32a81cd 100644 --- a/app/store/index.js +++ b/app/store/index.js @@ -4,6 +4,7 @@ import initialState from 'app/initial_state'; import configureAppStore from './store'; -const store = configureAppStore(initialState); +const {store, persistor} = configureAppStore(initialState); +export {persistor}; export default store; diff --git a/app/store/middleware.js b/app/store/middleware.js index 2b6c86152..75c624d2c 100644 --- a/app/store/middleware.js +++ b/app/store/middleware.js @@ -3,22 +3,14 @@ import {Platform} from 'react-native'; import DeviceInfo from 'react-native-device-info'; -import {batchActions} from 'redux-batched-actions'; -import AsyncStorage from '@react-native-community/async-storage'; -import {purgeStoredState} from 'redux-persist'; +import {REHYDRATE} from 'redux-persist'; -import {NavigationTypes, ViewTypes} from 'app/constants'; +import {ViewTypes} from 'app/constants'; import initialState from 'app/initial_state'; import {throttle} from 'app/utils/general'; -import {General} from '@mm-redux/constants'; -import {ErrorTypes, GeneralTypes} from '@mm-redux/action_types'; -import EventEmitter from '@mm-redux/utils/event_emitter'; - import mattermostBucket from 'app/mattermost_bucket'; -import {getStateForReset} from './utils'; - import { captureException, LOGGER_JAVASCRIPT_WARNING, @@ -35,115 +27,92 @@ const SAVE_STATE_ACTIONS = [ '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. -const saveShareExtensionState = (store) => { +function saveShareExtensionState(store) { return (next) => (action) => { if (SAVE_STATE_ACTIONS.includes(action.type)) { throttle(saveStateToFile(store)); } return next(action); }; -}; +} -const saveStateToFile = async (store) => { - if (Platform.OS === 'ios') { - const state = store.getState(); +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]); - }); + 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]); - }); + 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; + 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 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)); - } - } -}; - -const purgeAppCacheWrapper = (persistConfig) => (store) => { - return (next) => (action) => { - if (action.type === General.OFFLINE_STORE_PURGE) { - purgeStoredState({...persistConfig, storage: AsyncStorage}); - - const state = store.getState(); - const resetState = getStateForReset(initialState, state); - - store.dispatch(batchActions([ - { - type: General.OFFLINE_STORE_RESET, - data: resetState, - }, - { - type: ErrorTypes.RESTORE_ERRORS, - data: [...state.errors], - }, - { - type: GeneralTypes.RECEIVED_APP_DEVICE_TOKEN, - data: state.entities.general.deviceToken, - }, - { - type: GeneralTypes.RECEIVED_APP_CREDENTIALS, - data: { - url: state.entities.general.credentials.url, - }, - }, - { - type: ViewTypes.SERVER_URL_CHANGED, - serverUrl: state.entities.general.credentials.url || state.views.selectServer.serverUrl, - }, - { - type: GeneralTypes.RECEIVED_SERVER_VERSION, - data: state.entities.general.serverVersion, - }, - { - type: General.STORE_REHYDRATION_COMPLETE, - }, - ], 'BATCH_FOR_RESTART')); - - setTimeout(() => { - EventEmitter.emit(NavigationTypes.RESTART_APP); - }, 500); - } - return next(action); - }; -}; - -const messageRetention = (store) => { - return (next) => (action) => { - if (action.type === 'persist/REHYDRATE') { const {app} = action.payload; const {entities, views} = action.payload; @@ -153,25 +122,24 @@ const messageRetention = (store) => { // When a new version of the app has been detected if (!app || !app.version || app.version !== DeviceInfo.getVersion() || app.build !== DeviceInfo.getBuildNumber()) { - return next(resetStateForNewVersion(action)); + 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 - let nextAction; try { - nextAction = cleanUpState(action); + 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); - nextAction = action; } - return next(nextAction); + return next(action); } else if (action.type === ViewTypes.DATA_CLEANUP) { - const nextAction = cleanUpState(action, true); - return next(nextAction); + action.payload = cleanUpState(action.payload, true); + return next(action); } /* Uncomment the following lines to log the actions being dispatched */ @@ -185,10 +153,9 @@ const messageRetention = (store) => { return next(action); }; -}; +} -function resetStateForNewVersion(action) { - const {payload} = action; +function resetStateForNewVersion(payload) { const lastChannelForTeam = getLastChannelForTeam(payload); let general = initialState.entities.general; @@ -317,29 +284,28 @@ function resetStateForNewVersion(action) { }, }; - return { - type: action.type, - payload: nextState, - error: action.error, - }; + return nextState; } function getLastChannelForTeam(payload) { - const lastChannelForTeam = {...payload.views.team.lastChannelForTeam}; - const convertLastChannelForTeam = Object.values(lastChannelForTeam).some((value) => !Array.isArray(value)); + if (payload?.views?.team?.lastChannelForTeam) { + const lastChannelForTeam = {...payload.views.team.lastChannelForTeam}; + const convertLastChannelForTeam = Object.values(lastChannelForTeam).some((value) => !Array.isArray(value)); - if (convertLastChannelForTeam) { - Object.keys(lastChannelForTeam).forEach((id) => { - lastChannelForTeam[id] = [lastChannelForTeam[id]]; - }); + if (convertLastChannelForTeam) { + Object.keys(lastChannelForTeam).forEach((id) => { + lastChannelForTeam[id] = [lastChannelForTeam[id]]; + }); + } + + return lastChannelForTeam; } - return lastChannelForTeam; + return {}; } -export function cleanUpState(action, keepCurrent = false) { - const {payload: resetPayload} = resetStateForNewVersion(action); - const {payload} = action; +export function cleanUpState(payload, keepCurrent = false) { + const resetPayload = resetStateForNewVersion(payload); const {currentChannelId} = payload.entities.channels; const {lastChannelForTeam} = resetPayload.views.team; @@ -470,6 +436,9 @@ export function cleanUpState(action, keepCurrent = false) { ...resetPayload.views.channel, ...payload.views.channel, }, + root: { + hydrationComplete: true, + }, }, websocket: { lastConnectAt: payload.websocket?.lastConnectAt, @@ -479,11 +448,7 @@ export function cleanUpState(action, keepCurrent = false) { nextState.errors = payload.errors; - return { - type: action.type, - payload: nextState, - error: action.error, - }; + return nextState; } // cleanUpPostsInChannel returns a copy of postsInChannel where only the most recent posts in each channel are kept @@ -564,17 +529,3 @@ function removePendingPost(pendingPostIds, id) { pendingPostIds.splice(pendingIndex, 1); } } - -export const middlewares = (persistConfig) => { - const middlewareFunctions = [ - messageRetention, - purgeAppCacheWrapper(persistConfig), - ]; - - if (Platform.OS === 'ios') { - middlewareFunctions.push(saveShareExtensionState); - } - - return middlewareFunctions; -}; - diff --git a/app/store/middleware.test.js b/app/store/middleware.test.js index 38f38668a..c02db6a5d 100644 --- a/app/store/middleware.test.js +++ b/app/store/middleware.test.js @@ -102,13 +102,13 @@ describe('cleanUpState', () => { }, }; - const result = cleanUpState({payload: state}); + const result = cleanUpState(state); - expect(result.payload.entities.posts.posts.post1).toBeDefined(); - expect(result.payload.entities.posts.posts.post2).toBeUndefined(); - expect(result.payload.entities.posts.postsInChannel.channel1).toEqual([{order: ['post1'], recent: true}]); - expect(result.payload.entities.search.results).toEqual(['post1', 'post2']); - expect(result.payload.entities.search.flagged).toEqual(['post1', 'post2', 'post3']); + expect(result.entities.posts.posts.post1).toBeDefined(); + expect(result.entities.posts.posts.post2).toBeUndefined(); + expect(result.entities.posts.postsInChannel.channel1).toEqual([{order: ['post1'], recent: true}]); + expect(result.entities.search.results).toEqual(['post1', 'post2']); + expect(result.entities.search.flagged).toEqual(['post1', 'post2', 'post3']); }); test('should keep failed pending post', () => { @@ -145,11 +145,11 @@ describe('cleanUpState', () => { }, }; - const result = cleanUpState({payload: state}); + const result = cleanUpState(state); - expect(result.payload.entities.posts.pendingPostIds).toEqual(['pending']); - expect(result.payload.entities.posts.posts.pending).toBeDefined(); - expect(result.payload.entities.posts.postsInChannel.channel1).toEqual([{order: ['pending', 'post1', 'post2'], recent: true}]); + expect(result.entities.posts.pendingPostIds).toEqual(['pending']); + expect(result.entities.posts.posts.pending).toBeDefined(); + expect(result.entities.posts.postsInChannel.channel1).toEqual([{order: ['pending', 'post1', 'post2'], recent: true}]); }); test('should remove non-failed pending post', () => { @@ -186,11 +186,11 @@ describe('cleanUpState', () => { }, }; - const result = cleanUpState({payload: state}); + const result = cleanUpState(state); - expect(result.payload.entities.posts.pendingPostIds).toEqual([]); - expect(result.payload.entities.posts.posts.pending).toBeUndefined(); - expect(result.payload.entities.posts.postsInChannel.channel1).toEqual([{order: ['post1', 'post2'], recent: true}]); + expect(result.entities.posts.pendingPostIds).toEqual([]); + expect(result.entities.posts.posts.pending).toBeUndefined(); + expect(result.entities.posts.postsInChannel.channel1).toEqual([{order: ['post1', 'post2'], recent: true}]); }); test('should remove non-existent pending post', () => { @@ -226,10 +226,10 @@ describe('cleanUpState', () => { }, }; - const result = cleanUpState({payload: state}); + const result = cleanUpState(state); - expect(result.payload.entities.posts.pendingPostIds).toEqual([]); - expect(result.payload.entities.posts.postsInChannel.channel1).toEqual([{order: ['post1', 'post2'], recent: true}]); + expect(result.entities.posts.pendingPostIds).toEqual([]); + expect(result.entities.posts.postsInChannel.channel1).toEqual([{order: ['post1', 'post2'], recent: true}]); }); }); diff --git a/app/store/store.js b/app/store/store.js index fbab8f025..9c34b51b6 100644 --- a/app/store/store.js +++ b/app/store/store.js @@ -1,26 +1,21 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import AsyncStorage from '@react-native-community/async-storage'; import {createBlacklistFilter} from 'redux-persist-transform-filter'; -import {createTransform, persistStore} from 'redux-persist'; +import {createTransform} from 'redux-persist'; +import reduxReset from 'redux-reset'; import {General} from '@mm-redux/constants'; -import {getConfig} from '@mm-redux/selectors/entities/general'; import configureStore from '@mm-redux/store'; +import MMKVStorageAdapter from '@mm-redux/store/mmkv_adapter'; import appReducer from 'app/reducers'; -import {getSiteUrl, setSiteUrl} from 'app/utils/image_cache_manager'; import {createSentryMiddleware} from 'app/utils/sentry/middleware'; import {middlewares} from './middleware'; import {createThunkMiddleware} from './thunk'; import {transformSet} from './utils'; -function getAppReducer() { - return require('../../app/reducers'); // eslint-disable-line global-require -} - const usersSetTransform = [ 'profilesInChannel', 'profilesNotInChannel', @@ -56,8 +51,9 @@ const channelViewBlackList = {loading: true, refreshing: true, loadingPosts: tru const channelViewBlackListFilter = createTransform( (inboundState) => { const channel = {}; + const keys = inboundState.channel ? Object.keys(inboundState.channel) : []; - for (const channelKey of Object.keys(inboundState.channel)) { + for (const channelKey of keys) { if (!channelViewBlackList[channelKey]) { channel[channelKey] = inboundState.channel[channelKey]; } @@ -76,8 +72,9 @@ const emojiBlackList = {nonExistentEmoji: true}; const emojiBlackListFilter = createTransform( (inboundState) => { const emojis = {}; + const keys = inboundState.emojis ? Object.keys(inboundState.emojis) : []; - for (const emojiKey of Object.keys(inboundState.emojis)) { + for (const emojiKey of keys) { if (!emojiBlackList[emojiKey]) { emojis[emojiKey] = inboundState.emojis[emojiKey]; } @@ -124,47 +121,16 @@ const setTransformer = createTransform( ); const persistConfig = { - effect: (effect, action) => { - if (typeof effect !== 'function') { - throw new Error('Offline Action: effect must be a function.'); - } else if (!action.meta.offline.commit) { - throw new Error('Offline Action: commit action must be present.'); - } - - return effect(); - }, - persist: (store, options) => { - const persistor = persistStore(store, {storage: AsyncStorage, ...options}, () => { - store.dispatch({ - type: General.STORE_REHYDRATION_COMPLETE, - }); - }); - - store.subscribe(async () => { - const state = store.getState(); - const config = getConfig(state); - - if (getSiteUrl() !== config?.SiteURL) { - setSiteUrl(config.SiteURL); - } - }); - - return persistor; - }, - persistOptions: { - autoRehydrate: { - log: false, - }, - blacklist: ['device', 'navigation', 'offline', 'requests'], - debounce: 500, - transforms: [ - setTransformer, - viewsBlackListFilter, - typingBlackListFilter, - channelViewBlackListFilter, - emojiBlackListFilter, - ], - }, + key: 'root', + storage: MMKVStorageAdapter, + blacklist: ['device', 'navigation', 'offline', 'requests'], + transforms: [ + setTransformer, + viewsBlackListFilter, + typingBlackListFilter, + channelViewBlackListFilter, + emojiBlackListFilter, + ], }; export default function configureAppStore(initialState) { @@ -172,10 +138,11 @@ export default function configureAppStore(initialState) { additionalMiddleware: [ createThunkMiddleware(), createSentryMiddleware(), - ...middlewares(persistConfig), + ...middlewares(), ], enableThunk: false, // We override the default thunk middleware + enhancers: [reduxReset(General.OFFLINE_STORE_PURGE)], }; - return configureStore(initialState, appReducer, persistConfig, getAppReducer, clientOptions); + return configureStore(initialState, appReducer, persistConfig, clientOptions); } diff --git a/app/store/utils.js b/app/store/utils.js index 355616899..e9f43a10c 100644 --- a/app/store/utils.js +++ b/app/store/utils.js @@ -45,14 +45,14 @@ 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) { + if (store.getState().views?.root?.hydrationComplete && !executed) { if (callback && typeof callback === 'function') { executed = true; callback(); } } else { const subscription = () => { - if (store.getState().views.root.hydrationComplete && !executed) { + if (store.getState().views?.root?.hydrationComplete && !executed) { unsubscribeFromStore(); if (callback && typeof callback === 'function') { executed = true; @@ -73,6 +73,7 @@ export function getStateForReset(initialState, currentState) { const resetState = merge(initialState, { entities: { + general: currentState.entities.general, users: { currentUserId, profiles: { @@ -84,6 +85,15 @@ export function getStateForReset(initialState, currentState) { }, preferences, }, + errors: currentState.errors, + views: { + selectServer: { + serverUrl: currentState.views?.selectServer?.serverUrl, + }, + root: { + hydrationComplete: true, + }, + }, }); return resetState; diff --git a/app/store/utils.test.js b/app/store/utils.test.js index a055a8dad..0ab1b163c 100644 --- a/app/store/utils.test.js +++ b/app/store/utils.test.js @@ -52,6 +52,11 @@ describe('getStateForReset', () => { }, }, }, + views: { + selectServer: { + serverUrl: 'localhost:8065', + }, + }, }; it('should keep the current user\'s ID and profile', () => { diff --git a/app/utils/push_notifications.js b/app/utils/push_notifications.js index 1cb556463..c455161b3 100644 --- a/app/utils/push_notifications.js +++ b/app/utils/push_notifications.js @@ -80,13 +80,9 @@ class PushNotificationUtils { if (foreground) { EventEmitter.emit(ViewTypes.NOTIFICATION_IN_APP, notification); } else if (userInteraction && !notification?.data?.localNotification) { - if (getState().views.root.hydrationComplete) { //TODO: Replace when realm is ready + waitForHydration(this.store, () => { this.loadFromNotification(notification); - } else { - waitForHydration(this.store, () => { - this.loadFromNotification(notification); - }); - } + }); } } }; diff --git a/babel.config.js b/babel.config.js index c72284af0..632bec942 100644 --- a/babel.config.js +++ b/babel.config.js @@ -15,6 +15,7 @@ module.exports = { assets: './dist/assets', '@actions': './app/actions', '@constants': './app/constants', + '@i18n': './app/i18n', '@selectors': './app/selectors', '@telemetry': './app/telemetry', '@utils': './app/utils', diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 61fb8529d..968b1f9d4 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -32,6 +32,7 @@ PODS: - libwebp/mux (1.1.0): - libwebp/demux - libwebp/webp (1.1.0) + - MMKV (1.0.24) - Permission-Camera (2.0.10): - RNPermissions - Permission-PhotoLibrary (2.0.10): @@ -212,6 +213,9 @@ PODS: - React - react-native-image-picker (2.3.1): - React + - react-native-mmkv-storage (0.2.2): + - MMKV (= 1.0.24) + - React - react-native-netinfo (4.4.0): - React - react-native-notifications (2.0.6): @@ -338,6 +342,7 @@ DEPENDENCIES: - react-native-document-picker (from `../node_modules/react-native-document-picker`) - react-native-hw-keyboard-event (from `../node_modules/react-native-hw-keyboard-event`) - react-native-image-picker (from `../node_modules/react-native-image-picker`) + - react-native-mmkv-storage (from `../node_modules/react-native-mmkv-storage`) - "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)" - react-native-notifications (from `../node_modules/react-native-notifications`) - react-native-passcode-status (from `../node_modules/react-native-passcode-status`) @@ -378,6 +383,7 @@ SPEC REPOS: https://github.com/cocoapods/specs.git: - boost-for-react-native - libwebp + - MMKV - SDWebImage - SDWebImageWebPCoder - Sentry @@ -434,6 +440,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-hw-keyboard-event" react-native-image-picker: :path: "../node_modules/react-native-image-picker" + react-native-mmkv-storage: + :path: "../node_modules/react-native-mmkv-storage" react-native-netinfo: :path: "../node_modules/@react-native-community/netinfo" react-native-notifications: @@ -509,6 +517,7 @@ SPEC CHECKSUMS: glog: 1f3da668190260b06b429bb211bfbee5cd790c28 jail-monkey: d7c5048b2336f22ee9c9e0efa145f1f917338ea9 libwebp: 946cb3063cea9236285f7e9a8505d806d30e07f3 + MMKV: 758b2edee46b08bdd958db4169191afb9a6d4ebd Permission-Camera: 8f0e5decca5f28f70f28a8dc31f012c9bad40ad8 Permission-PhotoLibrary: b209bf23b784c9e1409a57d81c6d11ab1d3079c1 RCTRequired: b153add4da6e7dbc44aebf93f3cf4fcae392ddf1 @@ -526,6 +535,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-netinfo: 892a5130be97ff8bb69c523739c424a2ffc296d1 react-native-notifications: d5cb54ef8bf3004dcb56c887650dea08ecbddee7 react-native-passcode-status: 88c4f6e074328bc278bd127646b6c694ad5a530a diff --git a/package-lock.json b/package-lock.json index a9275264d..076df787d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6896,16 +6896,6 @@ "node-int64": "^0.4.0" } }, - "buffer": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.4.3.tgz", - "integrity": "sha512-zvj65TkFeIt3i6aj5bIvJDzjjQQGs4o/sNoezg1F1kYap9Nu2jcUdpwzRSJTHMMzG0H7bZkn4rNQpImhuxWX2A==", - "dev": true, - "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4" - } - }, "buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", @@ -7158,12 +7148,6 @@ } } }, - "clone": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", - "integrity": "sha1-0hfR6WERjjrJpLi7oyhVU79kfNs=", - "dev": true - }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -7266,9 +7250,9 @@ }, "dependencies": { "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" } } }, @@ -9801,25 +9785,25 @@ "dependencies": { "abbrev": { "version": "1.1.1", - "resolved": "", + "resolved": false, "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "optional": true }, "ansi-regex": { "version": "2.1.1", - "resolved": "", + "resolved": false, "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "optional": true }, "aproba": { "version": "1.2.0", - "resolved": "", + "resolved": false, "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "optional": true }, "are-we-there-yet": { "version": "1.1.5", - "resolved": "", + "resolved": false, "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", "optional": true, "requires": { @@ -9829,13 +9813,13 @@ }, "balanced-match": { "version": "1.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "optional": true }, "brace-expansion": { "version": "1.1.11", - "resolved": "", + "resolved": false, "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "optional": true, "requires": { @@ -9845,37 +9829,37 @@ }, "chownr": { "version": "1.1.1", - "resolved": "", + "resolved": false, "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", "optional": true }, "code-point-at": { "version": "1.1.0", - "resolved": "", + "resolved": false, "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "optional": true }, "concat-map": { "version": "0.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "optional": true }, "console-control-strings": { "version": "1.1.0", - "resolved": "", + "resolved": false, "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", "optional": true }, "core-util-is": { "version": "1.0.2", - "resolved": "", + "resolved": false, "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "optional": true }, "debug": { "version": "4.1.1", - "resolved": "", + "resolved": false, "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "optional": true, "requires": { @@ -9884,25 +9868,25 @@ }, "deep-extend": { "version": "0.6.0", - "resolved": "", + "resolved": false, "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "optional": true }, "delegates": { "version": "1.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "optional": true }, "detect-libc": { "version": "1.0.3", - "resolved": "", + "resolved": false, "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", "optional": true }, "fs-minipass": { "version": "1.2.5", - "resolved": "", + "resolved": false, "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", "optional": true, "requires": { @@ -9911,13 +9895,13 @@ }, "fs.realpath": { "version": "1.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "optional": true }, "gauge": { "version": "2.7.4", - "resolved": "", + "resolved": false, "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "optional": true, "requires": { @@ -9933,7 +9917,7 @@ }, "glob": { "version": "7.1.3", - "resolved": "", + "resolved": false, "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "optional": true, "requires": { @@ -9947,13 +9931,13 @@ }, "has-unicode": { "version": "2.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", "optional": true }, "iconv-lite": { "version": "0.4.24", - "resolved": "", + "resolved": false, "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "optional": true, "requires": { @@ -9962,7 +9946,7 @@ }, "ignore-walk": { "version": "3.0.1", - "resolved": "", + "resolved": false, "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", "optional": true, "requires": { @@ -9971,7 +9955,7 @@ }, "inflight": { "version": "1.0.6", - "resolved": "", + "resolved": false, "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "optional": true, "requires": { @@ -9981,19 +9965,19 @@ }, "inherits": { "version": "2.0.3", - "resolved": "", + "resolved": false, "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "optional": true }, "ini": { "version": "1.3.5", - "resolved": "", + "resolved": false, "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "optional": true }, "is-fullwidth-code-point": { "version": "1.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "optional": true, "requires": { @@ -10002,13 +9986,13 @@ }, "isarray": { "version": "1.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "optional": true }, "minimatch": { "version": "3.0.4", - "resolved": "", + "resolved": false, "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "optional": true, "requires": { @@ -10017,13 +10001,13 @@ }, "minimist": { "version": "0.0.8", - "resolved": "", + "resolved": false, "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "optional": true }, "minipass": { "version": "2.3.5", - "resolved": "", + "resolved": false, "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", "optional": true, "requires": { @@ -10033,7 +10017,7 @@ }, "minizlib": { "version": "1.2.1", - "resolved": "", + "resolved": false, "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", "optional": true, "requires": { @@ -10042,7 +10026,7 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": "", + "resolved": false, "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "optional": true, "requires": { @@ -10051,13 +10035,13 @@ }, "ms": { "version": "2.1.1", - "resolved": "", + "resolved": false, "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "optional": true }, "needle": { "version": "2.3.0", - "resolved": "", + "resolved": false, "integrity": "sha512-QBZu7aAFR0522EyaXZM0FZ9GLpq6lvQ3uq8gteiDUp7wKdy0lSd2hPlgFwVuW1CBkfEs9PfDQsQzZghLs/psdg==", "optional": true, "requires": { @@ -10068,7 +10052,7 @@ }, "node-pre-gyp": { "version": "0.12.0", - "resolved": "", + "resolved": false, "integrity": "sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==", "optional": true, "requires": { @@ -10086,7 +10070,7 @@ }, "nopt": { "version": "4.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "optional": true, "requires": { @@ -10096,13 +10080,13 @@ }, "npm-bundled": { "version": "1.0.6", - "resolved": "", + "resolved": false, "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==", "optional": true }, "npm-packlist": { "version": "1.4.1", - "resolved": "", + "resolved": false, "integrity": "sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==", "optional": true, "requires": { @@ -10112,7 +10096,7 @@ }, "npmlog": { "version": "4.1.2", - "resolved": "", + "resolved": false, "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "optional": true, "requires": { @@ -10124,19 +10108,19 @@ }, "number-is-nan": { "version": "1.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "optional": true }, "object-assign": { "version": "4.1.1", - "resolved": "", + "resolved": false, "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "optional": true }, "once": { "version": "1.4.0", - "resolved": "", + "resolved": false, "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "optional": true, "requires": { @@ -10145,19 +10129,19 @@ }, "os-homedir": { "version": "1.0.2", - "resolved": "", + "resolved": false, "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "optional": true }, "os-tmpdir": { "version": "1.0.2", - "resolved": "", + "resolved": false, "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "optional": true }, "osenv": { "version": "0.1.5", - "resolved": "", + "resolved": false, "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "optional": true, "requires": { @@ -10167,19 +10151,19 @@ }, "path-is-absolute": { "version": "1.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "optional": true }, "process-nextick-args": { "version": "2.0.0", - "resolved": "", + "resolved": false, "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", "optional": true }, "rc": { "version": "1.2.8", - "resolved": "", + "resolved": false, "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "optional": true, "requires": { @@ -10199,7 +10183,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "", + "resolved": false, "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "optional": true, "requires": { @@ -10214,7 +10198,7 @@ }, "rimraf": { "version": "2.6.3", - "resolved": "", + "resolved": false, "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "optional": true, "requires": { @@ -10223,43 +10207,43 @@ }, "safe-buffer": { "version": "5.1.2", - "resolved": "", + "resolved": false, "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "optional": true }, "safer-buffer": { "version": "2.1.2", - "resolved": "", + "resolved": false, "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "optional": true }, "sax": { "version": "1.2.4", - "resolved": "", + "resolved": false, "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "optional": true }, "semver": { "version": "5.7.0", - "resolved": "", + "resolved": false, "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", "optional": true }, "set-blocking": { "version": "2.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "optional": true }, "signal-exit": { "version": "3.0.2", - "resolved": "", + "resolved": false, "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "optional": true }, "string-width": { "version": "1.0.2", - "resolved": "", + "resolved": false, "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "optional": true, "requires": { @@ -10270,7 +10254,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "", + "resolved": false, "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "optional": true, "requires": { @@ -10279,7 +10263,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "optional": true, "requires": { @@ -10288,13 +10272,13 @@ }, "strip-json-comments": { "version": "2.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "optional": true }, "tar": { "version": "4.4.8", - "resolved": "", + "resolved": false, "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", "optional": true, "requires": { @@ -10309,13 +10293,13 @@ }, "util-deprecate": { "version": "1.0.2", - "resolved": "", + "resolved": false, "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "optional": true }, "wide-align": { "version": "1.1.3", - "resolved": "", + "resolved": false, "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "optional": true, "requires": { @@ -10324,13 +10308,13 @@ }, "wrappy": { "version": "1.0.2", - "resolved": "", + "resolved": false, "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "optional": true }, "yallist": { "version": "3.0.3", - "resolved": "", + "resolved": false, "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", "optional": true } @@ -10435,12 +10419,6 @@ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" }, - "get-params": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/get-params/-/get-params-0.1.2.tgz", - "integrity": "sha1-uuDfq6WIoMYNeDTA2Nwv9g7u8v4=", - "dev": true - }, "get-stream": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", @@ -10931,12 +10909,6 @@ "safer-buffer": ">= 2.1.2 < 3" } }, - "ieee754": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", - "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", - "dev": true - }, "ignore": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", @@ -10973,9 +10945,9 @@ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" }, "in-publish": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.0.tgz", - "integrity": "sha1-4g/146KvwmkDILbcVSaCqcf631E=" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.1.tgz", + "integrity": "sha512-oDM0kUSNFC31ShNxHKUyfZKy8ZeXZBWMjMdZHKLOk13uvT27VTL/QzRGfRUcevJhpkZAvlhPYuXkF7eNWrtyxQ==" }, "indent-string": { "version": "4.0.0", @@ -16858,12 +16830,6 @@ "esprima": "^4.0.0" } }, - "jsan": { - "version": "3.1.13", - "resolved": "https://registry.npmjs.org/jsan/-/jsan-3.1.13.tgz", - "integrity": "sha512-9kGpCsGHifmw6oJet+y8HaCl14y7qgAsxVdV3pCHDySNR3BfDC30zgkssd7x5LRVAT22dnpbe9JdzzmXZnq9/g==", - "dev": true - }, "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", @@ -16977,7 +16943,8 @@ "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true }, "json5": { "version": "2.1.1", @@ -17106,12 +17073,6 @@ "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", "dev": true }, - "linked-list": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/linked-list/-/linked-list-0.1.0.tgz", - "integrity": "sha1-eYsP+X0bkqT9CEgPVa6k6dSdN78=", - "dev": true - }, "load-json-file": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", @@ -17180,11 +17141,6 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" }, - "lodash-es": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.15.tgz", - "integrity": "sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ==" - }, "lodash._baseisequal": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/lodash._baseisequal/-/lodash._baseisequal-3.0.7.tgz", @@ -18357,12 +18313,6 @@ "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", "optional": true }, - "nanoid": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-2.1.7.tgz", - "integrity": "sha512-fmS3qwDldm4bE01HCIRqNk+f255CNjnAoeV3Zzzv0KemObHKqYgirVaZA9DtKcjogicWjYcHkJs4D5A8CjnuVQ==", - "dev": true - }, "nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", @@ -19721,12 +19671,6 @@ "strict-uri-encode": "^2.0.0" } }, - "querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", - "dev": true - }, "querystringify": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", @@ -20310,6 +20254,11 @@ "resolved": "https://registry.npmjs.org/react-native-local-auth/-/react-native-local-auth-1.6.0.tgz", "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==" + }, "react-native-navigation": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/react-native-navigation/-/react-native-navigation-6.3.0.tgz", @@ -20771,29 +20720,6 @@ "resolved": "https://registry.npmjs.org/redux-batched-actions/-/redux-batched-actions-0.4.1.tgz", "integrity": "sha512-r6tLDyBP3U9cXNLEHs0n1mX5TQfmk6xE0Y9uinYZ5HOyAWDgIJxYqRRkU/bC6XrJ4nS7tasNbxaHJHVmf9UdkA==" }, - "redux-devtools-core": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/redux-devtools-core/-/redux-devtools-core-0.2.1.tgz", - "integrity": "sha512-RAGOxtUFdr/1USAvxrWd+Gq/Euzgw7quCZlO5TgFpDfG7rB5tMhZUrNyBjpzgzL2yMk0eHnPYIGm7NkIfRzHxQ==", - "dev": true, - "requires": { - "get-params": "^0.1.2", - "jsan": "^3.1.13", - "lodash": "^4.17.11", - "nanoid": "^2.0.0", - "remotedev-serialize": "^0.1.8" - } - }, - "redux-devtools-instrument": { - "version": "1.9.6", - "resolved": "https://registry.npmjs.org/redux-devtools-instrument/-/redux-devtools-instrument-1.9.6.tgz", - "integrity": "sha512-MwvY4cLEB2tIfWWBzrUR02UM9qRG2i7daNzywRvabOSVdvAY7s9BxSwMmVRH1Y/7QWjplNtOwgT0apKhHg2Qew==", - "dev": true, - "requires": { - "lodash": "^4.2.0", - "symbol-observable": "^1.0.2" - } - }, "redux-mock-store": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/redux-mock-store/-/redux-mock-store-1.5.4.tgz", @@ -20803,23 +20729,10 @@ "lodash.isplainobject": "^4.0.6" } }, - "redux-offline": { - "version": "github:mattermost/redux-offline#885024de96b6ec73650c340c8928066585c413df", - "from": "github:mattermost/redux-offline#885024de96b6ec73650c340c8928066585c413df", - "requires": { - "@react-native-community/netinfo": "^4.1.3", - "redux-persist": "^4.5.0" - } - }, "redux-persist": { - "version": "4.10.2", - "resolved": "https://registry.npmjs.org/redux-persist/-/redux-persist-4.10.2.tgz", - "integrity": "sha512-U+e0ieMGC69Zr72929iJW40dEld7Mflh6mu0eJtVMLGfMq/aJqjxUM1hzyUWMR1VUyAEEdPHuQmeq5ti9krIgg==", - "requires": { - "json-stringify-safe": "^5.0.1", - "lodash": "^4.17.4", - "lodash-es": "^4.17.4" - } + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/redux-persist/-/redux-persist-6.0.0.tgz", + "integrity": "sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==" }, "redux-persist-node-storage": { "version": "2.0.0", @@ -20844,6 +20757,11 @@ "lodash.unset": "^4.5.2" } }, + "redux-reset": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/redux-reset/-/redux-reset-0.3.0.tgz", + "integrity": "sha1-q4B5W0ENWyhomYbqxAy4R6ac0BI=" + }, "redux-thunk": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.3.0.tgz", @@ -20999,29 +20917,6 @@ "es6-error": "^4.0.1" } }, - "remote-redux-devtools": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/remote-redux-devtools/-/remote-redux-devtools-0.5.16.tgz", - "integrity": "sha512-xZ2D1VRIWzat5nsvcraT6fKEX9Cfi+HbQBCwzNnUAM8Uicm/anOc60XGalcaDPrVmLug7nhDl2nimEa3bL3K9w==", - "dev": true, - "requires": { - "jsan": "^3.1.13", - "querystring": "^0.2.0", - "redux-devtools-core": "^0.2.1", - "redux-devtools-instrument": "^1.9.4", - "rn-host-detect": "^1.1.5", - "socketcluster-client": "^14.2.1" - } - }, - "remotedev-serialize": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/remotedev-serialize/-/remotedev-serialize-0.1.8.tgz", - "integrity": "sha512-3YG/FDcOmiK22bl5oMRM8RRnbGrFEuPGjbcDG+z2xi5aQaNQNZ8lqoRnZTwXVfaZtutXuiAQOgPRrogzQk8edg==", - "dev": true, - "requires": { - "jsan": "^3.1.13" - } - }, "remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", @@ -21236,12 +21131,6 @@ } } }, - "rn-host-detect": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/rn-host-detect/-/rn-host-detect-1.2.0.tgz", - "integrity": "sha512-btNg5kzHcjZZ7t7mvvV/4wNJ9e3MPgrWivkRgWURzXL0JJ0pwWlU4zrbmdlz3HHzHOxhBhHB4D+/dbMFfu4/4A==", - "dev": true - }, "rn-placeholder": { "version": "github:mattermost/rn-placeholder#02c629c65d0123a2eee623ada0fd17186415d3c3", "from": "github:mattermost/rn-placeholder#02c629c65d0123a2eee623ada0fd17186415d3c3", @@ -21359,27 +21248,6 @@ "xmlchars": "^2.1.1" } }, - "sc-channel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/sc-channel/-/sc-channel-1.2.0.tgz", - "integrity": "sha512-M3gdq8PlKg0zWJSisWqAsMmTVxYRTpVRqw4CWAdKBgAfVKumFcTjoCV0hYu7lgUXccCtCD8Wk9VkkE+IXCxmZA==", - "dev": true, - "requires": { - "component-emitter": "1.2.1" - } - }, - "sc-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/sc-errors/-/sc-errors-2.0.1.tgz", - "integrity": "sha512-JoVhq3Ud+3Ujv2SIG7W0XtjRHsrNgl6iXuHHsh0s+Kdt5NwI6N2EGAZD4iteitdDv68ENBkpjtSvN597/wxPSQ==", - "dev": true - }, - "sc-formatter": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/sc-formatter/-/sc-formatter-3.0.2.tgz", - "integrity": "sha512-9PbqYBpCq+OoEeRQ3QfFIGE6qwjjBcd2j7UjgDlhnZbtSnuGgHdcRklPKYGuYFH82V/dwd+AIpu8XvA1zqTd+A==", - "dev": true - }, "scheduler": { "version": "0.15.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.15.0.tgz", @@ -21793,32 +21661,6 @@ } } }, - "socketcluster-client": { - "version": "14.3.1", - "resolved": "https://registry.npmjs.org/socketcluster-client/-/socketcluster-client-14.3.1.tgz", - "integrity": "sha512-Sd/T0K/9UlqTfz+HUuFq90dshA5OBJPQbdkRzGtcKIOm52fkdsBTt0FYpiuzzxv5VrU7PWpRm6KIfNXyPwlLpw==", - "dev": true, - "requires": { - "buffer": "^5.2.1", - "clone": "2.1.1", - "component-emitter": "1.2.1", - "linked-list": "0.1.0", - "querystring": "0.2.0", - "sc-channel": "^1.2.0", - "sc-errors": "^2.0.1", - "sc-formatter": "^3.0.1", - "uuid": "3.2.1", - "ws": "7.1.0" - }, - "dependencies": { - "uuid": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", - "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==", - "dev": true - } - } - }, "sort-json": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/sort-json/-/sort-json-2.0.0.tgz", @@ -23015,9 +22857,9 @@ "dev": true }, "webpack-cli": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.10.tgz", - "integrity": "sha512-u1dgND9+MXaEt74sJR4PR7qkPxXUSQ0RXYq8x1L6Jg1MYVEmGPrH6Ah6C4arD4r0J1P5HKjRqpab36k0eIzPqg==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.11.tgz", + "integrity": "sha512-dXlfuml7xvAFwYUPsrtQAA9e4DOe58gnzSxhgrO/ZM/gyXTBowrsYeubyN4mqGhYdpXMFNyQ6emjJS9M7OBd4g==", "dev": true, "requires": { "chalk": "2.4.2", @@ -23119,9 +22961,9 @@ } }, "yargs-parser": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", - "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", "dev": true, "requires": { "camelcase": "^5.0.0", diff --git a/package.json b/package.json index 409864439..2cb3cd563 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "react-native-keychain": "4.0.5", "react-native-linear-gradient": "2.5.6", "react-native-local-auth": "1.6.0", + "react-native-mmkv-storage": "0.2.2", "react-native-navigation": "6.3.0", "react-native-notifications": "2.0.6", "react-native-passcode-status": "1.1.2", @@ -71,9 +72,9 @@ "redux": "4.0.5", "redux-action-buffer": "1.2.0", "redux-batched-actions": "0.4.1", - "redux-offline": "github:mattermost/redux-offline#885024de96b6ec73650c340c8928066585c413df", - "redux-persist": "4.10.2", + "redux-persist": "6.0.0", "redux-persist-transform-filter": "0.0.20", + "redux-reset": "0.3.0", "redux-thunk": "2.3.0", "reselect": "4.0.0", "rn-fetch-blob": "0.12.0", @@ -133,7 +134,6 @@ "react-test-renderer": "16.13.0", "redux-mock-store": "1.5.4", "redux-persist-node-storage": "2.0.0", - "remote-redux-devtools": "0.5.16", "socketcluster": "16.0.1", "ts-jest": "25.2.1", "typescript": "3.8.3", diff --git a/packager/moduleNames.js b/packager/moduleNames.js index 18dadfa71..277d28d06 100644 --- a/packager/moduleNames.js +++ b/packager/moduleNames.js @@ -151,12 +151,10 @@ 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/configureStore.prod.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/store/reducer_registry.ts', 'app/mm-redux/types/actions.ts', 'app/mm-redux/utils/channel_utils.ts', 'app/mm-redux/utils/emoji_utils.ts', @@ -837,19 +835,6 @@ module.exports = [ 'node_modules/react/index.js', 'node_modules/redux-action-buffer/index.js', 'node_modules/redux-batched-actions/lib/index.js', - 'node_modules/redux-offline/lib/actions.js', - 'node_modules/redux-offline/lib/config.js', - 'node_modules/redux-offline/lib/constants.js', - 'node_modules/redux-offline/lib/defaults/batch.js', - 'node_modules/redux-offline/lib/defaults/detectNetwork.native.js', - 'node_modules/redux-offline/lib/defaults/discard.js', - 'node_modules/redux-offline/lib/defaults/effect.js', - 'node_modules/redux-offline/lib/defaults/index.js', - 'node_modules/redux-offline/lib/defaults/persist.native.js', - 'node_modules/redux-offline/lib/defaults/retry.js', - 'node_modules/redux-offline/lib/index.js', - 'node_modules/redux-offline/lib/middleware.js', - 'node_modules/redux-offline/lib/updater.js', 'node_modules/redux-persist-transform-filter/dist/index.js', 'node_modules/redux-persist/lib/autoRehydrate.js', 'node_modules/redux-persist/lib/constants.js', diff --git a/packager/modulePaths.js b/packager/modulePaths.js index 24017d6d8..554863609 100644 --- a/packager/modulePaths.js +++ b/packager/modulePaths.js @@ -150,12 +150,10 @@ 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/configureStore.prod.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/store/reducer_registry.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', @@ -831,19 +829,6 @@ module.exports = [ './node_modules/node_modules/react/index.js', './node_modules/node_modules/redux-action-buffer/index.js', './node_modules/node_modules/redux-batched-actions/lib/index.js', - './node_modules/node_modules/redux-offline/lib/actions.js', - './node_modules/node_modules/redux-offline/lib/config.js', - './node_modules/node_modules/redux-offline/lib/constants.js', - './node_modules/node_modules/redux-offline/lib/defaults/batch.js', - './node_modules/node_modules/redux-offline/lib/defaults/detectNetwork.native.js', - './node_modules/node_modules/redux-offline/lib/defaults/discard.js', - './node_modules/node_modules/redux-offline/lib/defaults/effect.js', - './node_modules/node_modules/redux-offline/lib/defaults/index.js', - './node_modules/node_modules/redux-offline/lib/defaults/persist.native.js', - './node_modules/node_modules/redux-offline/lib/defaults/retry.js', - './node_modules/node_modules/redux-offline/lib/index.js', - './node_modules/node_modules/redux-offline/lib/middleware.js', - './node_modules/node_modules/redux-offline/lib/updater.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', diff --git a/test/setup.js b/test/setup.js index 107437666..fd87fd1c4 100644 --- a/test/setup.js +++ b/test/setup.js @@ -87,6 +87,10 @@ jest.doMock('react-native', () => { pick: jest.fn(), }, RNPermissions: {}, + RNFastStorage: { + setupLibrary: jest.fn(), + setStringAsync: jest.fn(), + }, }; return Object.setPrototypeOf({ diff --git a/test/test_store.js b/test/test_store.js index 4f53732fa..753efbf76 100644 --- a/test/test_store.js +++ b/test/test_store.js @@ -1,7 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. import {AsyncNodeStorage} from 'redux-persist-node-storage'; -import {createTransform, persistStore} from 'redux-persist'; +import {createTransform} from 'redux-persist'; import configureStore from '@mm-redux/store'; @@ -11,29 +11,16 @@ export default async function testConfigureStore(preloadedState) { () => ({}), ); - const offlineConfig = { - detectNetwork: (callback) => callback(true), - persist: (store, options) => { - return persistStore(store, {storage: new AsyncNodeStorage('./.tmp'), ...options}); - }, - persistOptions: { - debounce: 1000, - transforms: [ - storageTransform, - ], - whitelist: [], - }, - retry: (action, retries) => 200 * (retries + 1), - discard: (error, action, retries) => { - if (action.meta && action.meta.offline.hasOwnProperty('maxRetry')) { - return retries >= action.meta.offline.maxRetry; - } - - return retries >= 1; - }, + const persistConfig = { + key: 'root', + storage: new AsyncNodeStorage('./.tmp'), + whitelist: [], + transforms: [ + storageTransform, + ], }; - const store = configureStore(preloadedState, {}, offlineConfig, () => ({}), {enableBuffer: false}); + const {store} = configureStore(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 ddb9fc851..8779e1a2d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -34,6 +34,7 @@ "@actions/*": ["app/actions/*"], "@constants/*": ["app/constants/*"], "@constants": ["app/constants/index"], + "@i18n": ["app/i18n/index"], "@selectors/*": ["app/selectors/*"], "@telemetry/*": ["/app/telemetry/*"], "@utils/*": ["app/utils/*"],