[MM-24426] [MM-24451] Set previous app version in redux store (#4197)
* Set previous app version in redux store * Handle setting previousVersion on login and clearing data * Set previous version on logout instead * Remove action arg
This commit is contained in:
parent
b5ef54527f
commit
135dc8c9bd
10 changed files with 174 additions and 28 deletions
|
|
@ -276,6 +276,11 @@ class GlobalEventHandler {
|
|||
const state = Store.redux.getState();
|
||||
const newState = {
|
||||
...initialState,
|
||||
app: {
|
||||
build: DeviceInfo.getBuildNumber(),
|
||||
version: DeviceInfo.getVersion(),
|
||||
previousVersion: state.app?.previousVersion || DeviceInfo.getVersion(),
|
||||
},
|
||||
entities: {
|
||||
...initialState.entities,
|
||||
general: {
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import Store from '@store/store';
|
|||
import {waitForHydration} from '@store/utils';
|
||||
import {validatePreviousVersion} from '@utils/general';
|
||||
import pushNotificationsUtils from '@utils/push_notifications';
|
||||
import {captureJSException} from '@utils/sentry';
|
||||
|
||||
const init = async () => {
|
||||
const credentials = await getAppCredentials();
|
||||
|
|
@ -55,11 +56,18 @@ const launchApp = (credentials) => {
|
|||
'start:channel_screen',
|
||||
]);
|
||||
|
||||
const store = Store.redux;
|
||||
if (credentials) {
|
||||
waitForHydration(Store.redux, async () => {
|
||||
if (validatePreviousVersion(Store.redux, EphemeralStore.prevAppVersion)) {
|
||||
Store.redux.dispatch(loadMe());
|
||||
waitForHydration(store, async () => {
|
||||
const {previousVersion} = store.getState().app;
|
||||
const valid = validatePreviousVersion(previousVersion);
|
||||
if (valid) {
|
||||
store.dispatch(loadMe());
|
||||
resetToChannel({skipMetrics: true});
|
||||
} else {
|
||||
const error = new Error(`Previous app version "${previousVersion}" is invalid.`);
|
||||
captureJSException(error, false, store);
|
||||
store.dispatch(logout());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
|
|
@ -71,7 +79,7 @@ const launchApp = (credentials) => {
|
|||
|
||||
Linking.getInitialURL().then((url) => {
|
||||
if (url) {
|
||||
Store.redux.dispatch(setDeepLinkURL(url));
|
||||
store.dispatch(setDeepLinkURL(url));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,8 +5,10 @@ import {combineReducers} from 'redux';
|
|||
|
||||
import build from './build';
|
||||
import version from './version';
|
||||
import previousVersion from './previousVersion';
|
||||
|
||||
export default combineReducers({
|
||||
build,
|
||||
version,
|
||||
previousVersion,
|
||||
});
|
||||
|
|
|
|||
6
app/reducers/app/previousVersion.js
Normal file
6
app/reducers/app/previousVersion.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
export default function previousVersion(state = '') {
|
||||
return state;
|
||||
}
|
||||
|
|
@ -101,8 +101,7 @@ export function resetStateForNewVersion(payload) {
|
|||
rehydrated: true,
|
||||
},
|
||||
app: {
|
||||
build: DeviceInfo.getBuildNumber(),
|
||||
version: DeviceInfo.getVersion(),
|
||||
...payload.app,
|
||||
},
|
||||
entities: {
|
||||
channels,
|
||||
|
|
|
|||
|
|
@ -3,11 +3,8 @@
|
|||
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
import {REHYDRATE} from 'redux-persist';
|
||||
import semver from 'semver/preload';
|
||||
|
||||
import {ViewTypes} from '@constants';
|
||||
import {General} from '@mm-redux/constants';
|
||||
import EphemeralStore from '@store/ephemeral_store';
|
||||
import {
|
||||
captureException,
|
||||
LOGGER_JAVASCRIPT_WARNING,
|
||||
|
|
@ -20,17 +17,7 @@ export default function messageRetention(store) {
|
|||
if (action.type === REHYDRATE) {
|
||||
if (!action.payload) {
|
||||
// On first run payload is not set (when installed)
|
||||
const version = DeviceInfo.getVersion();
|
||||
const major = semver.major(version);
|
||||
const minor = semver.minor(version);
|
||||
const patch = semver.patch(version);
|
||||
const prevAppVersion = `${major}.${parseInt(minor, 10) - 1}.${patch}`;
|
||||
EphemeralStore.prevAppVersion = prevAppVersion;
|
||||
action.payload = {
|
||||
app: {
|
||||
build: DeviceInfo.getBuildNumber(),
|
||||
version,
|
||||
},
|
||||
_persist: {
|
||||
rehydrated: true,
|
||||
},
|
||||
|
|
@ -40,16 +27,26 @@ export default function messageRetention(store) {
|
|||
const {app} = action.payload;
|
||||
const {entities, views} = action.payload;
|
||||
|
||||
if (!EphemeralStore.prevAppVersion) {
|
||||
EphemeralStore.prevAppVersion = app?.version;
|
||||
}
|
||||
const build = DeviceInfo.getBuildNumber();
|
||||
const version = DeviceInfo.getVersion();
|
||||
const previousVersion = app?.version;
|
||||
const previousBuild = app?.build;
|
||||
|
||||
action.payload = {
|
||||
...action.payload,
|
||||
app: {
|
||||
build,
|
||||
version,
|
||||
previousVersion,
|
||||
},
|
||||
};
|
||||
|
||||
if (!entities || !views) {
|
||||
return next(action);
|
||||
}
|
||||
|
||||
// When a new version of the app has been detected
|
||||
if (!app || !app.version || app.version !== DeviceInfo.getVersion() || app.build !== DeviceInfo.getBuildNumber()) {
|
||||
if (previousVersion !== version || previousBuild !== build) {
|
||||
action.payload = resetStateForNewVersion(action.payload);
|
||||
return next(action);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,10 @@
|
|||
|
||||
/* eslint-disable max-nested-callbacks */
|
||||
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
|
||||
import assert from 'assert';
|
||||
import {REHYDRATE} from 'redux-persist';
|
||||
|
||||
import {ViewTypes} from '@constants';
|
||||
import {
|
||||
|
|
@ -55,6 +58,122 @@ describe('messageRetention', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('should add build, version, and previousVersion to payload.app on persist/REHYDRATE', () => {
|
||||
const next = (a) => a;
|
||||
const store = {};
|
||||
const build = 'build';
|
||||
const version = 'version';
|
||||
const previousBuild = 'previous-build';
|
||||
const previousVersion = 'previous-version';
|
||||
DeviceInfo.getBuildNumber = jest.fn().mockReturnValue('build');
|
||||
DeviceInfo.getVersion = jest.fn().mockReturnValue('version');
|
||||
const rehydrateAction = {
|
||||
type: REHYDRATE,
|
||||
payload: {
|
||||
app: {
|
||||
build: previousBuild,
|
||||
version: previousVersion,
|
||||
},
|
||||
},
|
||||
};
|
||||
const expectedPayloadApp = {
|
||||
build,
|
||||
version,
|
||||
previousVersion,
|
||||
};
|
||||
const entities = {
|
||||
channels: {},
|
||||
posts: {},
|
||||
};
|
||||
const views = {
|
||||
team: {
|
||||
lastChannelForTeam: {},
|
||||
},
|
||||
};
|
||||
|
||||
test('when entities is missing', () => {
|
||||
const action = {...rehydrateAction};
|
||||
|
||||
const nextAction = messageRetention(store)(next)(action);
|
||||
expect(nextAction.payload.app).toStrictEqual(expectedPayloadApp);
|
||||
});
|
||||
|
||||
test('when views is missing', () => {
|
||||
const action = {
|
||||
...rehydrateAction,
|
||||
payload: {
|
||||
...rehydrateAction.payload,
|
||||
entities,
|
||||
},
|
||||
};
|
||||
|
||||
const nextAction = messageRetention(store)(next)(action);
|
||||
expect(nextAction.payload.app).toStrictEqual(expectedPayloadApp);
|
||||
});
|
||||
|
||||
test('when previousVersion !== version', () => {
|
||||
const action = {
|
||||
...rehydrateAction,
|
||||
payload: {
|
||||
...rehydrateAction.payload,
|
||||
entities,
|
||||
views,
|
||||
},
|
||||
};
|
||||
expect(action.payload.app.version).not.toEqual(DeviceInfo.getVersion());
|
||||
|
||||
const nextAction = messageRetention(store)(next)(action);
|
||||
expect(nextAction.payload.app).toStrictEqual(expectedPayloadApp);
|
||||
});
|
||||
|
||||
test('when previousBuild !== build', () => {
|
||||
const action = {
|
||||
...rehydrateAction,
|
||||
payload: {
|
||||
...rehydrateAction.payload,
|
||||
app: {
|
||||
...rehydrateAction.payload.app,
|
||||
version: DeviceInfo.getVersion(),
|
||||
},
|
||||
entities,
|
||||
views,
|
||||
},
|
||||
};
|
||||
expect(action.payload.app.version).toEqual(DeviceInfo.getVersion());
|
||||
expect(action.payload.app.build).not.toEqual(DeviceInfo.getBuildNumber());
|
||||
|
||||
const nextAction = messageRetention(store)(next)(action);
|
||||
expect(nextAction.payload.app).toStrictEqual({
|
||||
...expectedPayloadApp,
|
||||
previousVersion: DeviceInfo.getVersion(),
|
||||
});
|
||||
});
|
||||
|
||||
test('when cleanUpState', () => {
|
||||
const action = {
|
||||
...rehydrateAction,
|
||||
payload: {
|
||||
...rehydrateAction.payload,
|
||||
app: {
|
||||
...rehydrateAction.payload.app,
|
||||
version: DeviceInfo.getVersion(),
|
||||
build: DeviceInfo.getBuildNumber(),
|
||||
},
|
||||
entities,
|
||||
views,
|
||||
},
|
||||
};
|
||||
expect(action.payload.app.version).toEqual(DeviceInfo.getVersion());
|
||||
expect(action.payload.app.build).toEqual(DeviceInfo.getBuildNumber());
|
||||
|
||||
const nextAction = messageRetention(store)(next)(action);
|
||||
expect(nextAction.payload.app).toStrictEqual({
|
||||
...expectedPayloadApp,
|
||||
previousVersion: DeviceInfo.getVersion(),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('cleanUpState', () => {
|
||||
|
|
|
|||
|
|
@ -73,12 +73,14 @@ export function waitForHydration(store, callback) {
|
|||
}
|
||||
|
||||
export function getStateForReset(initialState, currentState) {
|
||||
const {app} = currentState;
|
||||
const {currentUserId} = currentState.entities.users;
|
||||
const currentUserProfile = currentState.entities.users.profiles[currentUserId];
|
||||
const {currentTeamId} = currentState.entities.teams;
|
||||
const preferences = currentState.entities.preferences;
|
||||
|
||||
const resetState = merge(initialState, {
|
||||
app,
|
||||
entities: {
|
||||
general: currentState.entities.general,
|
||||
users: {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,11 @@ describe('getStateForReset', () => {
|
|||
const otherUserId = 'other-user-id';
|
||||
const currentTeamId = 'current-team-id';
|
||||
const currentState = {
|
||||
app: {
|
||||
build: 'build',
|
||||
version: 'version',
|
||||
previousVersion: 'previousVersion',
|
||||
},
|
||||
entities: {
|
||||
users: {
|
||||
currentUserId,
|
||||
|
|
@ -80,4 +85,10 @@ describe('getStateForReset', () => {
|
|||
const themeKeys = preferenceKeys.filter((key) => key.startsWith('theme--'));
|
||||
expect(themeKeys.length).toEqual(2);
|
||||
});
|
||||
|
||||
it('should keep app', () => {
|
||||
const resetState = getStateForReset(initialState, currentState);
|
||||
const {app} = resetState;
|
||||
expect(app).toStrictEqual(currentState.app);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@ import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
|
|||
|
||||
import {Posts} from '@mm-redux/constants';
|
||||
|
||||
import {logout} from 'app/actions/views/user';
|
||||
|
||||
const INVALID_VERSIONS = ['1.29.0'];
|
||||
|
||||
export function fromAutoResponder(post) {
|
||||
|
|
@ -87,9 +85,8 @@ export function isPendingPost(postId, userId) {
|
|||
return postId.startsWith(userId);
|
||||
}
|
||||
|
||||
export function validatePreviousVersion(store, version) {
|
||||
if (INVALID_VERSIONS.includes(version)) {
|
||||
store.dispatch(logout());
|
||||
export function validatePreviousVersion(previousVersion) {
|
||||
if (!previousVersion || INVALID_VERSIONS.includes(previousVersion)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue