This commit is contained in:
zeppi 2021-09-26 23:06:34 -04:00
parent f251ad999e
commit 63196824a7
16 changed files with 249 additions and 169 deletions

View file

@ -58,6 +58,7 @@
"if-env": "^1.0.4", "if-env": "^1.0.4",
"match-sorter": "^6.3.0", "match-sorter": "^6.3.0",
"parse-duration": "^1.0.0", "parse-duration": "^1.0.0",
"keycloak-js": "^15.0.2",
"react-datetime-picker": "^3.2.1", "react-datetime-picker": "^3.2.1",
"react-plastic": "^1.1.1", "react-plastic": "^1.1.1",
"react-top-loading-bar": "^2.0.1", "react-top-loading-bar": "^2.0.1",
@ -156,7 +157,7 @@
"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#32b578707116d45f5b51b7ab523d200e75668676", "lbry-redux": "lbryio/lbry-redux#32b578707116d45f5b51b7ab523d200e75668676",
"lbryinc": "lbryio/lbryinc#0b4e41ef90d6347819dd3453f2f9398a5c1b4f36", "lbryinc": "lbryio/lbryinc#f133a7ed62c1f2202f98e785e923a7d841504d0d",
"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

@ -0,0 +1,6 @@
<html>
<body>
<h1>Hi</h1>
<script>parent.postMessage(location.href, location.origin)</script>
</body>
</html>

View file

@ -317,7 +317,7 @@ function App(props: Props) {
if (previousHasVerifiedEmail === false && hasVerifiedEmail) { if (previousHasVerifiedEmail === false && hasVerifiedEmail) {
analytics.emailVerifiedEvent(); analytics.emailVerifiedEvent();
} }
}, [previousHasVerifiedEmail, hasVerifiedEmail, signIn]); }, [previousHasVerifiedEmail, hasVerifiedEmail]);
useEffect(() => { useEffect(() => {
if (previousRewardApproved === false && isRewardApproved) { if (previousRewardApproved === false && isRewardApproved) {

View file

@ -3,7 +3,7 @@ import { ENABLE_NO_SOURCE_CLAIMS, CHANNEL_STAKED_LEVEL_LIVESTREAM, ENABLE_UI_NOT
import * as ICONS from 'constants/icons'; import * as ICONS from 'constants/icons';
import { SETTINGS } from 'lbry-redux'; import { SETTINGS } from 'lbry-redux';
import * as PAGES from 'constants/pages'; import * as PAGES from 'constants/pages';
import React from 'react'; import React, { useCallback } from 'react';
import { withRouter } from 'react-router'; import { withRouter } from 'react-router';
import classnames from 'classnames'; import classnames from 'classnames';
import Button from 'component/button'; import Button from 'component/button';
@ -16,6 +16,8 @@ import NotificationBubble from 'component/notificationBubble';
import NotificationHeaderButton from 'component/notificationHeaderButton'; import NotificationHeaderButton from 'component/notificationHeaderButton';
import ChannelThumbnail from 'component/channelThumbnail'; import ChannelThumbnail from 'component/channelThumbnail';
import SkipNavigationButton from 'component/skipNavigationButton'; import SkipNavigationButton from 'component/skipNavigationButton';
import keycloak from 'util/keycloak';
import Logo from 'component/logo'; import Logo from 'component/logo';
// @if TARGET='app' // @if TARGET='app'
import { remote } from 'electron'; import { remote } from 'electron';
@ -169,17 +171,21 @@ const Header = (props: Props) => {
setClientSetting(SETTINGS.THEME, 'dark', true); setClientSetting(SETTINGS.THEME, 'dark', true);
} }
} }
const login = useCallback(() => {
keycloak && keycloak.login().then((x) => console.log('cb', x));
}, [keycloak]);
const loginButtons = ( const loginButtons = (
<div className="header__auth-buttons"> <div className="header__auth-buttons">
<Button <Button
navigate={`/$/${PAGES.AUTH_SIGNIN}`} // navigate={`/$/${PAGES.AUTH_SIGNIN}`}
button="link" button="link"
label={__('Log In')} label={__('Log In')}
className="mobile-hidden" className="mobile-hidden"
disabled={user === null} disabled={user === null}
onClick={login}
/> />
<Button navigate={`/$/${PAGES.AUTH}`} button="primary" label={__('Sign Up')} disabled={user === null} /> {/*<Button navigate={`/$/${PAGES.AUTH}`} button="primary" label={__('Sign Up')} disabled={user === null} />*/}
</div> </div>
); );

View file

@ -1,4 +1,3 @@
import { DOMAIN } from 'config';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { doSetDaemonSetting } from 'redux/actions/settings'; import { doSetDaemonSetting } from 'redux/actions/settings';
import { doSetWelcomeVersion, doToggle3PAnalytics, doSignOut } from 'redux/actions/app'; import { doSetWelcomeVersion, doToggle3PAnalytics, doSignOut } from 'redux/actions/app';
@ -10,17 +9,16 @@ import { version as appVersion } from 'package.json';
import PrivacyAgreement from './view'; import PrivacyAgreement from './view';
const select = state => ({ const select = (state) => ({
authenticated: selectUserVerifiedEmail(state), authenticated: selectUserVerifiedEmail(state),
}); });
const perform = dispatch => ({ const perform = (dispatch) => ({
setWelcomeVersion: version => dispatch(doSetWelcomeVersion(version || WELCOME_VERSION)), setWelcomeVersion: (version) => dispatch(doSetWelcomeVersion(version || WELCOME_VERSION)),
setShareDataInternal: share => dispatch(doSetDaemonSetting(DAEMON_SETTINGS.SHARE_USAGE_DATA, share)), setShareDataInternal: (share) => dispatch(doSetDaemonSetting(DAEMON_SETTINGS.SHARE_USAGE_DATA, share)),
setShareDataThirdParty: share => dispatch(doToggle3PAnalytics(share)), setShareDataThirdParty: (share) => dispatch(doToggle3PAnalytics(share)),
signOut: () => dispatch(doSignOut()), signOut: () => dispatch(doSignOut()),
authenticateIfSharingData: () => authenticateIfSharingData: () => dispatch(doAuthenticate(appVersion, undefined, undefined, true)), // appVersion, shareData?, shareCallback, callInstall
dispatch(doAuthenticate(appVersion, undefined, undefined, true, undefined, undefined, DOMAIN)),
}); });
export default connect(select, perform)(PrivacyAgreement); export default connect(select, perform)(PrivacyAgreement);

View file

@ -12,6 +12,7 @@ import LoadingBarOneOff from 'component/loadingBarOneOff';
import { GetLinksData } from 'util/buildHomepage'; import { GetLinksData } from 'util/buildHomepage';
import HomePage from 'page/home'; import HomePage from 'page/home';
import keycloak from 'util/keycloak';
// @if TARGET='app' // @if TARGET='app'
const BackupPage = lazyImport(() => import('page/backup' /* webpackChunkName: "backup" */)); const BackupPage = lazyImport(() => import('page/backup' /* webpackChunkName: "backup" */));
@ -21,6 +22,8 @@ const BackupPage = lazyImport(() => import('page/backup' /* webpackChunkName: "b
const Code2257Page = lazyImport(() => import('web/page/code2257' /* webpackChunkName: "code2257" */)); const Code2257Page = lazyImport(() => import('web/page/code2257' /* webpackChunkName: "code2257" */));
// @endif // @endif
console.log('keycloak', keycloak)
// Chunk: "secondary" // Chunk: "secondary"
const SignInPage = lazyImport(() => import('page/signIn' /* webpackChunkName: "secondary" */)); const SignInPage = lazyImport(() => import('page/signIn' /* webpackChunkName: "secondary" */));
const SignInWalletPasswordPage = lazyImport(() => const SignInWalletPasswordPage = lazyImport(() =>
@ -86,7 +89,6 @@ const TopPage = lazyImport(() => import('page/top' /* webpackChunkName: "seconda
const UpdatePasswordPage = lazyImport(() => import('page/passwordUpdate' /* webpackChunkName: "passwordUpdate" */)); const UpdatePasswordPage = lazyImport(() => import('page/passwordUpdate' /* webpackChunkName: "passwordUpdate" */));
const Welcome = lazyImport(() => import('page/welcome' /* webpackChunkName: "secondary" */)); const Welcome = lazyImport(() => import('page/welcome' /* webpackChunkName: "secondary" */));
const YoutubeSyncPage = lazyImport(() => import('page/youtubeSync' /* webpackChunkName: "secondary" */)); const YoutubeSyncPage = lazyImport(() => import('page/youtubeSync' /* webpackChunkName: "secondary" */));
// Tell the browser we are handling scroll restoration // Tell the browser we are handling scroll restoration
if ('scrollRestoration' in history) { if ('scrollRestoration' in history) {
history.scrollRestoration = 'manual'; history.scrollRestoration = 'manual';
@ -128,14 +130,20 @@ function PrivateRoute(props: PrivateRouteProps) {
const { component: Component, isAuthenticated, ...rest } = props; const { component: Component, isAuthenticated, ...rest } = props;
const urlSearchParams = new URLSearchParams(props.location.search); const urlSearchParams = new URLSearchParams(props.location.search);
const redirectUrl = urlSearchParams.get('redirect'); const redirectUrl = urlSearchParams.get('redirect');
// const { keycloak } = useKeycloak();
return ( return (
<Route <Route
{...rest} {...rest}
render={(props) => render={(props) =>
isAuthenticated || !IS_WEB ? ( (isAuthenticated || (keycloak && keycloak.authenticated)) || !IS_WEB ? (
<Component {...props} /> <Component {...props} />
) : ( ) : (
<Redirect to={`/$/${PAGES.AUTH}?redirect=${redirectUrl || props.location.pathname}`} /> <Redirect
to={{
pathname: `/$/${PAGES.AUTH}?redirect=${redirectUrl || props.location.pathname}`,
state: { from: props.location },
}}
/>
) )
} }
/> />

View file

@ -14,7 +14,7 @@ import * as MODALS from 'constants/modal_types';
import React, { Fragment, useState, useEffect } from 'react'; import React, { Fragment, useState, useEffect } from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { doDaemonReady, doAutoUpdate, doOpenModal, doHideModal, doToggle3PAnalytics } from 'redux/actions/app'; import { doLbryReady, doAutoUpdate, doOpenModal, doHideModal, doToggle3PAnalytics } from 'redux/actions/app';
import { Lbry, isURIValid, apiCall } from 'lbry-redux'; import { Lbry, isURIValid, apiCall } from 'lbry-redux';
import { setSearchApi } from 'redux/actions/search'; import { setSearchApi } from 'redux/actions/search';
import { doSetLanguage, doFetchLanguage, doUpdateIsNightAsync } from 'redux/actions/settings'; import { doSetLanguage, doFetchLanguage, doUpdateIsNightAsync } from 'redux/actions/settings';
@ -28,15 +28,11 @@ import { formatLbryUrlForWeb, formatInAppUrl } from 'util/url';
import { PersistGate } from 'redux-persist/integration/react'; import { PersistGate } from 'redux-persist/integration/react';
import analytics from 'analytics'; import analytics from 'analytics';
import { doToast } from 'redux/actions/notifications'; import { doToast } from 'redux/actions/notifications';
import { import keycloak from 'util/keycloak';
getAuthToken,
setAuthToken,
doDeprecatedPasswordMigrationMarch2020,
doAuthTokenRefresh,
} from 'util/saved-passwords';
import { X_LBRY_AUTH_TOKEN } from 'constants/token';
import { LBRY_WEB_API, DEFAULT_LANGUAGE, LBRY_API_URL, LBRY_WEB_PUBLISH_API } from 'config';
import { getAuthToken, setAuthToken, doAuthTokenRefresh, getTokens, deleteAuthToken } from 'util/saved-passwords';
import { X_LBRY_AUTH_TOKEN } from 'constants/token';
import { LBRY_WEB_API, DEFAULT_LANGUAGE, LBRY_API_URL, LBRY_WEB_PUBLISH_API, URL as SITE_URL } from 'config';
// Import 3rd-party styles before ours for the current way we are code-splitting. // Import 3rd-party styles before ours for the current way we are code-splitting.
import 'scss/third-party.scss'; import 'scss/third-party.scss';
@ -98,6 +94,7 @@ Lbry.setOverride(
const startTime = Date.now(); const startTime = Date.now();
analytics.startupEvent(); analytics.startupEvent();
const isDev = process.env.NODE_ENV !== 'production';
// @if TARGET='app' // @if TARGET='app'
const { autoUpdater } = remote.require('electron-updater'); const { autoUpdater } = remote.require('electron-updater');
@ -112,28 +109,33 @@ if (process.env.SEARCH_API_URL) {
setSearchApi(process.env.SEARCH_API_URL); setSearchApi(process.env.SEARCH_API_URL);
} }
// Fix to make sure old users' cookies are set to the correct domain if (getTokens().auth_token) {
// This can be removed after March 11th, 2021
// https://github.com/lbryio/lbry-desktop/pull/3830
doDeprecatedPasswordMigrationMarch2020();
doAuthTokenRefresh(); doAuthTokenRefresh();
}
// We need to override Lbryio for getting/setting the authToken // We need to override Lbryio for getting/setting the authToken
// We interact with ipcRenderer to get the auth key from a users keyring // We interact with ipcRenderer to get the auth key from a users keyring
// We keep a local variable for authToken because `ipcRenderer.send` does not // We keep a local variable for authToken because `ipcRenderer.send` does not
// contain a response, so there is no way to know when it's been set // contain a response, so there is no way to know when it's been set
let authToken;
Lbryio.setOverride('setAuthToken', (authToken) => { Lbryio.setOverride('setAuthToken', (authToken) => {
setAuthToken(authToken); setAuthToken(authToken); // set the cookie to auth_token=
return authToken; return authToken;
}); });
Lbryio.setOverride('deleteAuthToken', () => deleteAuthToken());
Lbryio.setOverride(
'getTokens',
() =>
new Promise((resolve) => {
resolve(getTokens());
})
);
Lbryio.setOverride( Lbryio.setOverride(
'getAuthToken', 'getAuthToken',
() => () =>
new Promise((resolve) => { new Promise((resolve) => {
const authTokenToReturn = authToken || getAuthToken(); resolve(getAuthToken());
resolve(authTokenToReturn);
}) })
); );
@ -230,7 +232,9 @@ function AppWrapper() {
// Splash screen and sdk setup not needed on web // Splash screen and sdk setup not needed on web
const [readyToLaunch, setReadyToLaunch] = useState(IS_WEB); const [readyToLaunch, setReadyToLaunch] = useState(IS_WEB);
const [persistDone, setPersistDone] = useState(false); const [persistDone, setPersistDone] = useState(false);
const [keycloakReady, setKeycloakReady] = useState(false);
// init?
useEffect(() => { useEffect(() => {
// @if TARGET='app' // @if TARGET='app'
moment.locale(remote.app.getLocale()); moment.locale(remote.app.getLocale());
@ -254,19 +258,48 @@ function AppWrapper() {
// @endif // @endif
}, []); }, []);
function initKeycloak() {
console.dir(keycloak)
keycloak.init(
{ onLoad: 'check-sso',
silentCheckSsoFallback: false,
didInit: true,
redirectUri: isDev ? 'http://localhost:9090/' : `${SITE_URL}/`}
).then(function(authenticated) {
setKeycloakReady(true);
console.log('INIT: ', authenticated ? 'Authenticated' : 'Not Authenticated');
}).catch(function() {
console.log('INIT: FAILED');
});
}
useEffect(() => {
console.log('KCR RENDER', keycloakReady)
if (!keycloakReady) {
initKeycloak();
}
}, [keycloakReady]);
// initKeycloak();
useEffect(() => { useEffect(() => {
if (persistDone) { if (persistDone) {
app.store.dispatch(doToggle3PAnalytics(null, true)); app.store.dispatch(doToggle3PAnalytics(null, true));
} }
}, [persistDone]); }, [persistDone]);
/**
* We have assured we have the latest browser persist,
* that daemon has started up,
* and we have checked with keycloak for a token
*/
useEffect(() => { useEffect(() => {
if (readyToLaunch && persistDone) { if (readyToLaunch && persistDone && keycloakReady) { // keycloak ready
if (DEFAULT_LANGUAGE) { if (DEFAULT_LANGUAGE) {
app.store.dispatch(doFetchLanguage(DEFAULT_LANGUAGE)); app.store.dispatch(doFetchLanguage(DEFAULT_LANGUAGE));
} }
app.store.dispatch(doUpdateIsNightAsync()); app.store.dispatch(doUpdateIsNightAsync());
app.store.dispatch(doDaemonReady()); app.store.dispatch(doLbryReady());
app.store.dispatch(doBlackListedOutpointsSubscribe()); app.store.dispatch(doBlackListedOutpointsSubscribe());
app.store.dispatch(doFilteredOutpointsSubscribe()); app.store.dispatch(doFilteredOutpointsSubscribe());
@ -274,7 +307,11 @@ function AppWrapper() {
const timeToStart = appReadyTime - startTime; const timeToStart = appReadyTime - startTime;
analytics.readyEvent(timeToStart); analytics.readyEvent(timeToStart);
} }
}, [readyToLaunch, persistDone]); }, [readyToLaunch, persistDone, keycloakReady]);
useEffect(() => {
console.dir(keycloak);
}, [keycloak]);
return ( return (
<Provider store={store}> <Provider store={store}>

View file

@ -6,7 +6,7 @@ import { ipcRenderer, remote } from 'electron';
import path from 'path'; 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 { DOMAIN, SIMPLE_SITE } from 'config'; import { SIMPLE_SITE } from 'config';
import { import {
Lbry, Lbry,
doBalanceSubscribe, doBalanceSubscribe,
@ -353,7 +353,7 @@ export function doAlertWaitingForSync() {
}; };
} }
export function doDaemonReady() { export function doLbryReady() {
return (dispatch, getState) => { return (dispatch, getState) => {
const state = getState(); const state = getState();
@ -363,8 +363,6 @@ export function doDaemonReady() {
dispatch( dispatch(
doAuthenticate( doAuthenticate(
appVersion, appVersion,
undefined,
undefined,
shareUsageData, shareUsageData,
(status) => { (status) => {
const trendingAlgorithm = const trendingAlgorithm =
@ -377,8 +375,7 @@ export function doDaemonReady() {
analytics.trendingAlgorithmEvent(trendingAlgorithm); analytics.trendingAlgorithmEvent(trendingAlgorithm);
} }
}, },
undefined, undefined
DOMAIN
) )
); );
dispatch({ type: ACTIONS.DAEMON_READY }); dispatch({ type: ACTIONS.DAEMON_READY });

View file

@ -50,11 +50,8 @@ export function doFetchInviteStatus(shouldCallRewardList = true) {
}; };
} }
export function doInstallNew(appVersion, os = null, firebaseToken = null, callbackForUsersWhoAreSharingData, domain) { export function doInstallNew(appVersion, callbackForUsersWhoAreSharingData, domain) {
const payload = { app_version: appVersion, domain }; const payload = { app_version: appVersion, domain };
if (firebaseToken) {
payload.firebase_token = firebaseToken;
}
Lbry.status().then((status) => { Lbry.status().then((status) => {
payload.app_id = payload.app_id =
@ -64,7 +61,7 @@ export function doInstallNew(appVersion, os = null, firebaseToken = null, callba
payload.node_id = status.lbry_id; payload.node_id = status.lbry_id;
Lbry.version().then((version) => { Lbry.version().then((version) => {
payload.daemon_version = version.lbrynet_version; payload.daemon_version = version.lbrynet_version;
payload.operating_system = os || version.os_system; payload.operating_system = version.os_system;
payload.platform = version.platform; payload.platform = version.platform;
Lbryio.call('install', 'new', payload); Lbryio.call('install', 'new', payload);
@ -75,30 +72,6 @@ export function doInstallNew(appVersion, os = null, firebaseToken = null, callba
}); });
} }
export function doInstallNewWithParams(
appVersion,
installationId,
nodeId,
lbrynetVersion,
os,
platform,
firebaseToken = null
) {
return () => {
const payload = { app_version: appVersion };
if (firebaseToken) {
payload.firebase_token = firebaseToken;
}
payload.app_id = installationId;
payload.node_id = nodeId;
payload.daemon_version = lbrynetVersion;
payload.operating_system = os;
payload.platform = platform;
Lbryio.call('install', 'new', payload);
};
}
function checkAuthBusy() { function checkAuthBusy() {
let time = Date.now(); let time = Date.now();
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
@ -131,48 +104,59 @@ function checkAuthBusy() {
} }
// TODO: Call doInstallNew separately so we don't have to pass appVersion and os_system params? // TODO: Call doInstallNew separately so we don't have to pass appVersion and os_system params?
/**
*
* @param appVersion
* @param shareUsageData
* @param callbackForUsersWhoAreSharingData
* @param callInstall
* @returns {Function}
*
* Lbryio.fetchUser:
* getTokens then getCurrentUser, and if !user, call userNew and return the user
*
*/
export function doAuthenticate( export function doAuthenticate(
appVersion, appVersion,
os = null,
firebaseToken = null,
shareUsageData = true, shareUsageData = true,
callbackForUsersWhoAreSharingData, callbackForUsersWhoAreSharingData,
callInstall = true, callInstall = true
domain = null
) { ) {
return (dispatch) => { return async (dispatch) => {
dispatch({ dispatch({
type: ACTIONS.AUTHENTICATION_STARTED, type: ACTIONS.AUTHENTICATION_STARTED,
}); });
checkAuthBusy() try {
.then(() => { await checkAuthBusy();
return Lbryio.authenticate(DOMAIN, getDefaultLanguage()); const user = await Lbryio.fetchUser(DOMAIN, getDefaultLanguage());
}) console.log('USER', user);
.then((user) => {
if (sessionStorageAvailable) window.sessionStorage.removeItem(AUTH_IN_PROGRESS); if (sessionStorageAvailable) window.sessionStorage.removeItem(AUTH_IN_PROGRESS);
Lbryio.getAuthToken().then((token) => { // put this back: accessToken: tokens.access_token
if (user.error) {
throw new Error(user.error.message);
} else {
Lbryio.getTokens().then((tokens) => {
dispatch({ dispatch({
type: ACTIONS.AUTHENTICATION_SUCCESS, type: ACTIONS.AUTHENTICATION_SUCCESS,
data: { user, accessToken: token }, data: { user, accessToken: tokens.auth_token }, // rename 'accessToken' = authToken
}); });
if (shareUsageData) { if (shareUsageData) {
dispatch(doRewardList()); dispatch(doRewardList());
dispatch(doFetchInviteStatus(false)); dispatch(doFetchInviteStatus(false));
if (callInstall) { if (callInstall) {
doInstallNew(appVersion, os, firebaseToken, callbackForUsersWhoAreSharingData, domain); doInstallNew(appVersion, callbackForUsersWhoAreSharingData, DOMAIN);
} }
} }
}); });
}) }
.catch((error) => { } catch (error) {
if (sessionStorageAvailable) window.sessionStorage.removeItem(AUTH_IN_PROGRESS); if (sessionStorageAvailable) window.sessionStorage.removeItem(AUTH_IN_PROGRESS);
dispatch({ dispatch({
type: ACTIONS.AUTHENTICATION_FAILURE, type: ACTIONS.AUTHENTICATION_FAILURE,
data: { error }, data: { error },
}); });
}); }
}; };
} }
@ -183,7 +167,7 @@ export function doUserFetch() {
type: ACTIONS.USER_FETCH_STARTED, type: ACTIONS.USER_FETCH_STARTED,
}); });
Lbryio.getCurrentUser() Lbryio.fetchCurrentUser()
.then((user) => { .then((user) => {
dispatch({ dispatch({
type: ACTIONS.USER_FETCH_SUCCESS, type: ACTIONS.USER_FETCH_SUCCESS,
@ -204,7 +188,7 @@ export function doUserFetch() {
export function doUserCheckEmailVerified() { export function doUserCheckEmailVerified() {
// This will happen in the background so we don't need loading booleans // This will happen in the background so we don't need loading booleans
return (dispatch) => { return (dispatch) => {
Lbryio.getCurrentUser().then((user) => { Lbryio.fetchCurrentUser().then((user) => {
if (user.has_verified_email) { if (user.has_verified_email) {
dispatch(doRewardList()); dispatch(doRewardList());

View file

@ -24,37 +24,44 @@ const defaultState = {
invitees: undefined, invitees: undefined,
referralLink: undefined, referralLink: undefined,
referralCode: undefined, referralCode: undefined,
user: undefined, user: { has_verified_email: false },
accessToken: undefined, accessToken: undefined, // rename this
youtubeChannelImportPending: false, youtubeChannelImportPending: false,
youtubeChannelImportErrorMessage: '', youtubeChannelImportErrorMessage: '',
referrerSetIsPending: false, referrerSetIsPending: false,
referrerSetError: '', referrerSetError: '',
}; };
reducers[ACTIONS.AUTHENTICATION_STARTED] = state => // does this do anything with user?
reducers[ACTIONS.AUTHENTICATION_STARTED] = (state) =>
Object.assign({}, state, { Object.assign({}, state, {
authenticationIsPending: true, authenticationIsPending: true,
userIsPending: true, userIsPending: true,
// distinguish accessToken from authToken
accessToken: defaultState.accessToken, accessToken: defaultState.accessToken,
}); });
reducers[ACTIONS.AUTHENTICATION_SUCCESS] = (state, action) => reducers[ACTIONS.AUTHENTICATION_SUCCESS] = (state, action) => {
const newUserState = Object.assign({}, state, {
authenticationIsPending: false,
userIsPending: false,
});
if (action.data && action.data.user) {
newUserState.user = action.data.user;
}
if (action.data && action.data.accessToken) {
newUserState.accessToken = action.data.accessToken;
}
return newUserState;
};
reducers[ACTIONS.AUTHENTICATION_FAILURE] = (state) =>
Object.assign({}, state, { Object.assign({}, state, {
authenticationIsPending: false, authenticationIsPending: false,
userIsPending: false, userIsPending: false,
accessToken: action.data.accessToken,
user: action.data.user,
}); });
reducers[ACTIONS.AUTHENTICATION_FAILURE] = state => reducers[ACTIONS.USER_FETCH_STARTED] = (state) =>
Object.assign({}, state, {
authenticationIsPending: false,
userIsPending: false,
user: null,
});
reducers[ACTIONS.USER_FETCH_STARTED] = state =>
Object.assign({}, state, { Object.assign({}, state, {
userIsPending: true, userIsPending: true,
}); });
@ -66,7 +73,7 @@ reducers[ACTIONS.USER_FETCH_SUCCESS] = (state, action) =>
emailToVerify: action.data.user.has_verified_email ? null : state.emailToVerify, emailToVerify: action.data.user.has_verified_email ? null : state.emailToVerify,
}); });
reducers[ACTIONS.USER_FETCH_FAILURE] = state => reducers[ACTIONS.USER_FETCH_FAILURE] = (state) =>
Object.assign({}, state, { Object.assign({}, state, {
userIsPending: true, userIsPending: true,
user: null, user: null,
@ -88,7 +95,7 @@ reducers[ACTIONS.USER_PHONE_NEW_SUCCESS] = (state, action) =>
phoneNewIsPending: false, phoneNewIsPending: false,
}); });
reducers[ACTIONS.USER_PHONE_RESET] = state => reducers[ACTIONS.USER_PHONE_RESET] = (state) =>
Object.assign({}, state, { Object.assign({}, state, {
phoneToVerify: null, phoneToVerify: null,
}); });
@ -99,7 +106,7 @@ reducers[ACTIONS.USER_PHONE_NEW_FAILURE] = (state, action) =>
phoneNewErrorMessage: action.data.error, phoneNewErrorMessage: action.data.error,
}); });
reducers[ACTIONS.USER_PHONE_VERIFY_STARTED] = state => reducers[ACTIONS.USER_PHONE_VERIFY_STARTED] = (state) =>
Object.assign({}, state, { Object.assign({}, state, {
phoneVerifyIsPending: true, phoneVerifyIsPending: true,
phoneVerifyErrorMessage: '', phoneVerifyErrorMessage: '',
@ -118,7 +125,7 @@ reducers[ACTIONS.USER_PHONE_VERIFY_FAILURE] = (state, action) =>
phoneVerifyErrorMessage: action.data.error, phoneVerifyErrorMessage: action.data.error,
}); });
reducers[ACTIONS.USER_EMAIL_NEW_STARTED] = state => reducers[ACTIONS.USER_EMAIL_NEW_STARTED] = (state) =>
Object.assign({}, state, { Object.assign({}, state, {
emailNewIsPending: true, emailNewIsPending: true,
emailNewErrorMessage: '', emailNewErrorMessage: '',
@ -136,12 +143,12 @@ reducers[ACTIONS.USER_EMAIL_NEW_SUCCESS] = (state, action) => {
}); });
}; };
reducers[ACTIONS.USER_EMAIL_NEW_EXISTS] = state => reducers[ACTIONS.USER_EMAIL_NEW_EXISTS] = (state) =>
Object.assign({}, state, { Object.assign({}, state, {
emailAlreadyExists: true, emailAlreadyExists: true,
}); });
reducers[ACTIONS.USER_EMAIL_NEW_DOES_NOT_EXIST] = state => reducers[ACTIONS.USER_EMAIL_NEW_DOES_NOT_EXIST] = (state) =>
Object.assign({}, state, { Object.assign({}, state, {
emailDoesNotExist: true, emailDoesNotExist: true,
}); });
@ -152,7 +159,7 @@ reducers[ACTIONS.USER_EMAIL_NEW_FAILURE] = (state, action) =>
emailNewErrorMessage: action.data.error, emailNewErrorMessage: action.data.error,
}); });
reducers[ACTIONS.USER_EMAIL_NEW_CLEAR_ENTRY] = state => { reducers[ACTIONS.USER_EMAIL_NEW_CLEAR_ENTRY] = (state) => {
const newUser = { ...state.user }; const newUser = { ...state.user };
delete newUser.primary_email; delete newUser.primary_email;
@ -166,7 +173,7 @@ reducers[ACTIONS.USER_EMAIL_NEW_CLEAR_ENTRY] = state => {
}); });
}; };
reducers[ACTIONS.USER_PASSWORD_SET_CLEAR] = state => reducers[ACTIONS.USER_PASSWORD_SET_CLEAR] = (state) =>
Object.assign({}, state, { Object.assign({}, state, {
passwordResetSuccess: false, passwordResetSuccess: false,
passwordResetPending: false, passwordResetPending: false,
@ -175,7 +182,7 @@ reducers[ACTIONS.USER_PASSWORD_SET_CLEAR] = state =>
passwordSetSuccess: false, passwordSetSuccess: false,
}); });
reducers[ACTIONS.USER_EMAIL_VERIFY_STARTED] = state => reducers[ACTIONS.USER_EMAIL_VERIFY_STARTED] = (state) =>
Object.assign({}, state, { Object.assign({}, state, {
emailVerifyIsPending: true, emailVerifyIsPending: true,
emailVerifyErrorMessage: '', emailVerifyErrorMessage: '',
@ -202,7 +209,7 @@ reducers[ACTIONS.USER_EMAIL_VERIFY_SET] = (state, action) =>
emailToVerify: action.data.email, emailToVerify: action.data.email,
}); });
reducers[ACTIONS.USER_IDENTITY_VERIFY_STARTED] = state => reducers[ACTIONS.USER_IDENTITY_VERIFY_STARTED] = (state) =>
Object.assign({}, state, { Object.assign({}, state, {
identityVerifyIsPending: true, identityVerifyIsPending: true,
identityVerifyErrorMessage: '', identityVerifyErrorMessage: '',
@ -229,7 +236,7 @@ reducers[ACTIONS.FETCH_ACCESS_TOKEN_SUCCESS] = (state, action) => {
}); });
}; };
reducers[ACTIONS.USER_INVITE_STATUS_FETCH_STARTED] = state => reducers[ACTIONS.USER_INVITE_STATUS_FETCH_STARTED] = (state) =>
Object.assign({}, state, { Object.assign({}, state, {
inviteStatusIsPending: true, inviteStatusIsPending: true,
}); });
@ -243,13 +250,13 @@ reducers[ACTIONS.USER_INVITE_STATUS_FETCH_SUCCESS] = (state, action) =>
referralCode: action.data.referralCode, referralCode: action.data.referralCode,
}); });
reducers[ACTIONS.USER_INVITE_NEW_STARTED] = state => reducers[ACTIONS.USER_INVITE_NEW_STARTED] = (state) =>
Object.assign({}, state, { Object.assign({}, state, {
inviteNewIsPending: true, inviteNewIsPending: true,
inviteNewErrorMessage: '', inviteNewErrorMessage: '',
}); });
reducers[ACTIONS.USER_INVITE_NEW_SUCCESS] = state => reducers[ACTIONS.USER_INVITE_NEW_SUCCESS] = (state) =>
Object.assign({}, state, { Object.assign({}, state, {
inviteNewIsPending: false, inviteNewIsPending: false,
inviteNewErrorMessage: '', inviteNewErrorMessage: '',
@ -261,14 +268,14 @@ reducers[ACTIONS.USER_INVITE_NEW_FAILURE] = (state, action) =>
inviteNewErrorMessage: action.data.error.message, inviteNewErrorMessage: action.data.error.message,
}); });
reducers[ACTIONS.USER_INVITE_STATUS_FETCH_FAILURE] = state => reducers[ACTIONS.USER_INVITE_STATUS_FETCH_FAILURE] = (state) =>
Object.assign({}, state, { Object.assign({}, state, {
inviteStatusIsPending: false, inviteStatusIsPending: false,
invitesRemaining: null, invitesRemaining: null,
invitees: null, invitees: null,
}); });
reducers[ACTIONS.USER_YOUTUBE_IMPORT_STARTED] = state => reducers[ACTIONS.USER_YOUTUBE_IMPORT_STARTED] = (state) =>
Object.assign({}, state, { Object.assign({}, state, {
youtubeChannelImportPending: true, youtubeChannelImportPending: true,
youtubeChannelImportErrorMessage: '', youtubeChannelImportErrorMessage: '',
@ -293,28 +300,28 @@ reducers[ACTIONS.USER_YOUTUBE_IMPORT_FAILURE] = (state, action) =>
youtubeChannelImportErrorMessage: action.data, youtubeChannelImportErrorMessage: action.data,
}); });
reducers[ACTIONS.USER_EMAIL_VERIFY_RETRY_STARTED] = state => reducers[ACTIONS.USER_EMAIL_VERIFY_RETRY_STARTED] = (state) =>
Object.assign({}, state, { Object.assign({}, state, {
resendingVerificationEmail: true, resendingVerificationEmail: true,
}); });
reducers[ACTIONS.USER_EMAIL_VERIFY_RETRY_SUCCESS] = state => reducers[ACTIONS.USER_EMAIL_VERIFY_RETRY_SUCCESS] = (state) =>
Object.assign({}, state, { Object.assign({}, state, {
resendingVerificationEmail: false, resendingVerificationEmail: false,
}); });
reducers[ACTIONS.USER_EMAIL_VERIFY_RETRY_FAILURE] = state => reducers[ACTIONS.USER_EMAIL_VERIFY_RETRY_FAILURE] = (state) =>
Object.assign({}, state, { Object.assign({}, state, {
resendingVerificationEmail: false, resendingVerificationEmail: false,
}); });
reducers[ACTIONS.USER_SET_REFERRER_STARTED] = state => reducers[ACTIONS.USER_SET_REFERRER_STARTED] = (state) =>
Object.assign({}, state, { Object.assign({}, state, {
referrerSetIsPending: true, referrerSetIsPending: true,
referrerSetError: defaultState.referrerSetError, referrerSetError: defaultState.referrerSetError,
}); });
reducers[ACTIONS.USER_SET_REFERRER_SUCCESS] = state => reducers[ACTIONS.USER_SET_REFERRER_SUCCESS] = (state) =>
Object.assign({}, state, { Object.assign({}, state, {
referrerSetIsPending: false, referrerSetIsPending: false,
referrerSetError: defaultState.referrerSetError, referrerSetError: defaultState.referrerSetError,
@ -326,25 +333,25 @@ reducers[ACTIONS.USER_SET_REFERRER_FAILURE] = (state, action) =>
referrerSetError: action.data.error.message, referrerSetError: action.data.error.message,
}); });
reducers[ACTIONS.USER_SET_REFERRER_RESET] = state => reducers[ACTIONS.USER_SET_REFERRER_RESET] = (state) =>
Object.assign({}, state, { Object.assign({}, state, {
referrerSetIsPending: false, referrerSetIsPending: false,
referrerSetError: defaultState.referrerSetError, referrerSetError: defaultState.referrerSetError,
}); });
reducers[ACTIONS.USER_PASSWORD_EXISTS] = state => reducers[ACTIONS.USER_PASSWORD_EXISTS] = (state) =>
Object.assign({}, state, { Object.assign({}, state, {
passwordExistsForUser: true, passwordExistsForUser: true,
}); });
reducers[ACTIONS.USER_PASSWORD_RESET_STARTED] = state => reducers[ACTIONS.USER_PASSWORD_RESET_STARTED] = (state) =>
Object.assign({}, state, { Object.assign({}, state, {
passwordResetPending: true, passwordResetPending: true,
passwordResetSuccess: defaultState.passwordResetSuccess, passwordResetSuccess: defaultState.passwordResetSuccess,
passwordResetError: null, passwordResetError: null,
}); });
reducers[ACTIONS.USER_PASSWORD_RESET_SUCCESS] = state => reducers[ACTIONS.USER_PASSWORD_RESET_SUCCESS] = (state) =>
Object.assign({}, state, { Object.assign({}, state, {
passwordResetPending: false, passwordResetPending: false,
passwordResetSuccess: true, passwordResetSuccess: true,
@ -356,13 +363,13 @@ reducers[ACTIONS.USER_PASSWORD_RESET_FAILURE] = (state, action) =>
passwordResetError: action.data.error, passwordResetError: action.data.error,
}); });
reducers[ACTIONS.USER_PASSWORD_SET_STARTED] = state => reducers[ACTIONS.USER_PASSWORD_SET_STARTED] = (state) =>
Object.assign({}, state, { Object.assign({}, state, {
passwordSetPending: true, passwordSetPending: true,
passwordSetSuccess: defaultState.passwordSetSuccess, passwordSetSuccess: defaultState.passwordSetSuccess,
}); });
reducers[ACTIONS.USER_PASSWORD_SET_SUCCESS] = state => reducers[ACTIONS.USER_PASSWORD_SET_SUCCESS] = (state) =>
Object.assign({}, state, { Object.assign({}, state, {
passwordSetPending: false, passwordSetPending: false,
passwordSetSuccess: true, passwordSetSuccess: true,

View file

@ -11,7 +11,7 @@ import { routerMiddleware } from 'connected-react-router';
import createRootReducer from './reducers'; import createRootReducer from './reducers';
import { Lbry, buildSharedStateMiddleware, ACTIONS as LBRY_REDUX_ACTIONS } from 'lbry-redux'; import { Lbry, buildSharedStateMiddleware, ACTIONS as LBRY_REDUX_ACTIONS } from 'lbry-redux';
import { doSyncLoop } from 'redux/actions/sync'; import { doSyncLoop } from 'redux/actions/sync';
import { getAuthToken } from 'util/saved-passwords'; import { getTokens } from 'util/saved-passwords';
import { generateInitialUrl } from 'util/url'; import { generateInitialUrl } from 'util/url';
import { X_LBRY_AUTH_TOKEN } from 'constants/token'; import { X_LBRY_AUTH_TOKEN } from 'constants/token';
@ -189,12 +189,14 @@ const sharedStateCb = ({ dispatch, getState }) => {
const populateAuthTokenHeader = () => { const populateAuthTokenHeader = () => {
return (next) => (action) => { return (next) => (action) => {
if ( if (action.type === ACTIONS.USER_FETCH_SUCCESS || action.type === ACTIONS.AUTHENTICATION_SUCCESS) {
(action.type === ACTIONS.USER_FETCH_SUCCESS || action.type === ACTIONS.AUTHENTICATION_SUCCESS) && if (action.data) {
action.data.user.has_verified_email === true }
) { const tokens = getTokens();
const authToken = getAuthToken(); // if (tokens.access_token) {
Lbry.setApiHeader(X_LBRY_AUTH_TOKEN, authToken); // Lbry.setApiHeader('Authorization', 'Bearer ' + tokens.access_token);
// }
Lbry.setApiHeader(X_LBRY_AUTH_TOKEN, tokens.auth_token);
} }
return next(action); return next(action);

14
ui/util/keycloak.js Normal file
View file

@ -0,0 +1,14 @@
import Keycloak from 'keycloak-js';
// Setup Keycloak instance as needed
// Pass initialization options as required or leave blank to load from 'keycloak.json'
const keycloak = Keycloak({
url: 'https://sso.odysee.com/auth',
realm: 'Users',
clientId: 'odysee.com',
pkceMethod: 'S256',
onLoad: 'check-sso',
silentCheckSsoRedirectUri: window.location.origin + '/silent-check-sso.html',
});
export default keycloak;

View file

@ -1,4 +1,4 @@
const { DOMAIN } = require('../../config.js'); const { DOMAIN } = require('config.js');
const AUTH_TOKEN = 'auth_token'; const AUTH_TOKEN = 'auth_token';
const SAVED_PASSWORD = 'saved_password'; const SAVED_PASSWORD = 'saved_password';
const DEPRECATED_SAVED_PASSWORD = 'saved-password'; const DEPRECATED_SAVED_PASSWORD = 'saved-password';
@ -6,6 +6,7 @@ const domain =
typeof window === 'object' && window.location.hostname.includes('localhost') ? window.location.hostname : DOMAIN; typeof window === 'object' && window.location.hostname.includes('localhost') ? window.location.hostname : DOMAIN;
const isProduction = process.env.NODE_ENV === 'production'; const isProduction = process.env.NODE_ENV === 'production';
const maxExpiration = 2147483647; const maxExpiration = 2147483647;
const { default: keycloak } = require('util/keycloak');
let sessionPassword; let sessionPassword;
function setCookie(name, value, expirationDaysOnWeb) { function setCookie(name, value, expirationDaysOnWeb) {
@ -59,7 +60,7 @@ function deleteCookie(name) {
} }
function setSavedPassword(value, saveToDisk) { function setSavedPassword(value, saveToDisk) {
return new Promise(resolve => { return new Promise((resolve) => {
const password = value === undefined || value === null ? '' : value; const password = value === undefined || value === null ? '' : value;
sessionPassword = password; sessionPassword = password;
@ -74,17 +75,17 @@ function setSavedPassword(value, saveToDisk) {
} }
function getSavedPassword() { function getSavedPassword() {
return new Promise(resolve => { return new Promise((resolve) => {
if (sessionPassword) { if (sessionPassword) {
resolve(sessionPassword); resolve(sessionPassword);
} }
return getPasswordFromCookie().then(p => resolve(p)); return getPasswordFromCookie().then((p) => resolve(p));
}); });
} }
function getPasswordFromCookie() { function getPasswordFromCookie() {
return new Promise(resolve => { return new Promise((resolve) => {
let password; let password;
password = getCookie(SAVED_PASSWORD); password = getCookie(SAVED_PASSWORD);
resolve(password); resolve(password);
@ -92,7 +93,7 @@ function getPasswordFromCookie() {
} }
function deleteSavedPassword() { function deleteSavedPassword() {
return new Promise(resolve => { return new Promise((resolve) => {
deleteCookie(SAVED_PASSWORD); deleteCookie(SAVED_PASSWORD);
resolve(); resolve();
}); });
@ -102,19 +103,28 @@ function getAuthToken() {
return getCookie(AUTH_TOKEN); return getCookie(AUTH_TOKEN);
} }
// will take oidc token getter
// function getTokens() {
// return { auth_token: getAuthToken(), access_token: null };
// }
function getTokens() {
return { auth_token: getAuthToken(), access_token: keycloak.token };
}
function setAuthToken(value) { function setAuthToken(value) {
return setCookie(AUTH_TOKEN, value, 365); return setCookie(AUTH_TOKEN, value, 365);
} }
function deleteAuthToken() { function deleteAuthToken() {
return new Promise(resolve => { return new Promise((resolve) => {
deleteCookie(AUTH_TOKEN); deleteCookie(AUTH_TOKEN);
resolve(); resolve();
}); });
} }
function doSignOutCleanup() { function doSignOutCleanup() {
return new Promise(resolve => { return new Promise((resolve) => {
deleteAuthToken(); deleteAuthToken();
deleteSavedPassword(); deleteSavedPassword();
resolve(); resolve();
@ -122,10 +132,10 @@ function doSignOutCleanup() {
} }
function doAuthTokenRefresh() { function doAuthTokenRefresh() {
const authToken = getAuthToken(); const { auth_token: authToken } = getAuthToken();
if (authToken) { if (authToken) {
deleteAuthToken(); deleteAuthToken();
setAuthToken(authToken); setCookie(AUTH_TOKEN, authToken, 365);
} }
} }
@ -147,6 +157,7 @@ module.exports = {
deleteSavedPassword, deleteSavedPassword,
getAuthToken, getAuthToken,
setAuthToken, setAuthToken,
getTokens,
deleteAuthToken, deleteAuthToken,
doSignOutCleanup, doSignOutCleanup,
doAuthTokenRefresh, doAuthTokenRefresh,

View file

@ -1,12 +0,0 @@
const fs = require('fs');
const path = require('path');
let robots;
async function getRobots(ctx) {
if (!robots) {
robots = fs.readFileSync(path.join(__dirname, '/../dist/public/robots.txt'), 'utf8');
}
return robots;
}
module.exports = { getRobots };

View file

@ -2,6 +2,7 @@ const { getHtml } = require('./html');
const { getRss } = require('./rss'); const { getRss } = require('./rss');
const { getHomepageJSON } = require('./getHomepageJSON'); const { getHomepageJSON } = require('./getHomepageJSON');
const { generateStreamUrl } = require('../../ui/util/web'); const { generateStreamUrl } = require('../../ui/util/web');
const { createReadStream } = require('fs');
const fetch = require('node-fetch'); const fetch = require('node-fetch');
const Router = require('@koa/router'); const Router = require('@koa/router');
const { CUSTOM_HOMEPAGE } = require('../../config.js'); const { CUSTOM_HOMEPAGE } = require('../../config.js');
@ -50,6 +51,11 @@ router.get(`/$/api/content/v1/get`, async (ctx) => {
} }
}); });
router.get(`/$/sso/silent-check-sso.html`, async (ctx) => {
ctx.type = 'html';
ctx.body = createReadStream('./silent-check-sso.html');
})
router.get(`/$/download/:claimName/:claimId`, async (ctx) => { router.get(`/$/download/:claimName/:claimId`, async (ctx) => {
const streamUrl = getStreamUrl(ctx); const streamUrl = getStreamUrl(ctx);
const downloadUrl = `${streamUrl}?download=1`; const downloadUrl = `${streamUrl}?download=1`;

View file

@ -3495,9 +3495,10 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
base64-js@^1.0.2: base64-js@1.3.1, base64-js@^1.0.2:
version "1.3.1" version "1.3.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==
base@^0.11.1: base@^0.11.1:
version "0.11.2" version "0.11.2"
@ -8297,6 +8298,7 @@ hoek@4.x.x:
hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0: hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0:
version "3.3.2" version "3.3.2"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
dependencies: dependencies:
react-is "^16.7.0" react-is "^16.7.0"
@ -9822,6 +9824,11 @@ js-base64@^2.1.9:
resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.6.4.tgz#f4e686c5de1ea1f867dbcad3d46d969428df98c4" resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.6.4.tgz#f4e686c5de1ea1f867dbcad3d46d969428df98c4"
integrity sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ== integrity sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==
js-sha256@0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/js-sha256/-/js-sha256-0.9.0.tgz#0b89ac166583e91ef9123644bd3c5334ce9d0966"
integrity sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==
js-tokens@^3.0.0, js-tokens@^3.0.2: js-tokens@^3.0.0, js-tokens@^3.0.2:
version "3.0.2" version "3.0.2"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
@ -10062,6 +10069,14 @@ jszip@~2.5.0:
dependencies: dependencies:
pako "~0.2.5" pako "~0.2.5"
keycloak-js@^15.0.2:
version "15.0.2"
resolved "https://registry.yarnpkg.com/keycloak-js/-/keycloak-js-15.0.2.tgz#9d12dd8860953a267b9b18f351ad2e76b8e94a9c"
integrity sha512-dv2a4NcPSH3AzGWG3ZtB+VrHpuQLdFBYXtQBj/+oBzm6XNwnVAMdL6LIC0OzCLQpn3rKTQJtNSATAGhbKJgewQ==
dependencies:
base64-js "1.3.1"
js-sha256 "0.9.0"
keycode@^2.2.0: keycode@^2.2.0:
version "2.2.0" version "2.2.0"
resolved "https://registry.yarnpkg.com/keycode/-/keycode-2.2.0.tgz#3d0af56dc7b8b8e5cba8d0a97f107204eec22b04" resolved "https://registry.yarnpkg.com/keycode/-/keycode-2.2.0.tgz#3d0af56dc7b8b8e5cba8d0a97f107204eec22b04"
@ -10148,9 +10163,9 @@ lbry-redux@lbryio/lbry-redux#32b578707116d45f5b51b7ab523d200e75668676:
reselect "^3.0.0" reselect "^3.0.0"
uuid "^8.3.1" uuid "^8.3.1"
lbryinc@lbryio/lbryinc#0b4e41ef90d6347819dd3453f2f9398a5c1b4f36: lbryinc@lbryio/lbryinc#f133a7ed62c1f2202f98e785e923a7d841504d0d:
version "0.0.1" version "0.0.1"
resolved "https://codeload.github.com/lbryio/lbryinc/tar.gz/0b4e41ef90d6347819dd3453f2f9398a5c1b4f36" resolved "https://codeload.github.com/lbryio/lbryinc/tar.gz/f133a7ed62c1f2202f98e785e923a7d841504d0d"
dependencies: dependencies:
reselect "^3.0.0" reselect "^3.0.0"