From 4e706421638794fad07ca27f532d41cb328886bd Mon Sep 17 00:00:00 2001 From: Sean Yesmunt Date: Thu, 17 Oct 2019 11:27:41 -0400 Subject: [PATCH] don't lose subs/tags for existing users and don't opt them into sync automatically --- package.json | 1 - src/ui/component/app/index.js | 3 +- src/ui/component/app/view.jsx | 47 +++++++++++++++++---- src/ui/index.jsx | 11 ++--- src/ui/modal/modalWalletEncrypt/view.jsx | 9 ++-- src/ui/modal/modalWalletUnlock/view.jsx | 4 +- src/ui/redux/actions/app.js | 9 ++-- src/ui/store.js | 1 - src/ui/util/saved-passwords.js | 54 +++++++++++++++++++++--- static/app-strings.json | 5 ++- yarn.lock | 5 --- 11 files changed, 106 insertions(+), 43 deletions(-) diff --git a/package.json b/package.json index c7d55e7cf..09c00c780 100644 --- a/package.json +++ b/package.json @@ -89,7 +89,6 @@ "codemirror": "^5.39.2", "concurrently": "^4.1.2", "connected-react-router": "^6.4.0", - "cookie": "^0.3.1", "copy-webpack-plugin": "^4.6.0", "country-data": "^0.0.31", "cross-env": "^5.2.0", diff --git a/src/ui/component/app/index.js b/src/ui/component/app/index.js index 31ee09b0b..9971c30bd 100644 --- a/src/ui/component/app/index.js +++ b/src/ui/component/app/index.js @@ -1,7 +1,7 @@ import * as SETTINGS from 'constants/settings'; import { hot } from 'react-hot-loader/root'; import { connect } from 'react-redux'; -import { selectUser, doRewardList, doFetchRewardedContent, doFetchAccessToken } from 'lbryinc'; +import { selectUser, doRewardList, doFetchRewardedContent, doFetchAccessToken, selectAccessToken } from 'lbryinc'; import { doFetchTransactions, doFetchChannelListMine, selectBalance } from 'lbry-redux'; import { makeSelectClientSetting, selectThemePath } from 'redux/selectors/settings'; import { selectIsUpgradeAvailable, selectAutoUpdateDownloaded } from 'redux/selectors/app'; @@ -17,6 +17,7 @@ const select = state => ({ isUpgradeAvailable: selectIsUpgradeAvailable(state), balance: selectBalance(state), syncEnabled: makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state), + accessToken: selectAccessToken(state), }); const perform = dispatch => ({ diff --git a/src/ui/component/app/view.jsx b/src/ui/component/app/view.jsx index b4a9acd98..25812eb42 100644 --- a/src/ui/component/app/view.jsx +++ b/src/ui/component/app/view.jsx @@ -1,5 +1,6 @@ // @flow import * as ICONS from 'constants/icons'; +import * as ACTIONS from 'constants/action_types'; import React, { useEffect, useRef, useState } from 'react'; import classnames from 'classnames'; import analytics from 'analytics'; @@ -14,7 +15,8 @@ import FileViewer from 'component/fileViewer'; import { withRouter } from 'react-router'; import usePrevious from 'effects/use-previous'; import Button from 'component/button'; -import cookie from 'cookie'; +import usePersistedState from 'effects/use-persisted-state'; +import { Lbryio } from 'lbryinc'; export const MAIN_WRAPPER_CLASS = 'main-wrapper'; // @if TARGET='app' @@ -22,8 +24,6 @@ export const IS_MAC = process.platform === 'darwin'; // @endif const SYNC_INTERVAL = 1000 * 60 * 5; // 5 minutes -const { auth_token: authToken } = cookie.parse(document.cookie); - type Props = { alertError: (string | {}) => void, pageTitle: ?string, @@ -45,6 +45,8 @@ type Props = { checkSync: () => void, setSyncEnabled: boolean => void, syncEnabled: boolean, + balance: ?number, + accessToken: ?string, }; function App(props: Props) { @@ -63,11 +65,14 @@ function App(props: Props) { setSyncEnabled, syncEnabled, checkSync, + balance, + accessToken, } = props; const appRef = useRef(); const isEnhancedLayout = useKonamiListener(); const [hasSignedIn, setHasSignedIn] = useState(false); + const [hasDeterminedIfNewUser, setHasDeterminedIfNewUser] = usePersistedState('is-new-user', false); const userId = user && user.id; const hasVerifiedEmail = user && user.has_verified_email; const isRewardApproved = user && user.is_reward_approved; @@ -88,11 +93,35 @@ function App(props: Props) { // to automatically opt-in existing users. Only users that go through the new sign in flow // should be automatically opted-in (they choose to uncheck the option and turn off sync still) useEffect(() => { - if (!authToken) { - setSyncEnabled(true); + if (balance === undefined || accessToken === undefined) { + return; } - // don't pass in any props to this, we only want the initial value - }, []); + + // Manually call subscription/list once because I was dumb and wasn't persisting it in redux + Lbryio.call('subscription', 'list').then(response => { + if (response && response.length) { + const subscriptions = response.map(value => { + const { channel_name: channelName, claim_id: claimId } = value; + return { + channelName, + uri: buildURI({ channelName, channelClaimId: claimId }), + }; + }); + + window.store.dispatch({ + type: ACTIONS.FETCH_SUBSCRIPTIONS_SUCCESS, + data: subscriptions, + }); + } + + // Yeah... this isn't the best check, but it works for now + const newUser = balance === 0; + if (newUser) { + setSyncEnabled(true); + } + setHasDeterminedIfNewUser(true); + }); + }, [balance, accessToken]); useEffect(() => { ReactModal.setAppElement(appRef.current); @@ -141,7 +170,7 @@ function App(props: Props) { }, [hasVerifiedEmail, signIn, hasSignedIn]); useEffect(() => { - if (hasVerifiedEmail && syncEnabled) { + if (hasVerifiedEmail && syncEnabled && hasDeterminedIfNewUser) { checkSync(); let syncInterval = setInterval(() => { @@ -152,7 +181,7 @@ function App(props: Props) { clearInterval(syncInterval); }; } - }, [hasVerifiedEmail, syncEnabled, checkSync]); + }, [hasVerifiedEmail, syncEnabled, checkSync, hasDeterminedIfNewUser]); if (!user) { return null; diff --git a/src/ui/index.jsx b/src/ui/index.jsx index d375c86ae..02d54d0ae 100644 --- a/src/ui/index.jsx +++ b/src/ui/index.jsx @@ -26,10 +26,10 @@ import pjson from 'package.json'; import app from './app'; import doLogWarningConsoleMessage from './logWarningConsoleMessage'; import { ConnectedRouter, push } from 'connected-react-router'; -import cookie from 'cookie'; import { formatLbryUriForWeb } from 'util/uri'; import { PersistGate } from 'redux-persist/integration/react'; import analytics from 'analytics'; +import { getAuthToken, setAuthToken } from 'util/saved-passwords'; // Import our app styles // If a style is not necessary for the initial page load, it should be removed from `all.scss` @@ -82,12 +82,7 @@ Lbryio.setOverride( } authToken = response.auth_token; - - let date = new Date(); - date.setFullYear(date.getFullYear() + 1); - document.cookie = cookie.serialize('auth_token', authToken, { - expires: date, - }); + setAuthToken(authToken); // @if TARGET='app' ipcRenderer.send('set-auth-token', authToken); @@ -114,7 +109,7 @@ Lbryio.setOverride( ipcRenderer.send('get-auth-token'); // @endif // @if TARGET='web' - const { auth_token: authToken } = cookie.parse(document.cookie); + const authToken = getAuthToken(); resolve(authToken); // @endif } diff --git a/src/ui/modal/modalWalletEncrypt/view.jsx b/src/ui/modal/modalWalletEncrypt/view.jsx index be94866d7..6aa72e0fa 100644 --- a/src/ui/modal/modalWalletEncrypt/view.jsx +++ b/src/ui/modal/modalWalletEncrypt/view.jsx @@ -74,6 +74,10 @@ class ModalWalletEncrypt extends React.PureComponent { submitEncryptForm() { const { state } = this; + if (!state.newPassword) { + return; + } + let invalidEntries = false; if (state.newPassword !== state.newPasswordConfirm) { @@ -89,9 +93,8 @@ class ModalWalletEncrypt extends React.PureComponent { if (invalidEntries === true) { return; } - if (state.rememberPassword === true) { - setSavedPassword(state.newPassword); - } + + setSavedPassword(state.newPassword, state.rememberPassword); this.setState({ submitted: true }); this.props.encryptWallet(state.newPassword); } diff --git a/src/ui/modal/modalWalletUnlock/view.jsx b/src/ui/modal/modalWalletUnlock/view.jsx index 22fb9230e..3f8f794c5 100644 --- a/src/ui/modal/modalWalletUnlock/view.jsx +++ b/src/ui/modal/modalWalletUnlock/view.jsx @@ -39,9 +39,7 @@ class ModalWalletUnlock extends React.PureComponent { const { props } = this; if (props.walletUnlockSucceded === true) { - if (this.state.rememberPassword) { - setSavedPassword(this.state.password); - } + setSavedPassword(this.state.password, this.state.rememberPassword); props.closeModal(); } } diff --git a/src/ui/redux/actions/app.js b/src/ui/redux/actions/app.js index f8b317c08..96ee39463 100644 --- a/src/ui/redux/actions/app.js +++ b/src/ui/redux/actions/app.js @@ -37,8 +37,7 @@ import { doAuthenticate, doGetSync, doResetSync } from 'lbryinc'; import { lbrySettings as config, version as appVersion } from 'package.json'; import { push } from 'connected-react-router'; import analytics from 'analytics'; -import { deleteAuthToken, getSavedPassword } from 'util/saved-passwords'; -import cookie from 'cookie'; +import { deleteAuthToken, getSavedPassword, getAuthToken } from 'util/saved-passwords'; // @if TARGET='app' const { autoUpdater } = remote.require('electron-updater'); @@ -437,7 +436,7 @@ export function doAnalyticsView(uri, timeToStart) { export function doSignIn() { return (dispatch, getState) => { // @if TARGET='web' - const { auth_token: authToken } = cookie.parse(document.cookie); + const authToken = getAuthToken(); Lbry.setApiHeader('X-Lbry-Auth-Token', authToken); dispatch(doBalanceSubscribe()); dispatch(doFetchChannelListMine()); @@ -471,7 +470,9 @@ export function doSyncWithPreferences() { dispatch(doFetchChannelListMine()); function successCb(savedPreferences) { - dispatch(doPopulateSharedUserState(savedPreferences)); + if (savedPreferences !== null) { + dispatch(doPopulateSharedUserState(savedPreferences)); + } } function failCb() { diff --git a/src/ui/store.js b/src/ui/store.js index 78751d785..59e8a2a12 100644 --- a/src/ui/store.js +++ b/src/ui/store.js @@ -68,7 +68,6 @@ const whiteListedReducers = [ 'search', 'blocked', 'settings', - 'sync', 'subscriptions', ]; diff --git a/src/ui/util/saved-passwords.js b/src/ui/util/saved-passwords.js index 6990093a4..d9a1b2348 100644 --- a/src/ui/util/saved-passwords.js +++ b/src/ui/util/saved-passwords.js @@ -1,9 +1,42 @@ +// @flow import { ipcRenderer } from 'electron'; let sessionPassword; -export const setSavedPassword = (value, saveToDisk) => { - return new Promise(resolve => { +function setCookie(name: string, value: string, days: number) { + let expires = ''; + if (days) { + let date = new Date(); + date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000); + expires = '; expires=' + date.toUTCString(); + } + + document.cookie = `${name}=${value || ''}${expires}; path=/`; +} + +function getCookie(name: string) { + const nameEQ = name + '='; + const cookies = document.cookie.split(';'); + + for (var i = 0; i < cookies.length; i++) { + let cookie = cookies[i]; + while (cookie.charAt(0) === ' ') { + cookie = cookie.substring(1, cookie.length); + } + + if (cookie.indexOf(nameEQ) === 0) { + return cookie.substring(nameEQ.length, cookie.length); + } + } + return null; +} + +function deleteCookie(name: string) { + document.cookie = name + '=; Max-Age=-99999999;'; +} + +export const setSavedPassword = (value?: string, saveToDisk: boolean) => { + return new Promise<*>(resolve => { ipcRenderer.once('set-password-response', (event, success) => { resolve(success); }); @@ -16,7 +49,7 @@ export const setSavedPassword = (value, saveToDisk) => { }; export const getSavedPassword = () => { - return new Promise(resolve => { + return new Promise<*>(resolve => { if (sessionPassword) { resolve(sessionPassword); } @@ -36,7 +69,7 @@ export const getSavedPassword = () => { }; export const deleteSavedPassword = () => { - return new Promise(resolve => { + return new Promise<*>(resolve => { // @if TARGET='app' ipcRenderer.once('delete-password-response', (event, success) => { resolve(); @@ -46,16 +79,25 @@ export const deleteSavedPassword = () => { }); }; +export const getAuthToken = () => { + return getCookie('auth_token'); +}; + +export const setAuthToken = (value: string) => { + return setCookie('auth_token', value, 365); +}; + export const deleteAuthToken = () => { - return new Promise(resolve => { + return new Promise<*>(resolve => { // @if TARGET='app' ipcRenderer.once('delete-auth-token-response', (event, success) => { resolve(); }); ipcRenderer.send('delete-auth-token'); // @endif; + + deleteCookie('auth_token'); // @if TARGET='web' - document.cookie = 'auth_token= ; expires = Thu, 01 Jan 1970 00:00:00 GMT'; resolve(); // @endif }); diff --git a/static/app-strings.json b/static/app-strings.json index 00afb57aa..451cb2d78 100644 --- a/static/app-strings.json +++ b/static/app-strings.json @@ -820,5 +820,6 @@ "Content may be hidden on this %type% because of your %settings%.": "Content may be hidden on this %type% because of your %settings%.", "Sync": "Sync", "earned and bound in tips": "earned and bound in tips", - "currently staked": "currently staked" -} + "currently staked": "currently staked", + "%amountBehind% block behind": "%amountBehind% block behind" +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 41d8059a2..198935d66 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2941,11 +2941,6 @@ cookie@0.4.0: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== -cookie@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" - integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s= - copy-concurrently@^1.0.0: version "1.0.5" resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0"