bring in sync code from lbryinc
This commit is contained in:
parent
19664726d6
commit
f54a0de797
13 changed files with 423 additions and 19 deletions
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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
282
ui/redux/actions/sync.js
Normal 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
|
||||
};
|
||||
}
|
|
@ -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
89
ui/redux/reducers/sync.js
Normal 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;
|
||||
}
|
25
ui/redux/selectors/sync.js
Normal file
25
ui/redux/selectors/sync.js
Normal 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);
|
Loading…
Add table
Reference in a new issue