first pass at sync for everyone

This commit is contained in:
Sean Yesmunt 2019-10-15 17:23:51 -04:00
parent 099fec9137
commit a3b3a204b0
12 changed files with 164 additions and 116 deletions

View file

@ -130,7 +130,7 @@
"husky": "^0.14.3", "husky": "^0.14.3",
"json-loader": "^0.5.4", "json-loader": "^0.5.4",
"lbry-format": "https://github.com/lbryio/lbry-format.git", "lbry-format": "https://github.com/lbryio/lbry-format.git",
"lbry-redux": "lbryio/lbry-redux#6edcf747e10919605b05b905214fe1d3286898e3", "lbry-redux": "lbryio/lbry-redux#0090f195eb88f4620db7d038f7b01eaa76119836",
"lbryinc": "lbryio/lbryinc#b8e1708ee4491db342c81576265e1b58f542bedb", "lbryinc": "lbryio/lbryinc#b8e1708ee4491db342c81576265e1b58f542bedb",
"lint-staged": "^7.0.2", "lint-staged": "^7.0.2",
"localforage": "^1.7.1", "localforage": "^1.7.1",

View file

@ -5,7 +5,8 @@ import { selectUser, doRewardList, doFetchRewardedContent, doFetchAccessToken }
import { doFetchTransactions, doFetchChannelListMine, selectBalance } from 'lbry-redux'; import { doFetchTransactions, doFetchChannelListMine, selectBalance } from 'lbry-redux';
import { makeSelectClientSetting, selectThemePath } from 'redux/selectors/settings'; import { makeSelectClientSetting, selectThemePath } from 'redux/selectors/settings';
import { selectIsUpgradeAvailable, selectAutoUpdateDownloaded } from 'redux/selectors/app'; import { selectIsUpgradeAvailable, selectAutoUpdateDownloaded } from 'redux/selectors/app';
import { doDownloadUpgradeRequested, doSignIn } from 'redux/actions/app'; import { doDownloadUpgradeRequested, doSignIn, doSyncWithPreferences } from 'redux/actions/app';
import { doSetClientSetting } from 'redux/actions/settings';
import App from './view'; import App from './view';
const select = state => ({ const select = state => ({
@ -15,6 +16,7 @@ const select = state => ({
autoUpdateDownloaded: selectAutoUpdateDownloaded(state), autoUpdateDownloaded: selectAutoUpdateDownloaded(state),
isUpgradeAvailable: selectIsUpgradeAvailable(state), isUpgradeAvailable: selectIsUpgradeAvailable(state),
balance: selectBalance(state), balance: selectBalance(state),
syncEnabled: makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state),
}); });
const perform = dispatch => ({ const perform = dispatch => ({
@ -25,6 +27,8 @@ const perform = dispatch => ({
fetchChannelListMine: () => dispatch(doFetchChannelListMine()), fetchChannelListMine: () => dispatch(doFetchChannelListMine()),
signIn: () => dispatch(doSignIn()), signIn: () => dispatch(doSignIn()),
requestDownloadUpgrade: () => dispatch(doDownloadUpgradeRequested()), requestDownloadUpgrade: () => dispatch(doDownloadUpgradeRequested()),
checkSync: () => dispatch(doSyncWithPreferences()),
setSyncEnabled: value => dispatch(doSetClientSetting(SETTINGS.ENABLE_SYNC, value)),
}); });
export default hot( export default hot(

View file

@ -19,6 +19,7 @@ export const MAIN_WRAPPER_CLASS = 'main-wrapper';
// @if TARGET='app' // @if TARGET='app'
export const IS_MAC = process.platform === 'darwin'; export const IS_MAC = process.platform === 'darwin';
// @endif // @endif
const SYNC_INTERVAL = 1000 * 60 * 5; // 5 minutes
type Props = { type Props = {
alertError: (string | {}) => void, alertError: (string | {}) => void,
@ -38,7 +39,9 @@ type Props = {
onSignedIn: () => void, onSignedIn: () => void,
isUpgradeAvailable: boolean, isUpgradeAvailable: boolean,
autoUpdateDownloaded: boolean, autoUpdateDownloaded: boolean,
balance: ?number, checkSync: () => void,
setSyncEnabled: boolean => void,
syncEnabled: boolean,
}; };
function App(props: Props) { function App(props: Props) {
@ -54,7 +57,9 @@ function App(props: Props) {
autoUpdateDownloaded, autoUpdateDownloaded,
isUpgradeAvailable, isUpgradeAvailable,
requestDownloadUpgrade, requestDownloadUpgrade,
balance, syncEnabled,
checkSync,
setSyncEnabled,
} = props; } = props;
const appRef = useRef(); const appRef = useRef();
const isEnhancedLayout = useKonamiListener(); const isEnhancedLayout = useKonamiListener();
@ -114,13 +119,27 @@ function App(props: Props) {
// Keep this at the end to ensure initial setup effects are run first // Keep this at the end to ensure initial setup effects are run first
useEffect(() => { useEffect(() => {
// Wait for balance to be populated on desktop so we know when we can begin syncing // Wait for balance to be populated on desktop so we know when we can begin syncing
// @syncwithbalancefixme if (!hasSignedIn && hasVerifiedEmail) {
if (!hasSignedIn && hasVerifiedEmail && (IS_WEB || balance !== undefined)) {
signIn(); signIn();
setHasSignedIn(true); setHasSignedIn(true);
} }
}, [hasVerifiedEmail, signIn, balance, hasSignedIn]); }, [hasVerifiedEmail, signIn, hasSignedIn]);
useEffect(() => {
if (!hasVerifiedEmail && syncEnabled) {
setSyncEnabled(false);
} else if (hasVerifiedEmail && syncEnabled) {
checkSync();
let syncInterval = setInterval(() => {
checkSync();
}, SYNC_INTERVAL);
return () => {
clearInterval(syncInterval);
};
}
}, [hasVerifiedEmail, syncEnabled, checkSync, setSyncEnabled]);
if (!user) { if (!user) {
return null; return null;

View file

@ -0,0 +1,20 @@
import * as SETTINGS from 'constants/settings';
import { connect } from 'react-redux';
import { selectUserVerifiedEmail } from 'lbryinc';
import { makeSelectClientSetting } from 'redux/selectors/settings';
import { doSetClientSetting } from 'redux/actions/settings';
import SyncToggle from './view';
const select = state => ({
syncEnabled: makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state),
verifiedEmail: selectUserVerifiedEmail(state),
});
const perform = dispatch => ({
setSyncEnabled: value => dispatch(doSetClientSetting(SETTINGS.ENABLE_SYNC, value)),
});
export default connect(
select,
perform
)(SyncToggle);

View file

@ -0,0 +1,36 @@
// @flow
import React from 'react';
import { FormField } from 'component/common/form';
import Button from 'component/button';
type Props = {
setSyncEnabled: boolean => void,
syncEnabled: boolean,
verifiedEmail: ?string,
};
function SyncToggle(props: Props) {
const { setSyncEnabled, syncEnabled, verifiedEmail } = props;
function handleChange() {
setSyncEnabled(!syncEnabled);
}
return (
<div>
{!verifiedEmail ? (
<Button requiresAuth button="primary" label={__('Start Syncing')} />
) : (
<FormField
type="checkbox"
name="sync_toggle"
label={__('Sync your balance and preferences accross LBRY apps.')}
checked={syncEnabled}
onChange={handleChange}
/>
)}
</div>
);
}
export default SyncToggle;

View file

@ -1,6 +1,5 @@
import * as SETTINGS from 'constants/settings'; import * as SETTINGS from 'constants/settings';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { selectBalance } from 'lbry-redux';
import { selectEmailNewIsPending, selectEmailNewErrorMessage, doUserEmailNew } from 'lbryinc'; import { selectEmailNewIsPending, selectEmailNewErrorMessage, doUserEmailNew } from 'lbryinc';
import { doSetClientSetting } from 'redux/actions/settings'; import { doSetClientSetting } from 'redux/actions/settings';
import { makeSelectClientSetting } from 'redux/selectors/settings'; import { makeSelectClientSetting } from 'redux/selectors/settings';
@ -10,7 +9,6 @@ const select = state => ({
isPending: selectEmailNewIsPending(state), isPending: selectEmailNewIsPending(state),
errorMessage: selectEmailNewErrorMessage(state), errorMessage: selectEmailNewErrorMessage(state),
syncEnabled: makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state), syncEnabled: makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state),
balance: selectBalance(state),
}); });
const perform = dispatch => ({ const perform = dispatch => ({

View file

@ -17,12 +17,13 @@ type Props = {
}; };
function UserEmailNew(props: Props) { function UserEmailNew(props: Props) {
const { errorMessage, isPending, addUserEmail, syncEnabled, setSync, balance } = props; const { errorMessage, isPending, addUserEmail, setSync } = props;
const [newEmail, setEmail] = useState(''); const [newEmail, setEmail] = useState('');
const [ageConfirmation, setAgeConfirmation] = useState(true); const [formSyncEnabled, setFormSyncEnabled] = useState(true);
const valid = newEmail.match(EMAIL_REGEX); const valid = newEmail.match(EMAIL_REGEX);
function handleSubmit() { function handleSubmit() {
setSync(formSyncEnabled);
addUserEmail(newEmail); addUserEmail(newEmail);
analytics.emailProvidedEvent(); analytics.emailProvidedEvent();
@ -31,15 +32,8 @@ function UserEmailNew(props: Props) {
// @endif // @endif
} }
React.useEffect(() => {
// Sync currently doesn't work for wallets with balances
if (syncEnabled && balance) {
setSync(false);
}
}, [balance, syncEnabled, setSync]);
return ( return (
<div> <React.Fragment>
<h1 className="section__title--large">{__('Welcome To LBRY')}</h1> <h1 className="section__title--large">{__('Welcome To LBRY')}</h1>
<p className="section__subtitle">{__('Create a new account or sign in.')}</p> <p className="section__subtitle">{__('Create a new account or sign in.')}</p>
<Form onSubmit={handleSubmit} className="section__body"> <Form onSubmit={handleSubmit} className="section__body">
@ -54,60 +48,39 @@ function UserEmailNew(props: Props) {
error={errorMessage} error={errorMessage}
onChange={e => setEmail(e.target.value)} onChange={e => setEmail(e.target.value)}
/> />
<div className="section">
{!IS_WEB && (
<FormField <FormField
type="checkbox" type="checkbox"
name="age_checkbox" name="sync_checkbox"
label={ label={
<I18nMessage <React.Fragment>
tokens={{ {__('Sync balance and preferences across devices.')}{' '}
terms: ( <Button button="link" href="https://lbry.com/faq/account-sync" label={__('Learn More')} />
<Button button="link" href="https://www.lbry.com/termsofservice" label={__('Terms of Service')} /> </React.Fragment>
),
}}
>
I am over the age of 13 and agree to the %terms%.
</I18nMessage>
} }
checked={ageConfirmation} helper={
onChange={() => setAgeConfirmation(!ageConfirmation)} <React.Fragment>
<I18nMessage
tokens={{
terms: (
<Button button="link" href="https://www.lbry.com/termsofservice" label={__('Terms of Service')} />
),
}}
>
By continuing, I agree to the %terms% and confirm I am over the age of 13.
</I18nMessage>
</React.Fragment>
}
checked={formSyncEnabled}
onChange={() => setFormSyncEnabled(!formSyncEnabled)}
/> />
{!IS_WEB && ( )}
<FormField <div className="card__actions">
type="checkbox" <Button button="primary" type="submit" label={__('Continue')} disabled={!newEmail || !valid || isPending} />
name="sync_checkbox"
label={__('Sync balance and preferences across devices')}
helper={
balance > 0 ? (
__('This feature is not yet available for wallets with balances, but the gerbils are working on it.')
) : (
<I18nMessage
tokens={{
learn_more: (
<Button button="link" href="https://lbry.com/faq/account-sync" label={__('Learn More')} />
),
}}
>
Blockchain expert? %learn_more%
</I18nMessage>
)
}
checked={syncEnabled}
onChange={() => setSync(!syncEnabled)}
disabled={balance > 0}
/>
)}
<div className="card__actions">
<Button
button="primary"
type="submit"
label={__('Continue')}
disabled={!newEmail || !valid || !ageConfirmation || isPending}
/>
</div>
</div> </div>
</Form> </Form>
</div> </React.Fragment>
); );
} }

View file

@ -11,6 +11,7 @@ import I18nMessage from 'component/i18nMessage';
import Page from 'component/page'; import Page from 'component/page';
import SettingLanguage from 'component/settingLanguage'; import SettingLanguage from 'component/settingLanguage';
import FileSelector from 'component/common/file-selector'; import FileSelector from 'component/common/file-selector';
import SyncToggle from 'component/syncToggle';
import Card from 'component/common/card'; import Card from 'component/common/card';
import { getSavedPassword } from 'util/saved-passwords'; import { getSavedPassword } from 'util/saved-passwords';
@ -229,6 +230,7 @@ class SettingsPage extends React.PureComponent<Props, State> {
) : ( ) : (
<div> <div>
<Card title={__('Language')} actions={<SettingLanguage />} /> <Card title={__('Language')} actions={<SettingLanguage />} />
<Card title={__('Sync')} actions={<SyncToggle />} />
{/* @if TARGET='app' */} {/* @if TARGET='app' */}
<Card <Card
title={__('Download Directory')} title={__('Download Directory')}
@ -639,11 +641,11 @@ class SettingsPage extends React.PureComponent<Props, State> {
<Card <Card
title={__('Application Cache')} title={__('Application Cache')}
subtitle={ subtitle={
<p className="card__subtitle--status"> <span className="card__subtitle--status">
{__( {__(
'This will clear the application cache. Your wallet will not be affected. Currently, followed tags and blocked channels will be cleared.' 'This will clear the application cache. Your wallet will not be affected. Currently, followed tags and blocked channels will be cleared.'
)} )}
</p> </span>
} }
actions={ actions={
<Button <Button

View file

@ -7,7 +7,6 @@ import path from 'path';
import * as ACTIONS from 'constants/action_types'; import * as ACTIONS from 'constants/action_types';
import * as MODALS from 'constants/modal_types'; import * as MODALS from 'constants/modal_types';
import * as PAGES from 'constants/pages'; import * as PAGES from 'constants/pages';
import * as SETTINGS from 'constants/settings';
import { import {
Lbry, Lbry,
doBalanceSubscribe, doBalanceSubscribe,
@ -17,7 +16,6 @@ import {
makeSelectClaimIsMine, makeSelectClaimIsMine,
doPopulateSharedUserState, doPopulateSharedUserState,
doFetchChannelListMine, doFetchChannelListMine,
selectBalance,
doClearPublish, doClearPublish,
doPreferenceGet, doPreferenceGet,
doToast, doToast,
@ -35,13 +33,12 @@ import {
selectUpgradeTimer, selectUpgradeTimer,
selectModal, selectModal,
} from 'redux/selectors/app'; } from 'redux/selectors/app';
import { doAuthenticate, doGetSync, selectSyncHash, doResetSync } from 'lbryinc'; import { doAuthenticate, doGetSync, doResetSync } from 'lbryinc';
import { lbrySettings as config, version as appVersion } from 'package.json'; import { lbrySettings as config, version as appVersion } from 'package.json';
import { push } from 'connected-react-router'; import { push } from 'connected-react-router';
import analytics from 'analytics'; import analytics from 'analytics';
import { deleteAuthToken, getSavedPassword } from 'util/saved-passwords'; import { deleteAuthToken, getSavedPassword } from 'util/saved-passwords';
import cookie from 'cookie'; import cookie from 'cookie';
import { makeSelectClientSetting } from 'redux/selectors/settings';
// @if TARGET='app' // @if TARGET='app'
const { autoUpdater } = remote.require('electron-updater'); const { autoUpdater } = remote.require('electron-updater');
@ -439,53 +436,12 @@ export function doAnalyticsView(uri, timeToStart) {
export function doSignIn() { export function doSignIn() {
return (dispatch, getState) => { return (dispatch, getState) => {
function handlePreferencesAfterSync() {
function successCb(savedPreferences) {
dispatch(doPopulateSharedUserState(savedPreferences));
}
function failCb() {
dispatch(
doToast({
isError: true,
message: __('Unable to load your saved preferences.'),
})
);
}
doPreferenceGet('shared', successCb, failCb);
}
// The balance is subscribed to on launch for desktop
// @if TARGET='web' // @if TARGET='web'
const { auth_token: authToken } = cookie.parse(document.cookie); const { auth_token: authToken } = cookie.parse(document.cookie);
Lbry.setApiHeader('X-Lbry-Auth-Token', authToken); Lbry.setApiHeader('X-Lbry-Auth-Token', authToken);
dispatch(doBalanceSubscribe()); dispatch(doBalanceSubscribe());
dispatch(doFetchChannelListMine()); dispatch(doFetchChannelListMine());
// @endif // @endif
const state = getState();
const syncEnabled = makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state);
const hasSyncedBefore = selectSyncHash(state);
const balance = selectBalance(state);
// For existing users, check if they've synced before, or have 0 balance
// Always sync for web
const canSync = syncEnabled && (hasSyncedBefore || balance === 0 || IS_WEB);
if (canSync) {
getSavedPassword().then(password => {
const passwordArgument = password === null ? '' : password;
// Only set the default account if they have never synced before
dispatch(doGetSync(passwordArgument, handlePreferencesAfterSync));
setInterval(() => {
dispatch(doGetSync(passwordArgument, handlePreferencesAfterSync));
}, 1000 * 60 * 5);
});
}
}; };
} }
@ -508,3 +464,30 @@ export function doSignOut() {
.catch(() => location.reload()); .catch(() => location.reload());
}; };
} }
export function doSyncWithPreferences() {
return dispatch => {
function handlePreferencesAfterSync() {
function successCb(savedPreferences) {
dispatch(doPopulateSharedUserState(savedPreferences));
}
function failCb() {
dispatch(
doToast({
isError: true,
message: __('Unable to load your saved preferences.'),
})
);
}
doPreferenceGet('shared', successCb, failCb);
}
return getSavedPassword().then(password => {
const passwordArgument = password === null ? '' : password;
dispatch(doGetSync(passwordArgument, handlePreferencesAfterSync));
});
};
}

View file

@ -52,6 +52,11 @@ textarea {
} }
} }
// @lbry/components specificityfixme
checkbox-element input[type='checkbox']:checked + label {
color: lighten($lbry-black, 20%);
}
fieldset-section { fieldset-section {
margin-bottom: var(--spacing-small); margin-bottom: var(--spacing-small);

View file

@ -1,16 +1,24 @@
import { ipcRenderer } from 'electron'; import { ipcRenderer } from 'electron';
let sessionPassword;
export const setSavedPassword = value => { export const setSavedPassword = value => {
return new Promise(resolve => { return new Promise(resolve => {
ipcRenderer.once('set-password-response', (event, success) => { ipcRenderer.once('set-password-response', (event, success) => {
resolve(success); resolve(success);
}); });
sessionPassword = value;
ipcRenderer.send('set-password', value); ipcRenderer.send('set-password', value);
}); });
}; };
export const getSavedPassword = () => { export const getSavedPassword = () => {
return new Promise(resolve => { return new Promise(resolve => {
if (sessionPassword) {
resolve(sessionPassword);
}
// @if TARGET='app' // @if TARGET='app'
ipcRenderer.once('get-password-response', (event, password) => { ipcRenderer.once('get-password-response', (event, password) => {
resolve(password); resolve(password);

View file

@ -6870,9 +6870,9 @@ lazy-val@^1.0.3, lazy-val@^1.0.4:
yargs "^13.2.2" yargs "^13.2.2"
zstd-codec "^0.1.1" zstd-codec "^0.1.1"
lbry-redux@lbryio/lbry-redux#6edcf747e10919605b05b905214fe1d3286898e3: lbry-redux@lbryio/lbry-redux#0090f195eb88f4620db7d038f7b01eaa76119836:
version "0.0.1" version "0.0.1"
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/6edcf747e10919605b05b905214fe1d3286898e3" resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/0090f195eb88f4620db7d038f7b01eaa76119836"
dependencies: dependencies:
proxy-polyfill "0.1.6" proxy-polyfill "0.1.6"
reselect "^3.0.0" reselect "^3.0.0"