bring in sync code from lbryinc

This commit is contained in:
Sean Yesmunt 2020-10-02 11:03:25 -04:00
parent 19664726d6
commit f54a0de797
13 changed files with 423 additions and 19 deletions

View file

@ -1,6 +1,7 @@
import { hot } from 'react-hot-loader/root';
import { connect } from 'react-redux';
import { selectGetSyncErrorMessage, selectUploadCount } from 'lbryinc';
import { selectUploadCount } from 'lbryinc';
import { selectGetSyncErrorMessage } from 'redux/selectors/sync';
import { doFetchAccessToken, doUserSetReferrer } from 'redux/actions/user';
import { selectUser, selectAccessToken, selectUserVerifiedEmail } from 'redux/selectors/user';
import { selectUnclaimedRewards } from 'redux/selectors/rewards';

View file

@ -1,7 +1,7 @@
import * as MODALS from 'constants/modal_types';
import { connect } from 'react-redux';
import { selectBalance, formatCredits, selectMyChannelClaims, SETTINGS } from 'lbry-redux';
import { selectGetSyncErrorMessage } from 'lbryinc';
import { selectGetSyncErrorMessage } from 'redux/selectors/sync';
import { selectUserVerifiedEmail, selectUserEmail, selectEmailToVerify, selectUser } from 'redux/selectors/user';
import { doClearEmailEntry, doClearPasswordEntry } from 'redux/actions/user';
import { doSetClientSetting } from 'redux/actions/settings';

View file

@ -6,9 +6,8 @@ import {
selectHasSyncedWallet,
selectGetSyncIsPending,
selectHashChanged,
doCheckSync,
doGetSync,
} from 'lbryinc';
} from 'redux/selectors/sync';
import { doCheckSync, doGetSync } from 'redux/actions/sync';
import { makeSelectClientSetting } from 'redux/selectors/settings';
import { doSetWalletSyncPreference } from 'redux/actions/settings';
import SyncToggle from './view';

View file

@ -1,5 +1,5 @@
import { connect } from 'react-redux';
import { selectGetSyncIsPending, selectSyncApplyPasswordError } from 'lbryinc';
import { selectGetSyncIsPending, selectSyncApplyPasswordError } from 'redux/selectors/sync';
import { doGetSyncDesktop } from 'redux/actions/syncwrapper';
import { selectUserEmail } from 'redux/selectors/user';
import { doSetClientSetting } from 'redux/actions/settings';

View file

@ -1,7 +1,7 @@
import { SETTINGS } from 'lbry-redux';
import { connect } from 'react-redux';
import { selectUserVerifiedEmail } from 'redux/selectors/user';
import { selectGetSyncErrorMessage } from 'lbryinc';
import { selectGetSyncErrorMessage } from 'redux/selectors/sync';
import { makeSelectClientSetting } from 'redux/selectors/settings';
import { doSetWalletSyncPreference } from 'redux/actions/settings';
import { doOpenModal } from 'redux/actions/app';

View file

@ -1,6 +1,6 @@
import REWARD_TYPES from 'rewards';
import { connect } from 'react-redux';
import { selectGetSyncIsPending, selectSyncHash } from 'lbryinc';
import { selectGetSyncIsPending, selectSyncHash } from 'redux/selectors/sync';
import { doClaimRewardType } from 'redux/actions/rewards';
import { doSetClientSetting } from 'redux/actions/settings';
import { selectClaimedRewards, makeSelectIsRewardClaimPending } from 'redux/selectors/rewards';

View file

@ -1,7 +1,7 @@
import { connect } from 'react-redux';
import { selectBalance, selectClaimsBalance, selectSupportsBalance, selectTipsBalance } from 'lbry-redux';
import { doOpenModal } from 'redux/actions/app';
import { selectSyncHash } from 'lbryinc';
import { selectSyncHash } from 'redux/selectors/sync';
import { selectClaimedRewards } from 'redux/selectors/rewards';
import WalletBalance from './view';

View file

@ -283,6 +283,20 @@ export const TOGGLE_BLOCK_CHANNEL = 'TOGGLE_BLOCK_CHANNEL';
export const WS_CONNECT = 'WS_CONNECT';
export const WS_DISCONNECT = 'WS_DISCONNECT';
// Cross-device Sync
export const GET_SYNC_STARTED = 'GET_SYNC_STARTED';
export const GET_SYNC_COMPLETED = 'GET_SYNC_COMPLETED';
export const GET_SYNC_FAILED = 'GET_SYNC_FAILED';
export const SET_SYNC_STARTED = 'SET_SYNC_STARTED';
export const SET_SYNC_FAILED = 'SET_SYNC_FAILED';
export const SET_SYNC_COMPLETED = 'SET_SYNC_COMPLETED';
export const SET_DEFAULT_ACCOUNT = 'SET_DEFAULT_ACCOUNT';
export const SYNC_APPLY_STARTED = 'SYNC_APPLY_STARTED';
export const SYNC_APPLY_COMPLETED = 'SYNC_APPLY_COMPLETED';
export const SYNC_APPLY_FAILED = 'SYNC_APPLY_FAILED';
export const SYNC_APPLY_BAD_PASSWORD = 'SYNC_APPLY_BAD_PASSWORD';
export const SYNC_RESET = 'SYNC_RESET';
export const REACTIONS_LIST_STARTED = 'REACTIONS_LIST_STARTED';
export const REACTIONS_LIST_FAILED = 'REACTIONS_LIST_FAILED';
export const REACTIONS_LIST_COMPLETED = 'REACTIONS_LIST_COMPLETED';

View file

@ -1,15 +1,7 @@
import { combineReducers } from 'redux';
import { connectRouter } from 'connected-react-router';
import { claimsReducer, fileInfoReducer, walletReducer, tagsReducer, publishReducer } from 'lbry-redux';
import {
costInfoReducer,
blacklistReducer,
filteredReducer,
homepageReducer,
statsReducer,
syncReducer,
webReducer,
} from 'lbryinc';
import { costInfoReducer, blacklistReducer, filteredReducer, homepageReducer, statsReducer, webReducer } from 'lbryinc';
import appReducer from 'redux/reducers/app';
import contentReducer from 'redux/reducers/content';
import settingsReducer from 'redux/reducers/settings';
@ -21,6 +13,7 @@ import commentsReducer from 'redux/reducers/comments';
import blockedReducer from 'redux/reducers/blocked';
import searchReducer from 'redux/reducers/search';
import reactionsReducer from 'redux/reducers/reactions';
import syncReducer from 'redux/reducers/sync';
export default history =>
combineReducers({

282
ui/redux/actions/sync.js Normal file
View file

@ -0,0 +1,282 @@
import * as ACTIONS from 'constants/action_types';
import { Lbryio } from 'lbryinc';
import { Lbry, doWalletEncrypt, doWalletDecrypt } from 'lbry-redux';
export function doSetDefaultAccount(success, failure) {
return dispatch => {
dispatch({
type: ACTIONS.SET_DEFAULT_ACCOUNT,
});
Lbry.account_list()
.then(accountList => {
const { lbc_mainnet: accounts } = accountList;
let defaultId;
for (let i = 0; i < accounts.length; ++i) {
if (accounts[i].satoshis > 0) {
defaultId = accounts[i].id;
break;
}
}
// In a case where there's no balance on either account
// assume the second (which is created after sync) as default
if (!defaultId && accounts.length > 1) {
defaultId = accounts[1].id;
}
// Set the default account
if (defaultId) {
Lbry.account_set({ account_id: defaultId, default: true })
.then(() => {
if (success) {
success();
}
})
.catch(err => {
if (failure) {
failure(err);
}
});
} else if (failure) {
// no default account to set
failure('Could not set a default account'); // fail
}
})
.catch(err => {
if (failure) {
failure(err);
}
});
};
}
export function doSetSync(oldHash, newHash, data) {
return dispatch => {
dispatch({
type: ACTIONS.SET_SYNC_STARTED,
});
return Lbryio.call('sync', 'set', { old_hash: oldHash, new_hash: newHash, data }, 'post')
.then(response => {
if (!response.hash) {
throw Error('No hash returned for sync/set.');
}
return dispatch({
type: ACTIONS.SET_SYNC_COMPLETED,
data: { syncHash: response.hash },
});
})
.catch(error => {
dispatch({
type: ACTIONS.SET_SYNC_FAILED,
data: { error },
});
});
};
}
export function doGetSync(passedPassword, callback) {
const password = passedPassword === null || passedPassword === undefined ? '' : passedPassword;
function handleCallback(error, hasNewData) {
if (callback) {
if (typeof callback !== 'function') {
throw new Error('Second argument passed to "doGetSync" must be a function');
}
callback(error, hasNewData);
}
}
return dispatch => {
dispatch({
type: ACTIONS.GET_SYNC_STARTED,
});
const data = {};
Lbry.wallet_status()
.then(status => {
if (status.is_locked) {
return Lbry.wallet_unlock({ password });
}
// Wallet is already unlocked
return true;
})
.then(isUnlocked => {
if (isUnlocked) {
return Lbry.sync_hash();
}
data.unlockFailed = true;
throw new Error();
})
.then(hash => Lbryio.call('sync', 'get', { hash }, 'post'))
.then(response => {
const syncHash = response.hash;
data.syncHash = syncHash;
data.syncData = response.data;
data.changed = response.changed;
data.hasSyncedWallet = true;
if (response.changed) {
return Lbry.sync_apply({ password, data: response.data, blocking: true });
}
})
.then(response => {
if (!response) {
dispatch({ type: ACTIONS.GET_SYNC_COMPLETED, data });
handleCallback(null, data.changed);
return;
}
const { hash: walletHash, data: walletData } = response;
if (walletHash !== data.syncHash) {
// different local hash, need to synchronise
dispatch(doSetSync(data.syncHash, walletHash, walletData));
}
dispatch({ type: ACTIONS.GET_SYNC_COMPLETED, data });
handleCallback(null, data.changed);
})
.catch(syncAttemptError => {
if (data.unlockFailed) {
dispatch({ type: ACTIONS.GET_SYNC_FAILED, data: { error: syncAttemptError } });
if (password !== '') {
dispatch({ type: ACTIONS.SYNC_APPLY_BAD_PASSWORD });
}
handleCallback(syncAttemptError);
} else if (data.hasSyncedWallet) {
const error = (syncAttemptError && syncAttemptError.message) || 'Error getting synced wallet';
dispatch({
type: ACTIONS.GET_SYNC_FAILED,
data: {
error,
},
});
// Temp solution until we have a bad password error code
// Don't fail on blank passwords so we don't show a "password error" message
// before users have ever entered a password
if (password !== '') {
dispatch({ type: ACTIONS.SYNC_APPLY_BAD_PASSWORD });
}
handleCallback(error);
} else {
// user doesn't have a synced wallet
dispatch({
type: ACTIONS.GET_SYNC_COMPLETED,
data: { hasSyncedWallet: false, syncHash: null },
});
// call sync_apply to get data to sync
// first time sync. use any string for old hash
Lbry.sync_apply({ password })
.then(({ hash: walletHash, data: syncApplyData }) => {
dispatch(doSetSync('', walletHash, syncApplyData, password));
handleCallback();
})
.catch(syncApplyError => {
handleCallback(syncApplyError);
});
}
});
};
}
export function doSyncApply(syncHash, syncData, password) {
return dispatch => {
dispatch({
type: ACTIONS.SYNC_APPLY_STARTED,
});
Lbry.sync_apply({ password, data: syncData })
.then(({ hash: walletHash, data: walletData }) => {
dispatch({
type: ACTIONS.SYNC_APPLY_COMPLETED,
});
if (walletHash !== syncHash) {
// different local hash, need to synchronise
dispatch(doSetSync(syncHash, walletHash, walletData));
}
})
.catch(() => {
dispatch({
type: ACTIONS.SYNC_APPLY_FAILED,
data: {
error: 'Invalid password specified. Please enter the password for your previously synchronised wallet.',
},
});
});
};
}
export function doCheckSync() {
return dispatch => {
dispatch({
type: ACTIONS.GET_SYNC_STARTED,
});
Lbry.sync_hash().then(hash => {
Lbryio.call('sync', 'get', { hash }, 'post')
.then(response => {
const data = {
hasSyncedWallet: true,
syncHash: response.hash,
syncData: response.data,
hashChanged: response.changed,
};
dispatch({ type: ACTIONS.GET_SYNC_COMPLETED, data });
})
.catch(() => {
// user doesn't have a synced wallet
dispatch({
type: ACTIONS.GET_SYNC_COMPLETED,
data: { hasSyncedWallet: false, syncHash: null },
});
});
});
};
}
export function doResetSync() {
return dispatch =>
new Promise(resolve => {
dispatch({ type: ACTIONS.SYNC_RESET });
resolve();
});
}
export function doSyncEncryptAndDecrypt(oldPassword, newPassword, encrypt) {
return dispatch => {
const data = {};
return Lbry.sync_hash()
.then(hash => Lbryio.call('sync', 'get', { hash }, 'post'))
.then(syncGetResponse => {
data.oldHash = syncGetResponse.hash;
return Lbry.sync_apply({ password: oldPassword, data: syncGetResponse.data });
})
.then(() => {
if (encrypt) {
dispatch(doWalletEncrypt(newPassword));
} else {
dispatch(doWalletDecrypt());
}
})
.then(() => Lbry.sync_apply({ password: newPassword }))
.then(syncApplyResponse => {
if (syncApplyResponse.hash !== data.oldHash) {
return dispatch(doSetSync(data.oldHash, syncApplyResponse.hash, syncApplyResponse.data));
}
})
.catch(console.error); // eslint-disable-line
};
}

View file

@ -1,5 +1,6 @@
// @flow
import { doGetSync, selectGetSyncIsPending, selectSetSyncIsPending } from 'lbryinc';
import { doGetSync } from 'redux/actions/sync';
import { selectGetSyncIsPending, selectSetSyncIsPending } from 'redux/selectors/sync';
import { makeSelectClientSetting } from 'redux/selectors/settings';
import { getSavedPassword } from 'util/saved-passwords';
import { doAnalyticsTagSync, doHandleSyncComplete } from 'redux/actions/app';

89
ui/redux/reducers/sync.js Normal file
View file

@ -0,0 +1,89 @@
import * as ACTIONS from 'constants/action_types';
const reducers = {};
const defaultState = {
hasSyncedWallet: false,
syncHash: null,
syncData: null,
setSyncErrorMessage: null,
getSyncErrorMessage: null,
syncApplyErrorMessage: '',
syncApplyIsPending: false,
syncApplyPasswordError: false,
getSyncIsPending: false,
setSyncIsPending: false,
hashChanged: false,
};
reducers[ACTIONS.GET_SYNC_STARTED] = state =>
Object.assign({}, state, {
getSyncIsPending: true,
getSyncErrorMessage: null,
});
reducers[ACTIONS.GET_SYNC_COMPLETED] = (state, action) =>
Object.assign({}, state, {
syncHash: action.data.syncHash,
syncData: action.data.syncData,
hasSyncedWallet: action.data.hasSyncedWallet,
getSyncIsPending: false,
hashChanged: action.data.hashChanged,
});
reducers[ACTIONS.GET_SYNC_FAILED] = (state, action) =>
Object.assign({}, state, {
getSyncIsPending: false,
getSyncErrorMessage: action.data.error,
});
reducers[ACTIONS.SET_SYNC_STARTED] = state =>
Object.assign({}, state, {
setSyncIsPending: true,
setSyncErrorMessage: null,
});
reducers[ACTIONS.SET_SYNC_FAILED] = (state, action) =>
Object.assign({}, state, {
setSyncIsPending: false,
setSyncErrorMessage: action.data.error,
});
reducers[ACTIONS.SET_SYNC_COMPLETED] = (state, action) =>
Object.assign({}, state, {
setSyncIsPending: false,
setSyncErrorMessage: null,
hasSyncedWallet: true, // sync was successful, so the user has a synced wallet at this point
syncHash: action.data.syncHash,
});
reducers[ACTIONS.SYNC_APPLY_STARTED] = state =>
Object.assign({}, state, {
syncApplyPasswordError: false,
syncApplyIsPending: true,
syncApplyErrorMessage: '',
});
reducers[ACTIONS.SYNC_APPLY_COMPLETED] = state =>
Object.assign({}, state, {
syncApplyIsPending: false,
syncApplyErrorMessage: '',
});
reducers[ACTIONS.SYNC_APPLY_FAILED] = (state, action) =>
Object.assign({}, state, {
syncApplyIsPending: false,
syncApplyErrorMessage: action.data.error,
});
reducers[ACTIONS.SYNC_APPLY_BAD_PASSWORD] = state =>
Object.assign({}, state, {
syncApplyPasswordError: true,
});
reducers[ACTIONS.SYNC_RESET] = () => defaultState;
export default function syncReducer(state = defaultState, action) {
const handler = reducers[action.type];
if (handler) return handler(state, action);
return state;
}

View file

@ -0,0 +1,25 @@
import { createSelector } from 'reselect';
const selectState = state => state.sync || {};
export const selectHasSyncedWallet = createSelector(selectState, state => state.hasSyncedWallet);
export const selectSyncHash = createSelector(selectState, state => state.syncHash);
export const selectSyncData = createSelector(selectState, state => state.syncData);
export const selectSetSyncErrorMessage = createSelector(selectState, state => state.setSyncErrorMessage);
export const selectGetSyncErrorMessage = createSelector(selectState, state => state.getSyncErrorMessage);
export const selectGetSyncIsPending = createSelector(selectState, state => state.getSyncIsPending);
export const selectSetSyncIsPending = createSelector(selectState, state => state.setSyncIsPending);
export const selectHashChanged = createSelector(selectState, state => state.hashChanged);
export const selectSyncApplyIsPending = createSelector(selectState, state => state.syncApplyIsPending);
export const selectSyncApplyErrorMessage = createSelector(selectState, state => state.syncApplyErrorMessage);
export const selectSyncApplyPasswordError = createSelector(selectState, state => state.syncApplyPasswordError);