lbry-desktop/ui/redux/middleware/shared-state.js
infinite-persistence d7e3127e65 Sync: handle fast-actions being reverted
## 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.
2021-12-29 10:32:38 -05:00

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 });
};