diff --git a/flow-typed/sync.js b/flow-typed/sync.js index b30603340..cd0a323ef 100644 --- a/flow-typed/sync.js +++ b/flow-typed/sync.js @@ -11,6 +11,7 @@ declare type SyncState = { setSyncIsPending: boolean, prefsReady: boolean, syncLocked: boolean, + sharedStateSyncId: number, hashChanged: boolean, fatalError: boolean, }; diff --git a/ui/constants/action_types.js b/ui/constants/action_types.js index 148175c6d..4f029e169 100644 --- a/ui/constants/action_types.js +++ b/ui/constants/action_types.js @@ -451,6 +451,7 @@ export const SYNC_APPLY_BAD_PASSWORD = 'SYNC_APPLY_BAD_PASSWORD'; export const SYNC_RESET = 'SYNC_RESET'; export const SYNC_FATAL_ERROR = 'SYNC_FATAL_ERROR'; export const USER_STATE_POPULATE = 'USER_STATE_POPULATE'; +export const SHARED_STATE_SYNC_ID_CHANGED = 'SHARED_STATE_SYNC_ID_CHANGED'; export const REACTIONS_LIST_STARTED = 'REACTIONS_LIST_STARTED'; export const REACTIONS_LIST_FAILED = 'REACTIONS_LIST_FAILED'; diff --git a/ui/redux/actions/app.js b/ui/redux/actions/app.js index 61da1cdf8..44afb1ee8 100644 --- a/ui/redux/actions/app.js +++ b/ui/redux/actions/app.js @@ -42,7 +42,7 @@ import { } from 'redux/selectors/app'; import { selectDaemonSettings, selectClientSetting } from 'redux/selectors/settings'; import { selectUser, selectUserVerifiedEmail } from 'redux/selectors/user'; -import { doSetPrefsReady, doPreferenceGet, doPopulateSharedUserState } from 'redux/actions/sync'; +import { doSetPrefsReady, doPreferenceGet, doPopulateSharedUserState, syncInvalidated } from 'redux/actions/sync'; import { doAuthenticate } from 'redux/actions/user'; import { lbrySettings as config, version as appVersion } from 'package.json'; import analytics, { SHARE_INTERNAL } from 'analytics'; @@ -596,7 +596,7 @@ export function doToggle3PAnalytics(allowParam, doNotDispatch) { }; } -export function doGetAndPopulatePreferences() { +export function doGetAndPopulatePreferences(syncId /* ?: number */) { const { SDK_SYNC_KEYS } = SHARED_PREFERENCES; return (dispatch, getState) => { @@ -615,7 +615,10 @@ export function doGetAndPopulatePreferences() { const successState = getState(); const daemonSettings = selectDaemonSettings(successState); if (savedPreferences !== null) { - dispatch(doPopulateSharedUserState(savedPreferences)); + if (!syncInvalidated(getState, syncId)) { + dispatch(doPopulateSharedUserState(savedPreferences)); + } + // @if TARGET='app' const { settings } = savedPreferences.value; @@ -660,11 +663,15 @@ export function doGetAndPopulatePreferences() { }; } -export function doHandleSyncComplete(error, hasNewData) { - return (dispatch) => { +export function doHandleSyncComplete(error, hasNewData, syncId) { + return (dispatch, getState) => { if (!error) { if (hasNewData) { - dispatch(doGetAndPopulatePreferences()); + if (syncInvalidated(getState, syncId)) { + return; + } + + dispatch(doGetAndPopulatePreferences(syncId)); // we just got sync data, better update our channels dispatch(doFetchChannelListMine()); } diff --git a/ui/redux/actions/sync.js b/ui/redux/actions/sync.js index aa76f22a7..727cffcac 100644 --- a/ui/redux/actions/sync.js +++ b/ui/redux/actions/sync.js @@ -21,6 +21,19 @@ const SYNC_INTERVAL = 1000 * 60 * 5; // 5 minutes const NO_WALLET_ERROR = 'no wallet found for this user'; const BAD_PASSWORD_ERROR_NAME = 'InvalidPasswordError'; +/** + * Checks if there is a newer sync session, indicating that fetched data from + * the current one can be dropped. + * + * @param getState + * @param syncId [Optional] The id of the current sync session. If not given, assume not invalidated. + * @returns {boolean} + */ +export function syncInvalidated(getState: GetState, syncId?: number) { + const state = getState(); + return syncId && state.sync.sharedStateSyncId !== syncId; +} + export function doSetDefaultAccount(success: () => void, failure: (string) => void) { return (dispatch: Dispatch) => { dispatch({ @@ -115,7 +128,14 @@ export const doGetSyncDesktop = (cb?: (any, any) => void, password?: string) => }); }; -export function doSyncLoop(noInterval?: boolean) { +/** + * doSyncLoop + * + * @param noInterval + * @param syncId Optional ID to identify a specific loop. Can be used to abort the loop, for example. + * @returns {(function(Dispatch, GetState): void)|*} + */ +export function doSyncLoop(noInterval?: boolean, syncId?: number) { return (dispatch: Dispatch, getState: GetState) => { if (!noInterval && syncTimer) clearInterval(syncTimer); const state = getState(); @@ -123,7 +143,7 @@ export function doSyncLoop(noInterval?: boolean) { const syncEnabled = selectClientSetting(state, SETTINGS.ENABLE_SYNC); const syncLocked = selectSyncIsLocked(state); if (hasVerifiedEmail && syncEnabled && !syncLocked) { - dispatch(doGetSyncDesktop((error, hasNewData) => dispatch(doHandleSyncComplete(error, hasNewData)))); + dispatch(doGetSyncDesktop((error, hasNewData) => dispatch(doHandleSyncComplete(error, hasNewData, syncId)))); if (!noInterval) { syncTimer = setInterval(() => { const state = getState(); diff --git a/ui/redux/middleware/shared-state.js b/ui/redux/middleware/shared-state.js index 5bac9231f..91a608f1f 100644 --- a/ui/redux/middleware/shared-state.js +++ b/ui/redux/middleware/shared-state.js @@ -1,4 +1,5 @@ // @flow +import * as ACTIONS from 'constants/action_types'; import isEqual from 'util/deep-equal'; import { doPreferenceSet } from 'redux/actions/sync'; @@ -47,10 +48,12 @@ export const buildSharedStateMiddleware = ( if (sharedStateCb) { // Pass dispatch to the callback to consumers can dispatch actions in response to preference set - sharedStateCb({ dispatch, getState }); + 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 }); }; diff --git a/ui/redux/reducers/sync.js b/ui/redux/reducers/sync.js index 0e0e55a93..1547f4d94 100644 --- a/ui/redux/reducers/sync.js +++ b/ui/redux/reducers/sync.js @@ -16,6 +16,7 @@ const defaultState: SyncState = { setSyncIsPending: false, prefsReady: false, syncLocked: false, + sharedStateSyncId: -1, hashChanged: false, fatalError: false, }; @@ -114,6 +115,10 @@ reducers[ACTIONS.SYNC_FATAL_ERROR] = (state: SyncState) => { reducers[ACTIONS.SYNC_RESET] = () => defaultState; +reducers[ACTIONS.SHARED_STATE_SYNC_ID_CHANGED] = (state: SyncState, action: any) => { + return { ...state, sharedStateSyncId: action.data }; +}; + export default function syncReducer(state: SyncState = defaultState, action: any) { const handler = reducers[action.type]; if (handler) return handler(state, action); diff --git a/ui/store.js b/ui/store.js index 13f9e24a8..a26c57905 100644 --- a/ui/store.js +++ b/ui/store.js @@ -185,8 +185,8 @@ const sharedStateFilters = { unpublishedCollections: { source: 'collections', property: 'unpublished' }, }; -const sharedStateCb = ({ dispatch, getState }) => { - dispatch(doSyncLoop()); +const sharedStateCb = ({ dispatch, getState, syncId }) => { + dispatch(doSyncLoop(false, syncId)); }; const populateAuthTokenHeader = () => {