use sdk preference endpoints

This commit is contained in:
Sean Yesmunt 2019-10-15 00:20:12 -04:00
parent f5bf2fae4f
commit 90e8c74006
12 changed files with 110 additions and 70 deletions

View file

@ -129,8 +129,8 @@
"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#0a1c95a08835a6b892d853b156d4934e469c9589", "lbry-redux": "lbryio/lbry-redux#cd69946d4ca016c896639aa401d8f6b87d3e410e",
"lbryinc": "lbryio/lbryinc#d1dba98bb6f1dc67bc0db4c0a20fc13b8a0de98b", "lbryinc": "lbryio/lbryinc#b8e1708ee4491db342c81576265e1b58f542bedb",
"lint-staged": "^7.0.2", "lint-staged": "^7.0.2",
"localforage": "^1.7.1", "localforage": "^1.7.1",
"lodash-es": "^4.17.14", "lodash-es": "^4.17.14",

View file

@ -1,7 +1,7 @@
import * as SETTINGS from 'constants/settings'; import * as SETTINGS from 'constants/settings';
import { hot } from 'react-hot-loader/root'; import { hot } from 'react-hot-loader/root';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { selectUser, doRewardList, doFetchRewardedContent, doFetchAccessToken } from 'lbryinc'; import { selectUser, doRewardList, doFetchRewardedContent, doAuthenticate } from 'lbryinc';
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';
@ -21,7 +21,7 @@ const perform = dispatch => ({
fetchRewards: () => dispatch(doRewardList()), fetchRewards: () => dispatch(doRewardList()),
fetchRewardedContent: () => dispatch(doFetchRewardedContent()), fetchRewardedContent: () => dispatch(doFetchRewardedContent()),
fetchTransactions: () => dispatch(doFetchTransactions()), fetchTransactions: () => dispatch(doFetchTransactions()),
fetchAccessToken: () => dispatch(doFetchAccessToken()), fetchAccessToken: () => dispatch(doAuthenticate()),
fetchChannelListMine: () => dispatch(doFetchChannelListMine()), fetchChannelListMine: () => dispatch(doFetchChannelListMine()),
signIn: () => dispatch(doSignIn()), signIn: () => dispatch(doSignIn()),
requestDownloadUpgrade: () => dispatch(doDownloadUpgradeRequested()), requestDownloadUpgrade: () => dispatch(doDownloadUpgradeRequested()),

View file

@ -1,6 +1,6 @@
// @flow // @flow
import * as ICONS from 'constants/icons'; import * as ICONS from 'constants/icons';
import React, { useEffect, useRef } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import analytics from 'analytics'; import analytics from 'analytics';
import { buildURI, parseURI } from 'lbry-redux'; import { buildURI, parseURI } from 'lbry-redux';
@ -56,6 +56,7 @@ function App(props: Props) {
} = props; } = props;
const appRef = useRef(); const appRef = useRef();
const isEnhancedLayout = useKonamiListener(); const isEnhancedLayout = useKonamiListener();
const [hasSignedIn, setHasSignedIn] = useState(false);
const userId = user && user.id; const userId = user && user.id;
const hasVerifiedEmail = user && user.has_verified_email; const hasVerifiedEmail = user && user.has_verified_email;
const isRewardApproved = user && user.is_reward_approved; const isRewardApproved = user && user.is_reward_approved;
@ -112,10 +113,12 @@ function App(props: Props) {
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 // @syncwithbalancefixme
if (hasVerifiedEmail && (IS_WEB || balance !== undefined)) { if (!hasSignedIn && hasVerifiedEmail && (IS_WEB || balance !== undefined)) {
signIn(); signIn();
setHasSignedIn(true);
} }
}, [hasVerifiedEmail, signIn, balance]); }, [hasVerifiedEmail, signIn, balance, hasSignedIn]);
if (!user) { if (!user) {
return null; return null;

View file

@ -2,8 +2,8 @@ import * as SETTINGS from 'constants/settings';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { selectBalance } from 'lbry-redux'; import { selectBalance } from 'lbry-redux';
import { selectEmailNewIsPending, selectEmailNewErrorMessage, doUserEmailNew } from 'lbryinc'; import { selectEmailNewIsPending, selectEmailNewErrorMessage, doUserEmailNew } from 'lbryinc';
import { makeSelectClientSetting } from 'redux/selectors/settings';
import { doSetClientSetting } from 'redux/actions/settings'; import { doSetClientSetting } from 'redux/actions/settings';
import { makeSelectClientSetting } from 'redux/selectors/settings';
import UserEmailNew from './view'; import UserEmailNew from './view';
const select = state => ({ const select = state => ({

View file

@ -119,8 +119,8 @@ function UserSignIn(props: Props) {
<YoutubeTransferStatus /> <Confetti recycle={false} style={{ position: 'fixed' }} /> <YoutubeTransferStatus /> <Confetti recycle={false} style={{ position: 'fixed' }} />
</div> </div>
), ),
showSyncPassword && <SyncPassword />,
// @endif // @endif
showSyncPassword && <SyncPassword />,
showLoadingSpinner && ( showLoadingSpinner && (
<div className="main--empty"> <div className="main--empty">
<Spinner /> <Spinner />

View file

@ -54,7 +54,7 @@ if (process.env.SEARCH_API_URL) {
} }
// @if TARGET='web' // @if TARGET='web'
const SDK_API_URL = process.env.SDK_API_URL || 'https://api.lbry.tv/api/proxy'; const SDK_API_URL = process.env.SDK_API_URL || 'https://api.lbry.tv/api/v1/proxy';
Lbry.setDaemonConnectionString(SDK_API_URL); Lbry.setDaemonConnectionString(SDK_API_URL);
// @endif // @endif

View file

@ -52,7 +52,7 @@ type Props = {
themes: Array<string>, themes: Array<string>,
automaticDarkModeEnabled: boolean, automaticDarkModeEnabled: boolean,
autoplay: boolean, autoplay: boolean,
autoDownload: boolean, // autoDownload: boolean,
encryptWallet: () => void, encryptWallet: () => void,
decryptWallet: () => void, decryptWallet: () => void,
updateWalletStatus: () => void, updateWalletStatus: () => void,
@ -198,7 +198,7 @@ class SettingsPage extends React.PureComponent<Props, State> {
autoplay, autoplay,
walletEncrypted, walletEncrypted,
osNotificationsEnabled, osNotificationsEnabled,
autoDownload, // autoDownload,
setDaemonSetting, setDaemonSetting,
setClientSetting, setClientSetting,
supportOption, supportOption,
@ -588,7 +588,7 @@ class SettingsPage extends React.PureComponent<Props, State> {
/> />
{/* @if TARGET='app' */} {/* @if TARGET='app' */}
<FormField {/* <FormField
type="checkbox" type="checkbox"
name="auto_download" name="auto_download"
onChange={() => setClientSetting(SETTINGS.AUTO_DOWNLOAD, !autoDownload)} onChange={() => setClientSetting(SETTINGS.AUTO_DOWNLOAD, !autoDownload)}
@ -597,7 +597,7 @@ class SettingsPage extends React.PureComponent<Props, State> {
helper={__( helper={__(
"The latest file from each of your subscriptions will be downloaded for quick access as soon as it's published." "The latest file from each of your subscriptions will be downloaded for quick access as soon as it's published."
)} )}
/> /> */}
<fieldset-section> <fieldset-section>
<FormField <FormField
name="max_connections" name="max_connections"

View file

@ -19,10 +19,11 @@ import {
doFetchChannelListMine, doFetchChannelListMine,
selectBalance, selectBalance,
doClearPublish, doClearPublish,
doPreferenceGet,
doToast,
} from 'lbry-redux'; } from 'lbry-redux';
import Native from 'native'; import Native from 'native';
import { doFetchDaemonSettings } from 'redux/actions/settings'; import { doFetchDaemonSettings } from 'redux/actions/settings';
import { doCheckSubscriptionsInit } from 'redux/actions/subscriptions';
import { import {
selectIsUpgradeSkipped, selectIsUpgradeSkipped,
selectUpdateUrl, selectUpdateUrl,
@ -34,7 +35,7 @@ import {
selectUpgradeTimer, selectUpgradeTimer,
selectModal, selectModal,
} from 'redux/selectors/app'; } from 'redux/selectors/app';
import { Lbryio, doAuthenticate, doGetSync, selectSyncHash, doResetSync } from 'lbryinc'; import { doAuthenticate, doGetSync, selectSyncHash, 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';
@ -331,7 +332,6 @@ export function doDaemonReady() {
dispatch(doCheckUpgradeAvailable()); dispatch(doCheckUpgradeAvailable());
} }
dispatch(doCheckUpgradeSubscribe()); dispatch(doCheckUpgradeSubscribe());
dispatch(doCheckSubscriptionsInit());
// @endif // @endif
}; };
} }
@ -439,37 +439,53 @@ 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 // 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());
dispatch(doCheckSubscriptionsInit());
// @endif // @endif
// @if TARGET='app'
const state = getState(); const state = getState();
const syncEnabled = makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state); const syncEnabled = makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state);
const hasSyncedBefore = selectSyncHash(state); const hasSyncedBefore = selectSyncHash(state);
const balance = selectBalance(state); const balance = selectBalance(state);
// For existing users, check if they've synced before, or have 0 balance // For existing users, check if they've synced before, or have 0 balance
if (syncEnabled && (hasSyncedBefore || balance === 0)) { // Always sync for web
const canSync = syncEnabled && (hasSyncedBefore || balance === 0 || IS_WEB);
if (canSync) {
getSavedPassword().then(password => { getSavedPassword().then(password => {
const passwordArgument = password === null ? '' : password; const passwordArgument = password === null ? '' : password;
dispatch(doGetSync(passwordArgument, !hasSyncedBefore));
// Only set the default account if they have never synced before
dispatch(doGetSync(passwordArgument, handlePreferencesAfterSync));
setInterval(() => { setInterval(() => {
dispatch(doGetSync(passwordArgument)); dispatch(doGetSync(passwordArgument, handlePreferencesAfterSync));
}, 1000 * 60 * 5); }, 1000 * 60 * 5);
}); });
} }
// @endif
Lbryio.call('user_settings', 'get').then(settings => {
dispatch(doPopulateSharedUserState(settings));
});
}; };
} }

View file

@ -1,16 +1,8 @@
// @flow // @flow
import { PAGE_SIZE } from 'constants/claim';
import * as ACTIONS from 'constants/action_types'; import * as ACTIONS from 'constants/action_types';
import * as SETTINGS from 'constants/settings';
import * as NOTIFICATION_TYPES from 'constants/subscriptions';
import { Lbryio, rewards, doClaimRewardType } from 'lbryinc'; import { Lbryio, rewards, doClaimRewardType } from 'lbryinc';
import { selectSubscriptions, selectUnreadByChannel } from 'redux/selectors/subscriptions'; import { selectUnreadByChannel } from 'redux/selectors/subscriptions';
import { makeSelectClientSetting } from 'redux/selectors/settings'; import { parseURI, doResolveUris } from 'lbry-redux';
import { Lbry, parseURI, doResolveUris } from 'lbry-redux';
import { doPlayUri } from 'redux/actions/content';
const CHECK_SUBSCRIPTIONS_INTERVAL = 15 * 60 * 1000;
const SUBSCRIPTION_DOWNLOAD_LIMIT = 1;
export const doSetViewMode = (viewMode: ViewMode) => (dispatch: Dispatch) => export const doSetViewMode = (viewMode: ViewMode) => (dispatch: Dispatch) =>
dispatch({ dispatch({
@ -73,7 +65,6 @@ export const doFetchMySubscriptions = () => (dispatch: Dispatch, getState: GetSt
}); });
dispatch(doResolveUris(subscriptions.map(({ uri }) => uri))); dispatch(doResolveUris(subscriptions.map(({ uri }) => uri)));
dispatch(doCheckSubscriptions());
}) })
.catch(() => { .catch(() => {
dispatch({ dispatch({
@ -190,6 +181,7 @@ export const doRemoveUnreadSubscription = (channelUri: string, readUri: string)
dispatch(doRemoveUnreadSubscriptions(channelUri, [readUri])); dispatch(doRemoveUnreadSubscriptions(channelUri, [readUri]));
}; };
<<<<<<< HEAD
export const doCheckSubscription = (subscriptionUri: string, shouldNotify?: boolean) => ( export const doCheckSubscription = (subscriptionUri: string, shouldNotify?: boolean) => (
dispatch: Dispatch, dispatch: Dispatch,
getState: GetState getState: GetState
@ -290,6 +282,8 @@ export const doCheckSubscription = (subscriptionUri: string, shouldNotify?: bool
}); });
}; };
=======
>>>>>>> use sdk preference endpoints
export const doChannelSubscribe = (subscription: Subscription) => (dispatch: Dispatch, getState: GetState) => { export const doChannelSubscribe = (subscription: Subscription) => (dispatch: Dispatch, getState: GetState) => {
const { const {
settings: { daemonSettings }, settings: { daemonSettings },
@ -318,8 +312,6 @@ export const doChannelSubscribe = (subscription: Subscription) => (dispatch: Dis
dispatch(doClaimRewardType(rewards.TYPE_SUBSCRIPTION, { failSilently: true })); dispatch(doClaimRewardType(rewards.TYPE_SUBSCRIPTION, { failSilently: true }));
} }
dispatch(doCheckSubscription(subscription.uri, true));
}; };
export const doChannelUnsubscribe = (subscription: Subscription) => (dispatch: Dispatch, getState: GetState) => { export const doChannelUnsubscribe = (subscription: Subscription) => (dispatch: Dispatch, getState: GetState) => {
@ -342,6 +334,7 @@ export const doChannelUnsubscribe = (subscription: Subscription) => (dispatch: D
} }
}; };
<<<<<<< HEAD
export const doCheckSubscriptions = () => (dispatch: Dispatch, getState: GetState) => { export const doCheckSubscriptions = () => (dispatch: Dispatch, getState: GetState) => {
const state = getState(); const state = getState();
const subscriptions = selectSubscriptions(state); const subscriptions = selectSubscriptions(state);
@ -363,6 +356,8 @@ export const doCheckSubscriptionsInit = () => (dispatch: Dispatch) => {
}); });
}; };
=======
>>>>>>> use sdk preference endpoints
export const doFetchRecommendedSubscriptions = () => (dispatch: Dispatch) => { export const doFetchRecommendedSubscriptions = () => (dispatch: Dispatch) => {
dispatch({ dispatch({
type: ACTIONS.GET_SUGGESTED_SUBSCRIPTIONS_START, type: ACTIONS.GET_SUGGESTED_SUBSCRIPTIONS_START,

View file

@ -1,3 +1,5 @@
import * as ACTIONS from 'constants/action_types';
import * as SETTINGS from 'constants/settings';
import { persistStore, persistReducer } from 'redux-persist'; import { persistStore, persistReducer } from 'redux-persist';
import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2'; import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2';
import createCompressor from 'redux-persist-transform-compress'; import createCompressor from 'redux-persist-transform-compress';
@ -8,8 +10,10 @@ import thunk from 'redux-thunk';
import { createHashHistory, createBrowserHistory } from 'history'; import { createHashHistory, createBrowserHistory } from 'history';
import { routerMiddleware } from 'connected-react-router'; import { routerMiddleware } from 'connected-react-router';
import createRootReducer from './reducers'; import createRootReducer from './reducers';
import { Lbryio } from 'lbryinc'; import { buildSharedStateMiddleware, ACTIONS as LBRY_REDUX_ACTIONS } from 'lbry-redux';
import isEqual from 'util/deep-equal'; import { doGetSync, selectUserVerifiedEmail } from 'lbryinc';
import { getSavedPassword } from 'util/saved-passwords';
import { makeSelectClientSetting } from 'redux/selectors/settings';
function isFunction(object) { function isFunction(object) {
return typeof object === 'function'; return typeof object === 'function';
@ -50,6 +54,7 @@ const appFilter = createFilter('app', ['hasClickedComment', 'searchOptionsExpand
const walletFilter = createFilter('wallet', ['receiveAddress']); const walletFilter = createFilter('wallet', ['receiveAddress']);
const searchFilter = createFilter('search', ['options']); const searchFilter = createFilter('search', ['options']);
const tagsFilter = createFilter('tags', ['followedTags']); const tagsFilter = createFilter('tags', ['followedTags']);
const subscriptionsFilter = createFilter('subscriptions', ['subscriptions']);
const blockedFilter = createFilter('blocked', ['blockedChannels']); const blockedFilter = createFilter('blocked', ['blockedChannels']);
const whiteListedReducers = [ const whiteListedReducers = [
// @if TARGET='app' // @if TARGET='app'
@ -64,6 +69,7 @@ const whiteListedReducers = [
'blocked', 'blocked',
'settings', 'settings',
'sync', 'sync',
'subscriptions',
]; ];
const transforms = [ const transforms = [
@ -77,6 +83,7 @@ const transforms = [
searchFilter, searchFilter,
tagsFilter, tagsFilter,
contentFilter, contentFilter,
subscriptionsFilter,
createCompressor(), createCompressor(),
]; ];
@ -98,10 +105,46 @@ history = createHashHistory();
history = createBrowserHistory(); history = createBrowserHistory();
// @endif // @endif
const sharedStateActions = [
ACTIONS.CHANNEL_SUBSCRIBE,
ACTIONS.CHANNEL_UNSUBSCRIBE,
LBRY_REDUX_ACTIONS.TOGGLE_TAG_FOLLOW,
LBRY_REDUX_ACTIONS.TOGGLE_BLOCK_CHANNEL,
];
/**
* source: the reducer name
* property: the property in the reducer-specific state
* transform: optional method to modify the value to be stored
*/
const sharedStateFilters = {
tags: { source: 'tags', property: 'followedTags' },
subscriptions: {
source: 'subscriptions',
property: 'subscriptions',
transform: function(value) {
return value.map(({ uri }) => uri);
},
},
blocked: { source: 'blocked', property: 'blockedChannels' },
};
const sharedStateCb = ({ dispatch, getState }) => {
const state = getState();
const syncEnabled = makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state);
const emailVerified = selectUserVerifiedEmail(state);
if (syncEnabled && emailVerified) {
getSavedPassword().then(savedPassword => {
dispatch(doGetSync(savedPassword));
});
}
};
const sharedStateMiddleware = buildSharedStateMiddleware(sharedStateActions, sharedStateFilters, sharedStateCb);
const rootReducer = createRootReducer(history); const rootReducer = createRootReducer(history);
const persistedReducer = persistReducer(persistOptions, rootReducer); const persistedReducer = persistReducer(persistOptions, rootReducer);
const bulkThunk = createBulkThunkMiddleware(); const bulkThunk = createBulkThunkMiddleware();
const middleware = [routerMiddleware(history), thunk, bulkThunk]; const middleware = [sharedStateMiddleware, routerMiddleware(history), thunk, bulkThunk];
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore( const store = createStore(
enableBatching(persistedReducer), enableBatching(persistedReducer),
@ -109,29 +152,6 @@ const store = createStore(
composeEnhancers(applyMiddleware(...middleware)) composeEnhancers(applyMiddleware(...middleware))
); );
let currentPayload;
store.subscribe(() => {
const state = store.getState();
const subscriptions = state.subscriptions.subscriptions.map(({ uri }) => uri);
const tags = state.tags.followedTags;
const authToken = state.user.accessToken;
const newPayload = {
version: '0.1',
shared: {
subscriptions,
tags,
},
};
if (!isEqual(newPayload, currentPayload)) {
currentPayload = newPayload;
if (authToken) {
Lbryio.call('user_settings', 'set', { settings: newPayload });
}
}
});
const persistor = persistStore(store); const persistor = persistStore(store);
window.persistor = persistor; window.persistor = persistor;

View file

@ -11,10 +11,16 @@ export const setSavedPassword = value => {
export const getSavedPassword = () => { export const getSavedPassword = () => {
return new Promise(resolve => { return new Promise(resolve => {
// @if TARGET='app'
ipcRenderer.once('get-password-response', (event, password) => { ipcRenderer.once('get-password-response', (event, password) => {
resolve(password); resolve(password);
}); });
ipcRenderer.send('get-password'); ipcRenderer.send('get-password');
// @endif
// @if TARGET='web'
resolve();
// @endif
}); });
}; };

View file

@ -6862,17 +6862,17 @@ 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#0a1c95a08835a6b892d853b156d4934e469c9589: lbry-redux@lbryio/lbry-redux#cd69946d4ca016c896639aa401d8f6b87d3e410e:
version "0.0.1" version "0.0.1"
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/0a1c95a08835a6b892d853b156d4934e469c9589" resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/cd69946d4ca016c896639aa401d8f6b87d3e410e"
dependencies: dependencies:
proxy-polyfill "0.1.6" proxy-polyfill "0.1.6"
reselect "^3.0.0" reselect "^3.0.0"
uuid "^3.3.2" uuid "^3.3.2"
lbryinc@lbryio/lbryinc#d1dba98bb6f1dc67bc0db4c0a20fc13b8a0de98b: lbryinc@lbryio/lbryinc#b8e1708ee4491db342c81576265e1b58f542bedb:
version "0.0.1" version "0.0.1"
resolved "https://codeload.github.com/lbryio/lbryinc/tar.gz/d1dba98bb6f1dc67bc0db4c0a20fc13b8a0de98b" resolved "https://codeload.github.com/lbryio/lbryinc/tar.gz/b8e1708ee4491db342c81576265e1b58f542bedb"
dependencies: dependencies:
reselect "^3.0.0" reselect "^3.0.0"