58773ede91
## Tickets - 5504 Signing out of account causes page to break in other tabs - 6829 merged accounts - force log out / fail sync when x-auth-token and cookie auth token are different ## Steps to replicate 1. Login to odysee with account-A. 2. Open another tab, and split both tabs on the screen. 3. Logout from the 1st tab. Do not activate (focus) the 2nd tab. 4. On the 1st tab, login with account-B. 5. Activate (focus) the 2nd tab. The wallet would have been merged, and we are still logged in as account-A. ## Approach Reload when the LBRY API token no longer matches the auth token.
375 lines
11 KiB
JavaScript
375 lines
11 KiB
JavaScript
import * as ACTIONS from 'constants/action_types';
|
|
import { Lbryio } from 'lbryinc';
|
|
import { SETTINGS, Lbry, doWalletEncrypt, doWalletDecrypt } from 'lbry-redux';
|
|
import { selectGetSyncIsPending, selectSetSyncIsPending, selectSyncIsLocked } from 'redux/selectors/sync';
|
|
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
|
import { getSavedPassword, getAuthToken } from 'util/saved-passwords';
|
|
import { doAnalyticsTagSync, doHandleSyncComplete } from 'redux/actions/app';
|
|
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
|
import { X_LBRY_AUTH_TOKEN } from 'constants/token';
|
|
|
|
let syncTimer = null;
|
|
const SYNC_INTERVAL = 1000 * 60 * 5; // 5 minutes
|
|
const NO_WALLET_ERROR = 'no wallet found for this user';
|
|
const BAD_PASSWORD_ERROR_NAME = 'InvalidPasswordError';
|
|
|
|
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 const doGetSyncDesktop = (cb?, password) => (dispatch, getState) => {
|
|
const state = getState();
|
|
const syncEnabled = makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state);
|
|
const getSyncPending = selectGetSyncIsPending(state);
|
|
const setSyncPending = selectSetSyncIsPending(state);
|
|
const syncLocked = selectSyncIsLocked(state);
|
|
|
|
return getSavedPassword().then((savedPassword) => {
|
|
const passwordArgument = password || password === '' ? password : savedPassword === null ? '' : savedPassword;
|
|
|
|
if (syncEnabled && !getSyncPending && !setSyncPending && !syncLocked) {
|
|
return dispatch(doGetSync(passwordArgument, cb));
|
|
}
|
|
});
|
|
};
|
|
|
|
export function doSyncLoop(noInterval) {
|
|
return (dispatch, getState) => {
|
|
if (!noInterval && syncTimer) clearInterval(syncTimer);
|
|
const state = getState();
|
|
const hasVerifiedEmail = selectUserVerifiedEmail(state);
|
|
const syncEnabled = makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state);
|
|
const syncLocked = selectSyncIsLocked(state);
|
|
if (hasVerifiedEmail && syncEnabled && !syncLocked) {
|
|
dispatch(doGetSyncDesktop((error, hasNewData) => dispatch(doHandleSyncComplete(error, hasNewData))));
|
|
dispatch(doAnalyticsTagSync());
|
|
if (!noInterval) {
|
|
syncTimer = setInterval(() => {
|
|
const state = getState();
|
|
const syncEnabled = makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state);
|
|
if (syncEnabled) {
|
|
dispatch(doGetSyncDesktop((error, hasNewData) => dispatch(doHandleSyncComplete(error, hasNewData))));
|
|
dispatch(doAnalyticsTagSync());
|
|
}
|
|
}, SYNC_INTERVAL);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
export function doSyncUnsubscribe() {
|
|
return (dispatch) => {
|
|
if (syncTimer) {
|
|
clearInterval(syncTimer);
|
|
}
|
|
};
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
// @if TARGET='web'
|
|
const xAuth =
|
|
Lbry.getApiRequestHeaders() && Object.keys(Lbry.getApiRequestHeaders()).includes(X_LBRY_AUTH_TOKEN)
|
|
? Lbry.getApiRequestHeaders()[X_LBRY_AUTH_TOKEN]
|
|
: '';
|
|
if (xAuth && xAuth !== getAuthToken()) {
|
|
window.location.reload();
|
|
return;
|
|
}
|
|
// @endif
|
|
|
|
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) => {
|
|
const badPasswordError =
|
|
syncAttemptError && syncAttemptError.data && syncAttemptError.data.name === BAD_PASSWORD_ERROR_NAME;
|
|
|
|
if (data.unlockFailed) {
|
|
dispatch({ type: ACTIONS.GET_SYNC_FAILED, data: { error: syncAttemptError } });
|
|
|
|
if (badPasswordError) {
|
|
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,
|
|
},
|
|
});
|
|
|
|
if (badPasswordError) {
|
|
dispatch({ type: ACTIONS.SYNC_APPLY_BAD_PASSWORD });
|
|
}
|
|
|
|
handleCallback(error);
|
|
} else {
|
|
const noWalletError = syncAttemptError && syncAttemptError.message === NO_WALLET_ERROR;
|
|
|
|
dispatch({
|
|
type: ACTIONS.GET_SYNC_COMPLETED,
|
|
data: {
|
|
hasSyncedWallet: false,
|
|
syncHash: null,
|
|
// If there was some unknown error, bail
|
|
fatalError: !noWalletError,
|
|
},
|
|
});
|
|
|
|
// user doesn't have a synced wallet
|
|
// call sync_apply to get data to sync
|
|
// first time sync. use any string for old hash
|
|
if (noWalletError) {
|
|
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
|
|
};
|
|
}
|
|
|
|
export function doSetSyncLock(lock) {
|
|
return {
|
|
type: ACTIONS.SET_SYNC_LOCK,
|
|
data: lock,
|
|
};
|
|
}
|
|
|
|
export function doSetPrefsReady() {
|
|
return {
|
|
type: ACTIONS.SET_PREFS_READY,
|
|
data: true,
|
|
};
|
|
}
|