complete authentication flow and email verification link implementation (#232)
* complete authentication flow and email verification link implementation * send appVersion and deviceId with authentication request
This commit is contained in:
parent
1e91a53a8a
commit
413ef66701
16 changed files with 490 additions and 78 deletions
17
app/package-lock.json
generated
17
app/package-lock.json
generated
|
@ -3965,6 +3965,23 @@
|
|||
"reselect": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"lbryinc": {
|
||||
"version": "github:lbryio/lbryinc#8e33473daa56ebe80b12509ac5374c5884efef90",
|
||||
"from": "github:lbryio/lbryinc#authentication-flow",
|
||||
"requires": {
|
||||
"reselect": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"lbry-redux": {
|
||||
"version": "github:lbryio/lbry-redux#467e48c77b8004cef738e950bdcc67654748ae9f",
|
||||
"from": "github:lbryio/lbry-redux#467e48c77b8004cef738e950bdcc67654748ae9f",
|
||||
"requires": {
|
||||
"proxy-polyfill": "0.1.6",
|
||||
"reselect": "^3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"lcid": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz",
|
||||
|
|
|
@ -6,7 +6,9 @@
|
|||
"start": "node node_modules/react-native/local-cli/cli.js start"
|
||||
},
|
||||
"dependencies": {
|
||||
"base-64": "^0.1.0",
|
||||
"lbry-redux": "lbryio/lbry-redux",
|
||||
"lbryinc": "lbryio/lbryinc#authentication-flow",
|
||||
"moment": "^2.22.1",
|
||||
"react": "16.2.0",
|
||||
"react-native": "0.55.3",
|
||||
|
|
|
@ -27,12 +27,22 @@ import {
|
|||
TextInput,
|
||||
ToastAndroid
|
||||
} from 'react-native';
|
||||
import { SETTINGS, doHideNotification, selectNotification } from 'lbry-redux';
|
||||
import { SETTINGS, doHideNotification, doNotify, selectNotification } from 'lbry-redux';
|
||||
import {
|
||||
doUserEmailVerify,
|
||||
doUserEmailVerifyFailure,
|
||||
selectEmailToVerify,
|
||||
selectEmailVerifyIsPending,
|
||||
selectEmailVerifyErrorMessage,
|
||||
selectUser
|
||||
} from 'lbryinc';
|
||||
import { makeSelectClientSetting } from '../redux/selectors/settings';
|
||||
import { decode as atob } from 'base-64';
|
||||
import Icon from 'react-native-vector-icons/FontAwesome5';
|
||||
import Constants from '../constants';
|
||||
import discoverStyle from '../styles/discover';
|
||||
import searchStyle from '../styles/search';
|
||||
import SearchRightHeaderIcon from "../component/searchRightHeaderIcon";
|
||||
import SearchRightHeaderIcon from '../component/searchRightHeaderIcon';
|
||||
|
||||
const discoverStack = StackNavigator({
|
||||
Discover: {
|
||||
|
@ -122,6 +132,13 @@ export const AppNavigator = new StackNavigator({
|
|||
class AppWithNavigationState extends React.Component {
|
||||
static supportedDisplayTypes = ['toast'];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
emailVerifyDone: false
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
AppState.addEventListener('change', this._handleAppStateChange);
|
||||
BackHandler.addEventListener('hardwareBackPress', function() {
|
||||
|
@ -144,7 +161,7 @@ class AppWithNavigationState extends React.Component {
|
|||
}
|
||||
}
|
||||
}
|
||||
return false;fo
|
||||
return false;
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
|
@ -160,7 +177,14 @@ class AppWithNavigationState extends React.Component {
|
|||
|
||||
componentWillUpdate(nextProps) {
|
||||
const { dispatch } = this.props;
|
||||
const { notification } = nextProps;
|
||||
const {
|
||||
notification,
|
||||
emailToVerify,
|
||||
emailVerifyPending,
|
||||
emailVerifyErrorMessage,
|
||||
user
|
||||
} = nextProps;
|
||||
|
||||
if (notification) {
|
||||
const { displayType, message } = notification;
|
||||
let currentDisplayType;
|
||||
|
@ -182,6 +206,20 @@ class AppWithNavigationState extends React.Component {
|
|||
|
||||
dispatch(doHideNotification());
|
||||
}
|
||||
|
||||
if (user &&
|
||||
!emailVerifyPending &&
|
||||
!this.state.emailVerifyDone &&
|
||||
(emailToVerify || emailVerifyErrorMessage)) {
|
||||
AsyncStorage.getItem(Constants.KEY_SHOULD_VERIFY_EMAIL).then(shouldVerify => {
|
||||
if ('true' === shouldVerify) {
|
||||
this.setState({ emailVerifyDone: true });
|
||||
const message = emailVerifyErrorMessage ?
|
||||
String(emailVerifyErrorMessage) : 'Your email address was successfully verified.';
|
||||
dispatch(doNotify({ message, displayType: ['toast'] }));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_handleAppStateChange = (nextAppState) => {
|
||||
|
@ -200,6 +238,31 @@ class AppWithNavigationState extends React.Component {
|
|||
_handleUrl = (evt) => {
|
||||
const { dispatch } = this.props;
|
||||
if (evt.url) {
|
||||
if (evt.url.startsWith('lbry://?verify=')) {
|
||||
this.setState({ emailVerifyDone: false });
|
||||
let verification = {};
|
||||
try {
|
||||
verification = JSON.parse(atob(evt.url.substring(15)));
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
if (verification.token && verification.recaptcha) {
|
||||
AsyncStorage.setItem(Constants.KEY_SHOULD_VERIFY_EMAIL, 'true');
|
||||
try {
|
||||
dispatch(doUserEmailVerify(verification.token, verification.recaptcha));
|
||||
} catch (error) {
|
||||
const message = 'Invalid Verification Token';
|
||||
dispatch(doUserEmailVerifyFailure(message));
|
||||
dispatch(doNotify({ message, displayType: ['toast'] }));
|
||||
}
|
||||
} else {
|
||||
dispatch(doNotify({
|
||||
message: 'Invalid Verification URI',
|
||||
displayType: ['toast'],
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
const navigateAction = NavigationActions.navigate({
|
||||
routeName: 'File',
|
||||
key: evt.url,
|
||||
|
@ -208,6 +271,7 @@ class AppWithNavigationState extends React.Component {
|
|||
dispatch(navigateAction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { dispatch, nav } = this.props;
|
||||
|
@ -224,10 +288,14 @@ class AppWithNavigationState extends React.Component {
|
|||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
keepDaemonRunning: makeSelectClientSetting(SETTINGS.KEEP_DAEMON_RUNNING)(state),
|
||||
nav: state.nav,
|
||||
notification: selectNotification(state),
|
||||
keepDaemonRunning: makeSelectClientSetting(SETTINGS.KEEP_DAEMON_RUNNING)(state),
|
||||
showNsfw: makeSelectClientSetting(SETTINGS.SHOW_NSFW)(state)
|
||||
emailToVerify: selectEmailToVerify(state),
|
||||
emailVerifyPending: selectEmailVerifyIsPending(state),
|
||||
emailVerifyErrorMessage: selectEmailVerifyErrorMessage(state),
|
||||
showNsfw: makeSelectClientSetting(SETTINGS.SHOW_NSFW)(state),
|
||||
user: selectUser(state),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(AppWithNavigationState);
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
const Constants = {
|
||||
SETTING_ALPHA_UNDERSTANDS_RISKS: "ALPHA_UNDERSTANDS_RISKS"
|
||||
KEY_FIRST_RUN_EMAIL: "firstRunEmail",
|
||||
KEY_SHOULD_VERIFY_EMAIL: "shouldVerifyEmail",
|
||||
|
||||
SETTING_ALPHA_UNDERSTANDS_RISKS: "alphaUnderstandRisks",
|
||||
|
||||
ACTION_FIRST_RUN_PAGE_CHANGED: "FIRST_RUN_PAGE_CHANGED",
|
||||
};
|
||||
|
||||
export default Constants;
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
searchReducer,
|
||||
walletReducer
|
||||
} from 'lbry-redux';
|
||||
import { authReducer, userReducer } from 'lbryinc';
|
||||
import { createStore, applyMiddleware, compose, combineReducers } from 'redux';
|
||||
import { createLogger } from 'redux-logger';
|
||||
import { StackNavigator, addNavigationHelpers } from 'react-navigation';
|
||||
|
@ -68,6 +69,7 @@ const navigatorReducer = (state = initialNavState, action) => {
|
|||
};
|
||||
|
||||
const reducers = combineReducers({
|
||||
auth: authReducer,
|
||||
claims: claimsReducer,
|
||||
costInfo: costInfoReducer,
|
||||
fileInfo: fileInfoReducer,
|
||||
|
@ -75,7 +77,8 @@ const reducers = combineReducers({
|
|||
search: searchReducer,
|
||||
wallet: walletReducer,
|
||||
nav: navigatorReducer,
|
||||
settings: settingsReducer
|
||||
settings: settingsReducer,
|
||||
user: userReducer
|
||||
});
|
||||
|
||||
const bulkThunk = createBulkThunkMiddleware();
|
||||
|
@ -93,18 +96,20 @@ const store = createStore(
|
|||
applyMiddleware(...middleware)
|
||||
)
|
||||
);
|
||||
window.store = store;
|
||||
|
||||
const compressor = createCompressor();
|
||||
const authFilter = createFilter('auth', ['authToken']);
|
||||
const saveClaimsFilter = createFilter('claims', ['byId', 'claimsByUri']);
|
||||
const subscriptionsFilter = createFilter('subscriptions', ['subscriptions']);
|
||||
const settingsFilter = createFilter('settings', ['clientSettings']);
|
||||
const walletFilter = createFilter('wallet', ['receiveAddress']);
|
||||
|
||||
const persistOptions = {
|
||||
whitelist: ['claims', 'subscriptions', 'settings', 'wallet'],
|
||||
whitelist: ['auth', 'claims', 'subscriptions', 'settings', 'wallet'],
|
||||
// Order is important. Needs to be compressed last or other transforms can't
|
||||
// read the data
|
||||
transforms: [saveClaimsFilter, subscriptionsFilter, settingsFilter, walletFilter, compressor],
|
||||
transforms: [authFilter, saveClaimsFilter, subscriptionsFilter, settingsFilter, walletFilter, compressor],
|
||||
debounce: 10000,
|
||||
storage: FilesystemStorage
|
||||
};
|
||||
|
|
|
@ -24,7 +24,6 @@ class AboutPage extends React.PureComponent {
|
|||
});
|
||||
});
|
||||
Lbry.status().then(info => {
|
||||
console.log(info);
|
||||
this.setState({
|
||||
lbryId: info.installation_id,
|
||||
});
|
||||
|
|
|
@ -1,6 +1,28 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { doNotify } from 'lbry-redux';
|
||||
import {
|
||||
doGenerateAuthToken,
|
||||
doUserEmailNew,
|
||||
selectAuthToken,
|
||||
selectEmailNewErrorMessage,
|
||||
selectEmailNewIsPending,
|
||||
selectEmailToVerify,
|
||||
selectIsAuthenticating
|
||||
} from 'lbryinc';
|
||||
import FirstRun from './view';
|
||||
|
||||
const perform = dispatch => ({});
|
||||
const select = (state) => ({
|
||||
authenticating: selectIsAuthenticating(state),
|
||||
authToken: selectAuthToken(state),
|
||||
emailToVerify: selectEmailToVerify(state),
|
||||
emailNewErrorMessage: selectEmailNewErrorMessage(state),
|
||||
emailNewPending: selectEmailNewIsPending(state),
|
||||
});
|
||||
|
||||
export default connect(null, perform)(FirstRun);
|
||||
const perform = dispatch => ({
|
||||
addUserEmail: email => dispatch(doUserEmailNew(email)),
|
||||
generateAuthToken: installationId => dispatch(doGenerateAuthToken(installationId)),
|
||||
notify: data => dispatch(doNotify(data))
|
||||
});
|
||||
|
||||
export default connect(select, perform)(FirstRun);
|
113
app/src/page/firstRun/internal/email-collect-page.js
Normal file
113
app/src/page/firstRun/internal/email-collect-page.js
Normal file
|
@ -0,0 +1,113 @@
|
|||
import React from 'react';
|
||||
import { Lbry } from 'lbry-redux';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
AsyncStorage,
|
||||
Linking,
|
||||
Text,
|
||||
TextInput,
|
||||
View
|
||||
} from 'react-native';
|
||||
import Colors from '../../../styles/colors';
|
||||
import Constants from '../../../constants';
|
||||
import firstRunStyle from '../../../styles/firstRun';
|
||||
|
||||
class EmailCollectPage extends React.PureComponent {
|
||||
static MAX_STATUS_TRIES = 15;
|
||||
|
||||
state = {
|
||||
email: null,
|
||||
authenticationStarted: false,
|
||||
authenticationFailed: false,
|
||||
statusTries: 0
|
||||
};
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const { authenticating, authToken } = this.props;
|
||||
|
||||
if (this.state.authenticationStarted && !authenticating && authToken === null) {
|
||||
this.setState({ authenticationFailed: true, authenticationStarted: false });
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// call user/new
|
||||
const { generateAuthToken, authenticating, authToken } = this.props;
|
||||
if (!authToken && !authenticating) {
|
||||
this.startAuthenticating();
|
||||
}
|
||||
|
||||
AsyncStorage.getItem('firstRunEmail').then(email => {
|
||||
if (email) {
|
||||
this.setState({ email });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
startAuthenticating = () => {
|
||||
const { generateAuthToken } = this.props;
|
||||
this.setState({ authenticationStarted: true, authenticationFailed: false });
|
||||
Lbry.status().then(info => {
|
||||
generateAuthToken(info.installation_id)
|
||||
}).catch(error => {
|
||||
if (this.state.statusTries >= EmailCollectPage.MAX_STATUS_TRIES) {
|
||||
this.setState({ authenticationFailed: true });
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
this.startAuthenticating();
|
||||
this.setState({ statusTries: this.state.statusTries + 1 });
|
||||
}, 1000); // Retry every second for a maximum of MAX_STATUS_TRIES tries (15 seconds)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
handleChangeText = (text) => {
|
||||
// save the value to the state email
|
||||
this.setState({ email: text });
|
||||
AsyncStorage.setItem(Constants.KEY_FIRST_RUN_EMAIL, text);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { authenticating, authToken, onEmailViewLayout, emailToVerify } = this.props;
|
||||
|
||||
let content;
|
||||
if (this.state.authenticationFailed) {
|
||||
// Ask the user to try again
|
||||
content = (
|
||||
<View>
|
||||
<Text style={firstRunStyle.paragraph}>The LBRY servers were unreachable at this time. Please check your Internet connection and then restart the app to try again.</Text>
|
||||
</View>
|
||||
);
|
||||
} else if (!authToken || authenticating) {
|
||||
content = (
|
||||
<View>
|
||||
<ActivityIndicator size="large" color={Colors.White} style={firstRunStyle.waiting} />
|
||||
<Text style={firstRunStyle.paragraph}>Please wait while we get some things ready...</Text>
|
||||
</View>
|
||||
);
|
||||
} else {
|
||||
content = (
|
||||
<View onLayout={onEmailViewLayout}>
|
||||
<Text style={firstRunStyle.title}>Rewards.</Text>
|
||||
<Text style={firstRunStyle.paragraph}>You can earn LBRY Credits (LBC) rewards by completing various tasks in the app.</Text>
|
||||
<Text style={firstRunStyle.paragraph}>Please provide a valid email address below to be able to claim your rewards.</Text>
|
||||
<TextInput style={firstRunStyle.emailInput}
|
||||
placeholder="you@example.com"
|
||||
underlineColorAndroid="transparent"
|
||||
value={this.state.email}
|
||||
onChangeText={text => this.handleChangeText(text)}
|
||||
/>
|
||||
<Text style={firstRunStyle.infoParagraph}>This information is disclosed only to LBRY, Inc. and not to the LBRY network. It is only required to earn LBRY rewards and may be used to sync usage data across devices.</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={firstRunStyle.container}>
|
||||
{content}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default EmailCollectPage;
|
|
@ -1,6 +1,8 @@
|
|||
import React from 'react';
|
||||
import { Lbry } from 'lbry-redux';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
AsyncStorage,
|
||||
Linking,
|
||||
NativeModules,
|
||||
Text,
|
||||
|
@ -9,20 +11,24 @@ import {
|
|||
} from 'react-native';
|
||||
import { NavigationActions } from 'react-navigation';
|
||||
import Colors from '../../styles/colors';
|
||||
import Constants from '../../constants';
|
||||
import WelcomePage from './internal/welcome-page';
|
||||
import EmailCollectPage from './internal/email-collect-page';
|
||||
import firstRunStyle from '../../styles/firstRun';
|
||||
|
||||
class FirstRunScreen extends React.PureComponent {
|
||||
static pages = ['welcome'];
|
||||
static pages = [
|
||||
'welcome',
|
||||
'email-collect'
|
||||
];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
state = {
|
||||
currentPage: null,
|
||||
emailSubmitted: false,
|
||||
isFirstRun: false,
|
||||
launchUrl: null,
|
||||
isFirstRun: false
|
||||
}
|
||||
}
|
||||
showBottomContainer: true
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
Linking.getInitialURL().then((url) => {
|
||||
|
@ -47,6 +53,21 @@ class FirstRunScreen extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const { emailNewErrorMessage, emailNewPending } = nextProps;
|
||||
const { notify } = this.props;
|
||||
|
||||
if (this.state.emailSubmitted && !emailNewPending) {
|
||||
this.setState({ emailSubmitted: false });
|
||||
if (emailNewErrorMessage) {
|
||||
notify ({ message: String(emailNewErrorMessage), displayType: ['toast']});
|
||||
} else {
|
||||
// Request successful. Navigate to discover.
|
||||
this.closeFinalPage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
launchSplashScreen() {
|
||||
const { navigation } = this.props;
|
||||
const resetAction = NavigationActions.reset({
|
||||
|
@ -60,7 +81,47 @@ class FirstRunScreen extends React.PureComponent {
|
|||
|
||||
handleContinuePressed = () => {
|
||||
const pageIndex = FirstRunScreen.pages.indexOf(this.state.currentPage);
|
||||
if (pageIndex === (FirstRunScreen.pages.length - 1)) {
|
||||
if (this.state.currentPage !== 'email-collect' &&
|
||||
pageIndex === (FirstRunScreen.pages.length - 1)) {
|
||||
this.closeFinalPage();
|
||||
} else {
|
||||
// TODO: Actions and page verification for specific pages
|
||||
if (this.state.currentPage === 'email-collect') {
|
||||
// handle email collect
|
||||
this.handleEmailCollectPageContinue();
|
||||
} else {
|
||||
this.showNextPage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleEmailCollectPageContinue() {
|
||||
const { notify, addUserEmail } = this.props;
|
||||
// validate the email
|
||||
AsyncStorage.getItem(Constants.KEY_FIRST_RUN_EMAIL).then(email => {
|
||||
if (!email || email.trim().length === 0 || email.indexOf('@') === -1) {
|
||||
return notify({
|
||||
message: 'Please provide a valid email address to continue.',
|
||||
displayType: ['toast'],
|
||||
});
|
||||
}
|
||||
|
||||
addUserEmail(email);
|
||||
this.setState({ emailSubmitted: true });
|
||||
});
|
||||
}
|
||||
|
||||
showNextPage() {
|
||||
const pageIndex = FirstRunScreen.pages.indexOf(this.state.currentPage);
|
||||
const nextPage = FirstRunScreen.pages[pageIndex + 1];
|
||||
this.setState({ currentPage: nextPage });
|
||||
if (nextPage === 'email-collect') {
|
||||
// do not show the buttons (because we're waiting to get things ready)
|
||||
this.setState({ showBottomContainer: false });
|
||||
}
|
||||
}
|
||||
|
||||
closeFinalPage() {
|
||||
// Final page. Let the app know that first run experience is completed.
|
||||
if (NativeModules.FirstRun) {
|
||||
NativeModules.FirstRun.firstRunCompleted();
|
||||
|
@ -68,28 +129,44 @@ class FirstRunScreen extends React.PureComponent {
|
|||
|
||||
// Navigate to the splash screen
|
||||
this.launchSplashScreen();
|
||||
} else {
|
||||
// TODO: Page transition animation?
|
||||
this.state.currentPage = FirstRunScreen.pages[pageIndex + 1];
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
authenticating,
|
||||
authToken,
|
||||
generateAuthToken,
|
||||
emailNewErrorMessage,
|
||||
emailNewPending,
|
||||
emailToVerify
|
||||
} = this.props;
|
||||
|
||||
let page = null;
|
||||
if (this.state.currentPage === 'welcome') {
|
||||
// show welcome page
|
||||
page = (<WelcomePage />);
|
||||
} else if (this.state.currentPage === 'email-collect') {
|
||||
page = (<EmailCollectPage authenticating={authenticating}
|
||||
authToken={authToken}
|
||||
generateAuthToken={generateAuthToken}
|
||||
onEmailViewLayout={() => this.setState({ showBottomContainer: true })} />);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={firstRunStyle.screenContainer}>
|
||||
{page}
|
||||
{this.state.currentPage &&
|
||||
{this.state.currentPage && this.state.showBottomContainer &&
|
||||
<View style={firstRunStyle.bottomContainer}>
|
||||
{emailNewPending &&
|
||||
<ActivityIndicator size="small" color={Colors.White} style={firstRunStyle.pageWaiting} />}
|
||||
|
||||
{!emailNewPending &&
|
||||
<TouchableOpacity style={firstRunStyle.button} onPress={this.handleContinuePressed}>
|
||||
<Text style={firstRunStyle.buttonText}>Continue</Text>
|
||||
</TouchableOpacity>}
|
||||
</View>}
|
||||
</View>
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,18 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { doBalanceSubscribe } from 'lbry-redux';
|
||||
import { doBalanceSubscribe, doNotify } from 'lbry-redux';
|
||||
import { doAuthenticate, doUserEmailVerify, doUserEmailVerifyFailure, selectUser } from 'lbryinc';
|
||||
import SplashScreen from './view';
|
||||
|
||||
const perform = dispatch => ({
|
||||
balanceSubscribe: () => dispatch(doBalanceSubscribe())
|
||||
const select = state => ({
|
||||
user: selectUser(state),
|
||||
});
|
||||
|
||||
export default connect(null, perform)(SplashScreen);
|
||||
const perform = dispatch => ({
|
||||
authenticate: (appVersion, deviceId) => dispatch(doAuthenticate(appVersion, deviceId)),
|
||||
balanceSubscribe: () => dispatch(doBalanceSubscribe()),
|
||||
notify: data => dispatch(doNotify(data)),
|
||||
verifyUserEmail: (token, recaptcha) => dispatch(doUserEmailVerify(token, recaptcha)),
|
||||
verifyUserEmailFailure: error => dispatch(doUserEmailVerifyFailure(error)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(SplashScreen);
|
||||
|
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||
import { Lbry } from 'lbry-redux';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
AsyncStorage,
|
||||
Linking,
|
||||
NativeModules,
|
||||
Platform,
|
||||
|
@ -10,8 +11,10 @@ import {
|
|||
View
|
||||
} from 'react-native';
|
||||
import { NavigationActions } from 'react-navigation';
|
||||
import { decode as atob } from 'base-64';
|
||||
import PropTypes from 'prop-types';
|
||||
import Colors from '../../styles/colors';
|
||||
import Constants from '../../constants';
|
||||
import splashStyle from '../../styles/splash';
|
||||
|
||||
class SplashScreen extends React.PureComponent {
|
||||
|
@ -21,13 +24,15 @@ class SplashScreen extends React.PureComponent {
|
|||
|
||||
componentWillMount() {
|
||||
this.setState({
|
||||
daemonReady: false,
|
||||
details: 'Starting daemon',
|
||||
message: 'Connecting',
|
||||
isRunning: false,
|
||||
isLagging: false,
|
||||
launchUrl: null,
|
||||
isDownloadingHeaders: false,
|
||||
headersDownloadProgress: 0
|
||||
headersDownloadProgress: 0,
|
||||
shouldAuthenticate: false
|
||||
});
|
||||
|
||||
if (NativeModules.DaemonServiceControl) {
|
||||
|
@ -52,27 +57,11 @@ class SplashScreen extends React.PureComponent {
|
|||
});
|
||||
}
|
||||
|
||||
_updateStatusCallback(status) {
|
||||
const startupStatus = status.startup_status;
|
||||
// At the minimum, wallet should be started and blocks_behind equal to 0 before calling resolve
|
||||
const hasStarted = startupStatus.wallet && status.wallet.blocks_behind <= 0;
|
||||
if (hasStarted) {
|
||||
// Wait until we are able to resolve a name before declaring
|
||||
// that we are done.
|
||||
// TODO: This is a hack, and the logic should live in the daemon
|
||||
// to give us a better sense of when we are actually started
|
||||
this.setState({
|
||||
message: 'Testing Network',
|
||||
details: 'Waiting for name resolution',
|
||||
isLagging: false,
|
||||
isRunning: true,
|
||||
});
|
||||
|
||||
Lbry.resolve({ uri: 'lbry://one' }).then(() => {
|
||||
// Leave the splash screen
|
||||
const { balanceSubscribe, navigation } = this.props;
|
||||
balanceSubscribe();
|
||||
|
||||
componentWillUpdate(nextProps) {
|
||||
const { navigation, verifyUserEmail, verifyUserEmailFailure } = this.props;
|
||||
const { user } = nextProps;
|
||||
if (this.state.daemonReady && this.state.shouldAuthenticate && user && user.id) {
|
||||
// user is authenticated, navigate to the main view
|
||||
const resetAction = NavigationActions.reset({
|
||||
index: 0,
|
||||
actions: [
|
||||
|
@ -83,8 +72,73 @@ class SplashScreen extends React.PureComponent {
|
|||
|
||||
const launchUrl = navigation.state.params.launchUrl || this.state.launchUrl;
|
||||
if (launchUrl) {
|
||||
if (launchUrl.startsWith('lbry://?verify=')) {
|
||||
let verification = {};
|
||||
try {
|
||||
verification = JSON.parse(atob(launchUrl.substring(15)));
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
if (verification.token && verification.recaptcha) {
|
||||
AsyncStorage.setItem(Constants.KEY_SHOULD_VERIFY_EMAIL, 'true');
|
||||
try {
|
||||
verifyUserEmail(verification.token, verification.recaptcha);
|
||||
} catch (error) {
|
||||
const message = 'Invalid Verification Token';
|
||||
verifyUserEmailFailure(message);
|
||||
notify({ message, displayType: ['toast'] });
|
||||
}
|
||||
} else {
|
||||
notify({
|
||||
message: 'Invalid Verification URI',
|
||||
displayType: ['toast'],
|
||||
});
|
||||
}
|
||||
} else {
|
||||
navigation.navigate({ routeName: 'File', key: launchUrl, params: { uri: launchUrl } });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_updateStatusCallback(status) {
|
||||
const startupStatus = status.startup_status;
|
||||
// At the minimum, wallet should be started and blocks_behind equal to 0 before calling resolve
|
||||
const hasStarted = startupStatus.wallet && status.wallet.blocks_behind <= 0;
|
||||
if (hasStarted) {
|
||||
// Wait until we are able to resolve a name before declaring
|
||||
// that we are done.
|
||||
// TODO: This is a hack, and the logic should live in the daemon
|
||||
// to give us a better sense of when we are actually started
|
||||
this.setState({
|
||||
daemonReady: true,
|
||||
message: 'Testing Network',
|
||||
details: 'Waiting for name resolution',
|
||||
isLagging: false,
|
||||
isRunning: true,
|
||||
});
|
||||
|
||||
Lbry.resolve({ uri: 'lbry://one' }).then(() => {
|
||||
// Leave the splash screen
|
||||
const {
|
||||
authenticate,
|
||||
balanceSubscribe,
|
||||
navigation,
|
||||
notify
|
||||
} = this.props;
|
||||
|
||||
balanceSubscribe();
|
||||
NativeModules.VersionInfo.getAppVersion().then(appVersion => {
|
||||
if (NativeModules.UtilityModule) {
|
||||
// authenticate with the device ID if the method is available
|
||||
NativeModules.UtilityModule.getDeviceId().then(deviceId => {
|
||||
authenticate(`android-${appVersion}`, deviceId);
|
||||
});
|
||||
} else {
|
||||
authenticate(appVersion);
|
||||
}
|
||||
this.setState({ shouldAuthenticate: true });
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -9,3 +9,4 @@ export function doSetClientSetting(key, value) {
|
|||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import { AsyncStorage } from 'react-native';
|
||||
import { ACTIONS } from 'lbry-redux';
|
||||
|
||||
|
||||
const reducers = {};
|
||||
const defaultState = {
|
||||
clientSettings: {}
|
||||
|
|
|
@ -28,16 +28,56 @@ const firstRunStyle = StyleSheet.create({
|
|||
marginBottom: 20,
|
||||
color: Colors.White
|
||||
},
|
||||
button: {
|
||||
infoParagraph: {
|
||||
fontFamily: 'Metropolis-Regular',
|
||||
fontSize: 14,
|
||||
lineHeight: 20,
|
||||
marginLeft: 32,
|
||||
marginRight: 32,
|
||||
marginBottom: 20,
|
||||
color: Colors.White
|
||||
},
|
||||
emailInput: {
|
||||
fontFamily: 'Metropolis-Regular',
|
||||
fontSize: 24,
|
||||
lineHeight: 24,
|
||||
marginLeft: 32,
|
||||
marginRight: 32,
|
||||
marginBottom: 20,
|
||||
textAlign: 'center'
|
||||
},
|
||||
leftButton: {
|
||||
flex: 1,
|
||||
alignSelf: 'flex-start',
|
||||
marginLeft: 32,
|
||||
marginRight: 32
|
||||
},
|
||||
bottomContainer: {
|
||||
flex: 1
|
||||
},
|
||||
actionButton: {
|
||||
backgroundColor: Colors.White,
|
||||
alignSelf: 'center',
|
||||
fontFamily: 'Metropolis-Regular',
|
||||
fontSize: 12,
|
||||
paddingLeft: 16,
|
||||
paddingRight: 16
|
||||
},
|
||||
button: {
|
||||
alignSelf: 'flex-end',
|
||||
marginLeft: 32,
|
||||
marginRight: 32
|
||||
},
|
||||
buttonText: {
|
||||
fontFamily: 'Metropolis-Regular',
|
||||
fontSize: 28,
|
||||
fontSize: 24,
|
||||
color: Colors.White
|
||||
},
|
||||
waiting: {
|
||||
marginBottom: 24
|
||||
},
|
||||
pageWaiting: {
|
||||
alignSelf: 'center'
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -172,19 +172,10 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand
|
|||
Toast.makeText(context, "Rewards cannot be claimed because we could not identify your device.", Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-384");
|
||||
md.update(id.getBytes("UTF-8"));
|
||||
String hash = new BigInteger(1, md.digest()).toString(16);
|
||||
|
||||
SharedPreferences sp = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = sp.edit();
|
||||
editor.putString(DEVICE_ID_KEY, hash);
|
||||
editor.putString(DEVICE_ID_KEY, id);
|
||||
editor.commit();
|
||||
} catch (NoSuchAlgorithmException | UnsupportedEncodingException ex) {
|
||||
// SHA-384 not found, UTF-8 encoding not supported
|
||||
Toast.makeText(context, "Rewards cannot be claimed because we could not identify your device.", Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
|
|
@ -2,13 +2,17 @@ package io.lbry.browser.reactmodules;
|
|||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
|
||||
import io.lbry.browser.MainActivity;
|
||||
|
||||
public class UtilityModule extends ReactContextBaseJavaModule {
|
||||
private Context context;
|
||||
|
||||
|
@ -71,4 +75,11 @@ public class UtilityModule extends ReactContextBaseJavaModule {
|
|||
View.SYSTEM_UI_FLAG_VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void getDeviceId(final Promise promise) {
|
||||
SharedPreferences sp = context.getSharedPreferences(MainActivity.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
|
||||
String deviceId = sp.getString(MainActivity.DEVICE_ID_KEY, null);
|
||||
promise.resolve(deviceId);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue