d7e3127e65
## Repro 1. Follow a channel. 2. When `preference_set` is sent, unfollow the channel. 3. A few seconds later, the final setting reflects (1) instead of (2). The current sync loop involves doing a final `sync/get` at the end. While not necessary for the scenario above, the code flow covers various cases, so it's still needed for now. ## Approach Implement an abort mechanism to the sync-loop. When syncing from the `buildSharedStateMiddleware` loop, generate an ID for each sync session, and only store the latest one. Pass the ID to the completion-callback (and other places as needed), so we can check if our session is still the latest one before executing the callback. The check for invalidation can also be placed in more places to cut off the sync process earlier, but it's only done for 2 critical places for now.
59 lines
2.3 KiB
JavaScript
59 lines
2.3 KiB
JavaScript
// @flow
|
|
import * as ACTIONS from 'constants/action_types';
|
|
import isEqual from 'util/deep-equal';
|
|
import { doPreferenceSet } from 'redux/actions/sync';
|
|
|
|
const RUN_PREFERENCES_DELAY_MS = 2000;
|
|
const SHARED_PREFERENCE_VERSION = '0.1';
|
|
let oldShared = {};
|
|
let timeout;
|
|
export const buildSharedStateMiddleware = (
|
|
actions: Array<string>,
|
|
sharedStateFilters: {},
|
|
sharedStateCb?: (any) => void
|
|
) => ({ getState, dispatch }: { getState: () => { user: any, settings: any }, dispatch: (any) => void }) => (
|
|
next: ({}) => void
|
|
) => (action: { type: string, data: any }) => {
|
|
// We don't care if sync is disabled here, we always want to backup preferences to the wallet
|
|
if (!actions.includes(action.type) || typeof action === 'function') {
|
|
return next(action);
|
|
}
|
|
clearTimeout(timeout);
|
|
const actionResult = next(action);
|
|
// Call `getState` after calling `next` to ensure the state has updated in response to the action
|
|
function runPreferences() {
|
|
const nextState: { user: any, settings: any } = getState();
|
|
const syncEnabled =
|
|
nextState.settings && nextState.settings.clientSettings && nextState.settings.clientSettings.enable_sync;
|
|
const hasVerifiedEmail = nextState.user && nextState.user.user && nextState.user.user.has_verified_email;
|
|
const preferenceKey = syncEnabled && hasVerifiedEmail ? 'shared' : 'local';
|
|
const shared = {};
|
|
|
|
Object.keys(sharedStateFilters).forEach((key) => {
|
|
const filter = sharedStateFilters[key];
|
|
const { source, property, transform } = filter;
|
|
let value = nextState[source][property];
|
|
if (transform) {
|
|
value = transform(value);
|
|
}
|
|
|
|
shared[key] = value;
|
|
});
|
|
|
|
if (!isEqual(oldShared, shared)) {
|
|
// only update if the preference changed from last call in the same session
|
|
oldShared = shared;
|
|
dispatch(doPreferenceSet(preferenceKey, shared, SHARED_PREFERENCE_VERSION));
|
|
}
|
|
|
|
if (sharedStateCb) {
|
|
// Pass dispatch to the callback to consumers can dispatch actions in response to preference set
|
|
sharedStateCb({ dispatch, getState, syncId: timeout });
|
|
}
|
|
clearTimeout(timeout);
|
|
return actionResult;
|
|
}
|
|
|
|
timeout = setTimeout(runPreferences, RUN_PREFERENCES_DELAY_MS);
|
|
dispatch({ type: ACTIONS.SHARED_STATE_SYNC_ID_CHANGED, data: timeout });
|
|
};
|