derive 3 keys, consolidate sync redux

This commit is contained in:
zeppi 2022-10-07 16:18:45 -04:00
parent ded2992e44
commit 6b21df2c17
12 changed files with 271 additions and 446 deletions

View file

@ -22,20 +22,21 @@ export function checkHmac(serverWalletState, hmacKey, hmac) {
} }
export function deriveSecrets(rootPassword, email, saltSeed, callback) { export function deriveSecrets(rootPassword, email, saltSeed, callback) {
const encodedPassword = Buffer.from(rootPassword.normalize('NFKC')) const encodedPassword = Buffer.from(rootPassword.normalize('NFKC'));
const encodedEmail = Buffer.from(email) const encodedEmail = Buffer.from(email);
const SCRYPT_N = 1 << 20; const SCRYPT_N = 1 << 20;
const SCRYPT_R = 8; const SCRYPT_R = 8;
const SCRYPT_P = 1; const SCRYPT_P = 1;
const KEY_LENGTH = 32; const KEY_LENGTH = 32;
const NUM_KEYS = 2; const NUM_KEYS = 3;
const MAXMEM_MULTIPLIER = 256; const MAXMEM_MULTIPLIER = 256;
const DEFAULT_MAXMEM = MAXMEM_MULTIPLIER * SCRYPT_N * SCRYPT_R; const DEFAULT_MAXMEM = MAXMEM_MULTIPLIER * SCRYPT_N * SCRYPT_R;
function getKeyParts(key) { function getKeyParts(key) {
const lbryIdPassword = key.slice(0, KEY_LENGTH).toString('base64'); const lbryIdPassword = key.slice(0, KEY_LENGTH).toString('base64');
const hmacKey = key.slice(KEY_LENGTH).toString('base64'); const hmacKey = key.slice(KEY_LENGTH, KEY_LENGTH * 2).toString('base64');
return { lbryIdPassword, hmacKey }; // Buffer aa bb cc 6c const dataKey = key.slice(KEY_LENGTH * 2).toString('base64');
return { lbryIdPassword, hmacKey, dataKey }; // Buffer aa bb cc 6c
} }
const salt = generateSalt(encodedEmail, saltSeed); const salt = generateSalt(encodedEmail, saltSeed);

View file

@ -1,7 +1,7 @@
import test from 'tape'; import test from 'tape';
// import sync from '../sync.js'; // import sync from '../sync.js';
import { generateSalt, generateSaltSeed, deriveSecrets, walletHmac } from './sync.js'; import { generateSalt, generateSaltSeed, deriveSecrets, walletHmac } from './sync.js';
export default function doTest () { export default function doTest() {
test('Generate sync seed', (assert) => { test('Generate sync seed', (assert) => {
const seed = generateSaltSeed(); const seed = generateSaltSeed();
@ -29,12 +29,11 @@ export default function doTest () {
const expectedHmacKey = 'bCxUIryLK0Lf9nKg9yiZDlGleMuGJkadLzTje1PAI+8='; //base64 const expectedHmacKey = 'bCxUIryLK0Lf9nKg9yiZDlGleMuGJkadLzTje1PAI+8='; //base64
const expectedLbryIdPassword = 'HKo/J+x4Hsy2NkMvj2JB9RI0yrvEiB4QSA/NHPaT/cA='; const expectedLbryIdPassword = 'HKo/J+x4Hsy2NkMvj2JB9RI0yrvEiB4QSA/NHPaT/cA=';
let result;
function cb(e, r) { function cb(e, r) {
console.log('result', r) console.log('result', r);
assert.equal(r.keys.hmacKey, expectedHmacKey, 'hmac is expected value'); assert.equal(r.hmacKey, expectedHmacKey, 'hmac is expected value');
assert.equal(r.keys.lbryIdPassword, expectedLbryIdPassword, 'lbryid password is expected value'); assert.equal(r.lbryIdPassword, expectedLbryIdPassword, 'lbryid password is expected value');
assert.end(); assert.end();
} }
@ -51,8 +50,7 @@ export default function doTest () {
const hmacHex = walletHmac(input_str); const hmacHex = walletHmac(input_str);
assert.equal(hmacHex, expectedHmacHex); assert.equal(hmacHex, expectedHmacHex);
assert.end(); assert.end();
}); });
} }
doTest() doTest();

View file

@ -6,7 +6,7 @@ import { selectGetSyncErrorMessage } from 'redux/selectors/sync';
import { selectHasNavigated } from 'redux/selectors/app'; import { selectHasNavigated } from 'redux/selectors/app';
import { selectTotalBalance, selectBalance } from 'redux/selectors/wallet'; import { selectTotalBalance, selectBalance } from 'redux/selectors/wallet';
import * as SETTINGS from 'constants/settings'; import * as SETTINGS from 'constants/settings';
import { doLbrysyncRegister } from 'redux/actions/lbrysync'; import { doLbrysyncRegister } from 'redux/actions/sync';
import Header from './view'; import Header from './view';
const select = (state) => ({ const select = (state) => ({

View file

@ -17,7 +17,7 @@ import {
selectLbrySyncEncryptedHmacKey, selectLbrySyncEncryptedHmacKey,
selectLbrySyncEncryptedRoot, selectLbrySyncEncryptedRoot,
selectLbrySyncEncryptedProviderPass, selectLbrySyncEncryptedProviderPass,
} from 'redux/selectors/lbrysync'; } from 'redux/selectors/sync';
import { import {
doLbrysyncGetSalt, doLbrysyncGetSalt,
@ -25,10 +25,10 @@ import {
doGenerateSaltSeed, doGenerateSaltSeed,
doDeriveSecrets, doDeriveSecrets,
doLbrysyncAuthenticate, doLbrysyncAuthenticate,
} from 'redux/actions/lbrysync'; } from 'redux/actions/sync';
const select = (state) => ({ const select = (state) => ({
walletEncrypted: selectWalletIsEncrypted(state), isWalletEncrypted: selectWalletIsEncrypted(state),
registering: selectLbrySyncRegistering(state), registering: selectLbrySyncRegistering(state),
registeredEmail: selectLbrySyncEmail(state), registeredEmail: selectLbrySyncEmail(state),
registerError: selectLbrySyncRegisterError(state), registerError: selectLbrySyncRegisterError(state),

View file

@ -5,7 +5,6 @@ import Card from 'component/common/card';
import Button from 'component/button'; import Button from 'component/button';
import { Form, FormField } from 'component/common/form'; import { Form, FormField } from 'component/common/form';
import I18nMessage from 'component/i18nMessage'; import I18nMessage from 'component/i18nMessage';
import Spinner from 'component/spinner';
import * as ICONS from 'constants/icons'; import * as ICONS from 'constants/icons';
type Props = { type Props = {
@ -55,6 +54,9 @@ export default function NotificationSettingsPage(props: Props) {
register, register,
} = props; } = props;
/*
Register / auth
*/
const SIGN_IN_MODE = 'sign_in'; const SIGN_IN_MODE = 'sign_in';
const SIGN_UP_MODE = 'sign_up'; const SIGN_UP_MODE = 'sign_up';
const VERIFY_MODE = 'verify'; const VERIFY_MODE = 'verify';
@ -192,7 +194,7 @@ export default function NotificationSettingsPage(props: Props) {
/> />
</> </>
} }
label={__('Password Again')} label={__('Password')}
value={pass} value={pass}
onChange={(e) => setPass(e.target.value)} onChange={(e) => setPass(e.target.value)}
/> />
@ -222,7 +224,7 @@ export default function NotificationSettingsPage(props: Props) {
<p> <p>
<I18nMessage <I18nMessage
tokens={{ tokens={{
sign_in: <Button button="link" onClick={() => setMode(SIGN_UP_MODE)} label={__('Sign in')} />, sign_in: <Button button="link" onClick={() => setMode(SIGN_IN_MODE)} label={__('Sign in')} />,
}} }}
> >
Sign up for a sync account. Or %sign_in%. Sign up for a sync account. Or %sign_in%.
@ -254,7 +256,7 @@ export default function NotificationSettingsPage(props: Props) {
/> />
</> </>
} }
label={__('Password Again')} label={__('Password')}
value={pass} value={pass}
onChange={(e) => setPass(e.target.value)} onChange={(e) => setPass(e.target.value)}
/> />

View file

@ -1,7 +1,6 @@
import { combineReducers } from 'redux'; import { combineReducers } from 'redux';
import { connectRouter } from 'connected-react-router'; import { connectRouter } from 'connected-react-router';
import { costInfoReducer, blacklistReducer, filteredReducer, statsReducer } from 'lbryinc'; import { costInfoReducer, blacklistReducer, filteredReducer, statsReducer } from 'lbryinc';
import { lbrysyncReducer } from 'redux/reducers/lbrysync';
import { claimsReducer } from 'redux/reducers/claims'; import { claimsReducer } from 'redux/reducers/claims';
import { fileInfoReducer } from 'redux/reducers/file_info'; import { fileInfoReducer } from 'redux/reducers/file_info';
import { walletReducer } from 'redux/reducers/wallet'; import { walletReducer } from 'redux/reducers/wallet';
@ -44,5 +43,4 @@ export default (history) =>
wallet: walletReducer, wallet: walletReducer,
sync: syncReducer, sync: syncReducer,
collections: collectionsReducer, collections: collectionsReducer,
lbrysync: lbrysyncReducer,
}); });

View file

@ -1,302 +0,0 @@
// @flow
import * as ACTIONS from 'constants/action_types';
import { ipcRenderer } from 'electron';
import { safeStoreEncrypt, safeStoreDecrypt } from 'util/saved-passwords';
import * as Lbrysync from 'lbrysync';
import Lbry from 'lbry';
import { Lbryio } from "lbryinc";
import { selectSyncHash } from '../selectors/sync';
export const doLbrysyncGetSalt = (email: string) => async (dispatch: Dispatch) => {
const { fetchSaltSeed } = Lbrysync;
dispatch({
type: ACTIONS.LSYNC_GET_SALT_STARTED,
});
try {
const saltOrError = await fetchSaltSeed(email);
dispatch({
type: ACTIONS.LSYNC_GET_SALT_COMPLETED,
data: { email: email, saltSeed: saltOrError},
});
return saltOrError;
} catch (e) {
dispatch({
type: ACTIONS.LSYNC_GET_SALT_FAILED,
data: { email: email, saltError: 'Not Found'},
});
return 'not found';
}
};
// register an email (eventually username)
export const doLbrysyncRegister = (email: string, secrets: any, saltSeed: string) => async (dispatch: Dispatch) => {
const { register } = Lbrysync;
// started
dispatch({
type: ACTIONS.LSYNC_REGISTER_STARTED,
});
const resultIfError = await register(email, secrets.providerPass, saltSeed);
const encProviderPass = safeStoreEncrypt(secrets.providerPass);
const encHmacKey = safeStoreEncrypt(secrets.hmacKey);
const enctyptedRoot = safeStoreEncrypt(secrets.rootPassword);
const registerData = {
email,
saltSeed,
providerPass: encProviderPass,
hmacKey: encHmacKey,
rootPass: enctyptedRoot,
};
if (!resultIfError) {
dispatch({
type: ACTIONS.LSYNC_REGISTER_COMPLETED,
data: registerData,
});
} else {
dispatch({
type: ACTIONS.LSYNC_REGISTER_FAILED,
data: resultIfError,
});
}
};
// get token given username/password
export const doLbrysyncAuthenticate =
() => async (dispatch: Dispatch, getState: GetState) => {
dispatch({
type: ACTIONS.LSYNC_AUTH_STARTED,
});
const state = getState();
const { lbrysync } = state;
const { registeredEmail: email, encryptedProviderPass } = lbrysync;
const status = await Lbry.status();
const { installation_id: deviceId } = status;
const password = safeStoreDecrypt(encryptedProviderPass);
const { getAuthToken } = Lbrysync;
const result: { token?: string, error?: string } = await getAuthToken(email, password, deviceId);
if (result.token) {
dispatch({
type: ACTIONS.LSYNC_AUTH_COMPLETED,
data: result.token,
});
} else {
dispatch({
type: ACTIONS.LSYNC_AUTH_FAILED,
data: result.error,
});
}
};
export const doGenerateSaltSeed = () => async (dispatch: Dispatch) => {
const result = await ipcRenderer.invoke('invoke-get-salt-seed');
return result;
};
export const doDeriveSecrets = (rootPassword: string, email: string, saltSeed: string) => async (dispatch: Dispatch) =>
{
dispatch({
type: ACTIONS.LSYNC_DERIVE_STARTED,
});
try {
const result = await ipcRenderer.invoke('invoke-get-secrets', rootPassword, email, saltSeed);
const data = {
hmacKey: result.hmacKey,
rootPassword,
providerPass: result.lbryIdPassword,
};
dispatch({
type: ACTIONS.LSYNC_DERIVE_COMPLETED,
data,
});
return data;
} catch (e) {
dispatch({
type: ACTIONS.LSYNC_DERIVE_FAILED,
data: {
error: e,
},
});
return { error: e.message };
}
};
export async function doSetSync() {
return (dispatch: Dispatch, getState: GetState) => {
const state = getState();
const localHash = selectSyncHash(state);
const { lbrysync } = state;
const { authToken, encryptedRoot } = lbrysync;
dispatch({
type: ACTIONS.SET_SYNC_STARTED,
});
let error;
try {
const status = Lbry.wallet_status();
if (status.is_locked) {
throw new Error('Error parsing i18n messages file: ' + messagesFilePath + ' err: ' + err);
}
} catch(e) {
error = e.message;
}
if (!error) {
const syncData = await Lbry.sync_apply({ password: , data: response.data, blocking: true })
}
// return Lbryio.call('sync', 'set', { old_hash: oldHash, new_hash: newHash, data }, 'post')
return pushWallet(authToken)
.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?: string, callback?: (any, ?boolean) => void) {
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, getState: GetState) => {
const state = getState();
const localHash = selectSyncHash(state);
const { lbrysync } = state;
const { authToken, encryptedRoot } = lbrysync;
const { pullWallet } = Lbrysync;
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(); //unnec
}
data.unlockFailed = true;
throw new Error();
})
// .then((hash?: string) => Lbryio.call('sync', 'get', { hash }, 'post'))
.then((hash?: string) => pullWallet(authToken))
.then((response: any) => {
// get data, put it in sync apply.
const syncHash = response.hash;
data.syncHash = syncHash;
data.syncData = response.data;
data.changed = response.changed || syncHash !== localHash;
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));
handleCallback();
})
.catch((syncApplyError) => {
handleCallback(syncApplyError);
});
}
}
});
};
}

View file

@ -4,6 +4,10 @@ import * as SETTINGS from 'constants/settings';
import * as SHARED_PREFERENCES from 'constants/shared_preferences'; import * as SHARED_PREFERENCES from 'constants/shared_preferences';
import { Lbryio } from 'lbryinc'; import { Lbryio } from 'lbryinc';
import Lbry from 'lbry'; import Lbry from 'lbry';
import { ipcRenderer } from 'electron';
import * as Lbrysync from 'lbrysync';
import { safeStoreEncrypt, safeStoreDecrypt, getSavedPassword } from 'util/saved-passwords';
import { doWalletEncrypt, doWalletDecrypt } from 'redux/actions/wallet'; import { doWalletEncrypt, doWalletDecrypt } from 'redux/actions/wallet';
import { import {
selectSyncHash, selectSyncHash,
@ -12,8 +16,7 @@ import {
selectSyncIsLocked, selectSyncIsLocked,
} from 'redux/selectors/sync'; } from 'redux/selectors/sync';
import { makeSelectClientSetting } from 'redux/selectors/settings'; import { makeSelectClientSetting } from 'redux/selectors/settings';
import { getSavedPassword } from 'util/saved-passwords'; import { doHandleSyncComplete } from 'redux/actions/app';
import { doAnalyticsTagSync, doHandleSyncComplete } from 'redux/actions/app';
import Comments from 'comments'; import Comments from 'comments';
import { getSubsetFromKeysArray } from 'util/sync-settings'; import { getSubsetFromKeysArray } from 'util/sync-settings';
@ -71,6 +74,124 @@ export function doSetDefaultAccount(success: () => void, failure: (string) => vo
}; };
} }
export const doLbrysyncGetSalt = (email: string) => async (dispatch: Dispatch) => {
const { fetchSaltSeed } = Lbrysync;
dispatch({
type: ACTIONS.LSYNC_GET_SALT_STARTED,
});
try {
const saltOrError = await fetchSaltSeed(email);
dispatch({
type: ACTIONS.LSYNC_GET_SALT_COMPLETED,
data: { email: email, saltSeed: saltOrError},
});
return saltOrError;
} catch (e) {
dispatch({
type: ACTIONS.LSYNC_GET_SALT_FAILED,
data: { email: email, saltError: 'Not Found'},
});
return 'not found';
}
};
// register an email (eventually username)
export const doLbrysyncRegister = (email: string, secrets: any, saltSeed: string) => async (dispatch: Dispatch) => {
const { register } = Lbrysync;
// started
dispatch({
type: ACTIONS.LSYNC_REGISTER_STARTED,
});
const resultIfError = await register(email, secrets.providerPass, saltSeed);
const encProviderPass = safeStoreEncrypt(secrets.providerPass);
const encHmacKey = safeStoreEncrypt(secrets.hmacKey);
const enctyptedRoot = safeStoreEncrypt(secrets.rootPassword);
const registerData = {
email,
saltSeed,
providerPass: encProviderPass,
hmacKey: encHmacKey,
rootPass: enctyptedRoot,
};
if (!resultIfError) {
dispatch({
type: ACTIONS.LSYNC_REGISTER_COMPLETED,
data: registerData,
});
} else {
dispatch({
type: ACTIONS.LSYNC_REGISTER_FAILED,
data: resultIfError,
});
}
};
// get token given username/password
export const doLbrysyncAuthenticate =
() => async (dispatch: Dispatch, getState: GetState) => {
dispatch({
type: ACTIONS.LSYNC_AUTH_STARTED,
});
const state = getState();
const { lbrysync } = state;
const { registeredEmail: email, encryptedProviderPass } = lbrysync;
const status = await Lbry.status();
const { installation_id: deviceId } = status;
const password = safeStoreDecrypt(encryptedProviderPass);
const { getAuthToken } = Lbrysync;
const result: { token?: string, error?: string } = await getAuthToken(email, password, deviceId);
if (result.token) {
dispatch({
type: ACTIONS.LSYNC_AUTH_COMPLETED,
data: result.token,
});
} else {
dispatch({
type: ACTIONS.LSYNC_AUTH_FAILED,
data: result.error,
});
}
};
export const doGenerateSaltSeed = () => async (dispatch: Dispatch) => {
const result = await ipcRenderer.invoke('invoke-get-salt-seed');
return result;
};
export const doDeriveSecrets = (rootPassword: string, email: string, saltSeed: string) => async (dispatch: Dispatch) =>
{
dispatch({
type: ACTIONS.LSYNC_DERIVE_STARTED,
});
try {
const result = await ipcRenderer.invoke('invoke-get-secrets', rootPassword, email, saltSeed);
const data = {
hmacKey: result.hmacKey,
rootPassword,
providerPass: result.lbryIdPassword,
};
dispatch({
type: ACTIONS.LSYNC_DERIVE_COMPLETED,
data,
});
return data;
} catch (e) {
dispatch({
type: ACTIONS.LSYNC_DERIVE_FAILED,
data: {
error: e,
},
});
return { error: e.message };
}
};
export function doSetSync(oldHash: string, newHash: string, data: any) { export function doSetSync(oldHash: string, newHash: string, data: any) {
return (dispatch: Dispatch) => { return (dispatch: Dispatch) => {
dispatch({ dispatch({
@ -97,10 +218,18 @@ export function doSetSync(oldHash: string, newHash: string, data: any) {
}; };
} }
/*
make sure
- enabled
- not pending
- wallet not locked
-
get password
doGetSync(password, cb)
*/
export const doGetSyncDesktop = export const doGetSyncDesktop =
(cb?: (any, any) => void, password?: string) => (dispatch: Dispatch, getState: GetState) => { (cb?: (any, any) => void, password?: string) => (dispatch: Dispatch, getState: GetState) => {
const state = getState(); const state = getState();
const syncEnabled = makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state);
const getSyncPending = selectGetSyncIsPending(state); const getSyncPending = selectGetSyncIsPending(state);
const setSyncPending = selectSetSyncIsPending(state); const setSyncPending = selectSetSyncIsPending(state);
const syncLocked = selectSyncIsLocked(state); const syncLocked = selectSyncIsLocked(state);
@ -108,31 +237,36 @@ export const doGetSyncDesktop =
return getSavedPassword().then((savedPassword) => { return getSavedPassword().then((savedPassword) => {
const passwordArgument = password || password === '' ? password : savedPassword === null ? '' : savedPassword; const passwordArgument = password || password === '' ? password : savedPassword === null ? '' : savedPassword;
if (syncEnabled && !getSyncPending && !setSyncPending && !syncLocked) { if (!getSyncPending && !setSyncPending && !syncLocked) {
return dispatch(doGetSync(passwordArgument, cb)); return dispatch(doGetSync(passwordArgument, cb));
} }
}); });
}; };
/*
start regularly polling sync
- start loop if should.
*/
export function doSyncLoop(noInterval?: boolean) { export function doSyncLoop(noInterval?: boolean) {
return (dispatch: Dispatch, getState: GetState) => { return (dispatch: Dispatch, getState: GetState) => {
if (!noInterval && syncTimer) clearInterval(syncTimer); if (!noInterval && syncTimer) clearInterval(syncTimer);
const state = getState(); const state = getState();
// SHOULD SYNC // SHOULD SYNC?
// syncSignedIn = selectLbrySyncSignedIn(state); // syncSignedIn = selectLbrySyncSignedIn(state);
const syncSignedIn = false; const syncSignedIn = false;
const syncEnabled = makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state); const syncEnabled = makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state);
const syncLocked = selectSyncIsLocked(state); const syncLocked = selectSyncIsLocked(state);
// if shouldSync
if (syncSignedIn && syncEnabled && !syncLocked) { if (syncSignedIn && syncEnabled && !syncLocked) {
// doSync
//
dispatch(doGetSyncDesktop((error, hasNewData) => dispatch(doHandleSyncComplete(error, hasNewData)))); dispatch(doGetSyncDesktop((error, hasNewData) => dispatch(doHandleSyncComplete(error, hasNewData))));
dispatch(doAnalyticsTagSync());
if (!noInterval) { if (!noInterval) {
syncTimer = setInterval(() => { syncTimer = setInterval(() => {
const state = getState(); const state = getState();
const syncEnabled = makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state); const syncEnabled = makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state);
if (syncEnabled) { if (syncEnabled) {
dispatch(doGetSyncDesktop((error, hasNewData) => dispatch(doHandleSyncComplete(error, hasNewData)))); dispatch(doGetSyncDesktop((error, hasNewData) => dispatch(doHandleSyncComplete(error, hasNewData))));
dispatch(doAnalyticsTagSync());
} }
}, SYNC_INTERVAL); }, SYNC_INTERVAL);
} }
@ -140,6 +274,9 @@ export function doSyncLoop(noInterval?: boolean) {
}; };
} }
/*
stop regularly polling sync
*/
export function doSyncUnsubscribe() { export function doSyncUnsubscribe() {
return () => { return () => {
if (syncTimer) { if (syncTimer) {
@ -148,6 +285,10 @@ export function doSyncUnsubscribe() {
}; };
} }
/*
make sure not locked
*/
export function doGetSync(passedPassword?: string, callback?: (any, ?boolean) => void) { export function doGetSync(passedPassword?: string, callback?: (any, ?boolean) => void) {
const password = passedPassword === null || passedPassword === undefined ? '' : passedPassword; const password = passedPassword === null || passedPassword === undefined ? '' : passedPassword;

View file

@ -1,94 +0,0 @@
import * as ACTIONS from 'constants/action_types';
import { handleActions } from 'util/redux-utils';
const defaultState = {
syncProvider: null,
// reg
registering: false,
registeredEmail: null,
registerError: null,
// authtoken
isAuthenticating: false,
authError: null,
authToken: null, // store this elsewhere
// keys
derivingKeys: false,
encryptedHmacKey: null,
encryptedRoot: null,
encryptedProviderPass: null,
// salt
gettingSalt: false,
saltSeed: null,
saltError: null,
};
export const lbrysyncReducer = handleActions(
{
// Register
[ACTIONS.LSYNC_REGISTER_STARTED]: (state) => ({
...state,
registering: true,
registerError: null,
}),
[ACTIONS.LSYNC_REGISTER_COMPLETED]: (state, action) => ({
...state,
registeredEmail: action.data.email,
encryptedHmacKey: action.data.hmacKey,
encryptedProviderPass: action.data.providerPass,
encryptedRoot: action.data.rootPass,
saltSeed: action.data.saltSeed,
}),
[ACTIONS.LSYNC_REGISTER_FAILED]: (state, action) => ({
...state,
registeredEmail: null,
registering: false,
registerError: action.data.error,
}),
// Auth
[ACTIONS.LSYNC_AUTH_STARTED]: (state) => ({
...state,
isAuthenticating: true,
authError: null,
}),
[ACTIONS.LSYNC_AUTH_COMPLETED]: (state, action) => ({
...state,
authToken: action.data,
}),
[ACTIONS.LSYNC_AUTH_FAILED]: (state, action) => ({
...state,
authError: action.data,
isAuthenticating: false,
}),
// derive
[ACTIONS.LSYNC_DERIVE_STARTED]: (state) => ({
...state,
derivingKeys: true,
deriveError: null,
}),
[ACTIONS.LSYNC_DERIVE_COMPLETED]: (state, action) => ({
...state,
derivingKeys: false,
}),
[ACTIONS.LSYNC_DERIVE_FAILED]: (state, action) => ({
...state,
deriveError: action.data.error,
derivingKeys: false,
}),
// salt
[ACTIONS.LSYNC_GET_SALT_STARTED]: (state) => ({
...state,
gettingSalt: true,
saltError: null,
}),
[ACTIONS.LSYNC_GET_SALT_COMPLETED]: (state, action) => ({
...state,
gettingSalt: false,
}),
[ACTIONS.LSYNC_GET_SALT_FAILED]: (state, action) => ({
...state,
saltError: action.data.error,
gettingSalt: false,
}),
},
defaultState
);

View file

@ -16,6 +16,25 @@ const defaultState = {
syncLocked: false, syncLocked: false,
hashChanged: false, hashChanged: false,
fatalError: false, fatalError: false,
// lbrysync
syncProvider: null,
// reg
registering: false,
registeredEmail: null,
registerError: null,
// authtoken
isAuthenticating: false,
authError: null,
authToken: null, // store this elsewhere
// keys
derivingKeys: false,
encryptedHmacKey: null,
encryptedRoot: null,
encryptedProviderPass: null,
// salt
gettingSalt: false,
saltSeed: null,
saltError: null,
}; };
reducers[ACTIONS.SYNC_STATE_POPULATE] = (state) => { reducers[ACTIONS.SYNC_STATE_POPULATE] = (state) => {
@ -107,6 +126,71 @@ reducers[ACTIONS.SYNC_FATAL_ERROR] = (state) => {
fatalError: true, fatalError: true,
}); });
}; };
// lbrysync
reducers[ACTIONS.LSYNC_REGISTER_STARTED] = (state) => ({
...state,
registering: true,
registerError: null,
});
reducers[ACTIONS.LSYNC_REGISTER_COMPLETED] = (state, action) => ({
...state,
registeredEmail: action.data.email,
encryptedHmacKey: action.data.hmacKey,
encryptedProviderPass: action.data.providerPass,
encryptedRoot: action.data.rootPass,
saltSeed: action.data.saltSeed,
});
reducers[ACTIONS.LSYNC_REGISTER_FAILED] = (state, action) => ({
...state,
registeredEmail: null,
registering: false,
registerError: action.data.error,
});
// Auth
reducers[ACTIONS.LSYNC_AUTH_STARTED] = (state) => ({
...state,
isAuthenticating: true,
authError: null,
});
reducers[ACTIONS.LSYNC_AUTH_COMPLETED] = (state, action) => ({
...state,
authToken: action.data,
});
reducers[ACTIONS.LSYNC_AUTH_FAILED] = (state, action) => ({
...state,
authError: action.data,
isAuthenticating: false,
});
// derive
reducers[ACTIONS.LSYNC_DERIVE_STARTED] = (state) => ({
...state,
derivingKeys: true,
deriveError: null,
});
reducers[ACTIONS.LSYNC_DERIVE_COMPLETED] = (state, action) => ({
...state,
derivingKeys: false,
});
reducers[ACTIONS.LSYNC_DERIVE_FAILED] = (state, action) => ({
...state,
deriveError: action.data.error,
derivingKeys: false,
});
// salt
reducers[ACTIONS.LSYNC_GET_SALT_STARTED] = (state) => ({
...state,
gettingSalt: true,
saltError: null,
});
reducers[ACTIONS.LSYNC_GET_SALT_COMPLETED] = (state, action) => ({
...state,
gettingSalt: false,
});
reducers[ACTIONS.LSYNC_GET_SALT_FAILED] = (state, action) => ({
...state,
saltError: action.data.error,
gettingSalt: false,
});
reducers[ACTIONS.SYNC_RESET] = () => defaultState; reducers[ACTIONS.SYNC_RESET] = () => defaultState;

View file

@ -1,21 +0,0 @@
import { createSelector } from 'reselect';
const selectState = (state) => state.lbrysync || {};
export const selectLbrySyncRegistering = createSelector(selectState, (state) => state.registering);
export const selectLbrySyncEmail = createSelector(selectState, (state) => state.registeredEmail);
export const selectLbrySyncRegisterError = createSelector(selectState, (state) => state.registerError);
export const selectLbrySyncGettingSalt = createSelector(selectState, (state) => state.gettingSalt);
export const selectLbrySyncSaltError = createSelector(selectState, (state) => state.saltError);
export const selectLbrySyncSaltSeed = createSelector(selectState, (state) => state.saltSeed);
export const selectLbrySyncIsAuthenticating = createSelector(selectState, (state) => state.isAuthenticating);
export const selectLbrySyncAuthError = createSelector(selectState, (state) => state.authError);
export const selectLbrySyncToken = createSelector(selectState, (state) => state.authToken);
export const selectLbrySyncDerivingKeys = createSelector(selectState, (state) => state.derivingKeys);
export const selectLbrySyncEncryptedHmacKey = createSelector(selectState, (state) => state.encryptedHmacKey);
export const selectLbrySyncEncryptedRoot = createSelector(selectState, (state) => state.encryptedRoot);
export const selectLbrySyncEncryptedProviderPass = createSelector(selectState, (state) => state.encryptedProviderPass);

View file

@ -29,3 +29,21 @@ export const selectSyncIsLocked = createSelector(selectState, state => state.syn
export const selectPrefsReady = createSelector(selectState, state => state.prefsReady); export const selectPrefsReady = createSelector(selectState, state => state.prefsReady);
export const selectSyncFatalError = createSelector(selectState, state => state.fatalError); export const selectSyncFatalError = createSelector(selectState, state => state.fatalError);
// lbrysync
export const selectLbrySyncRegistering = createSelector(selectState, (state) => state.registering);
export const selectLbrySyncEmail = createSelector(selectState, (state) => state.registeredEmail);
export const selectLbrySyncRegisterError = createSelector(selectState, (state) => state.registerError);
export const selectLbrySyncGettingSalt = createSelector(selectState, (state) => state.gettingSalt);
export const selectLbrySyncSaltError = createSelector(selectState, (state) => state.saltError);
export const selectLbrySyncSaltSeed = createSelector(selectState, (state) => state.saltSeed);
export const selectLbrySyncIsAuthenticating = createSelector(selectState, (state) => state.isAuthenticating);
export const selectLbrySyncAuthError = createSelector(selectState, (state) => state.authError);
export const selectLbrySyncToken = createSelector(selectState, (state) => state.authToken);
export const selectLbrySyncDerivingKeys = createSelector(selectState, (state) => state.derivingKeys);
export const selectLbrySyncEncryptedHmacKey = createSelector(selectState, (state) => state.encryptedHmacKey);
export const selectLbrySyncEncryptedRoot = createSelector(selectState, (state) => state.encryptedRoot);
export const selectLbrySyncEncryptedProviderPass = createSelector(selectState, (state) => state.encryptedProviderPass);