perform user authentication on first page of first run (#572)
* perform user authentication on first page of first run * additional firebase events for first run
This commit is contained in:
parent
309b1e70e6
commit
7e136faea5
7 changed files with 145 additions and 92 deletions
|
@ -16,6 +16,7 @@ const Constants = {
|
||||||
|
|
||||||
KEY_FIRST_RUN_EMAIL: "firstRunEmail",
|
KEY_FIRST_RUN_EMAIL: "firstRunEmail",
|
||||||
KEY_FIRST_RUN_PASSWORD: "firstRunPassword",
|
KEY_FIRST_RUN_PASSWORD: "firstRunPassword",
|
||||||
|
KEY_FIRST_USER_AUTH: "firstUserAuth",
|
||||||
KEY_SHOULD_VERIFY_EMAIL: "shouldVerifyEmail",
|
KEY_SHOULD_VERIFY_EMAIL: "shouldVerifyEmail",
|
||||||
KEY_EMAIL_VERIFY_PENDING: "emailVerifyPending",
|
KEY_EMAIL_VERIFY_PENDING: "emailVerifyPending",
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Lbry } from 'lbry-redux';
|
import { Lbry } from 'lbry-redux';
|
||||||
import {
|
import {
|
||||||
ActivityIndicator,
|
|
||||||
Linking,
|
|
||||||
NativeModules,
|
NativeModules,
|
||||||
Platform,
|
Platform,
|
||||||
Text,
|
Text,
|
||||||
|
@ -15,25 +13,16 @@ import Constants from 'constants';
|
||||||
import firstRunStyle from 'styles/firstRun';
|
import firstRunStyle from 'styles/firstRun';
|
||||||
|
|
||||||
class EmailCollectPage extends React.PureComponent {
|
class EmailCollectPage extends React.PureComponent {
|
||||||
static MAX_STATUS_TRIES = 30;
|
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
email: null,
|
email: null,
|
||||||
authenticationStarted: false,
|
|
||||||
authenticationFailed: false,
|
|
||||||
placeholder: 'you@example.com',
|
placeholder: 'you@example.com',
|
||||||
statusTries: 0,
|
|
||||||
verifying: true
|
verifying: true
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
const { authenticating, authToken, showNextPage } = this.props;
|
const { showNextPage } = this.props;
|
||||||
const { user } = nextProps;
|
const { user } = nextProps;
|
||||||
|
|
||||||
if (this.state.authenticationStarted && !authenticating && authToken === null) {
|
|
||||||
this.setState({ authenticationFailed: true, authenticationStarted: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.state.verifying) {
|
if (this.state.verifying) {
|
||||||
if (user && user.primary_email && user.has_verified_email) {
|
if (user && user.primary_email && user.has_verified_email) {
|
||||||
if (showNextPage) {
|
if (showNextPage) {
|
||||||
|
@ -45,33 +34,6 @@ class EmailCollectPage extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
// call user/new
|
|
||||||
const { generateAuthToken, authenticating, authToken } = this.props;
|
|
||||||
if (!authenticating) {
|
|
||||||
this.startAuthenticating();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
startAuthenticating = () => {
|
|
||||||
const { authenticate } = this.props;
|
|
||||||
this.setState({ authenticationStarted: true, authenticationFailed: false });
|
|
||||||
NativeModules.VersionInfo.getAppVersion().then(appVersion => {
|
|
||||||
Lbry.status().then(info => {
|
|
||||||
authenticate(appVersion, Platform.OS);
|
|
||||||
}).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 (30 seconds)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
handleChangeText = (text) => {
|
handleChangeText = (text) => {
|
||||||
// save the value to the state email
|
// save the value to the state email
|
||||||
const { onEmailChanged } = this.props;
|
const { onEmailChanged } = this.props;
|
||||||
|
@ -84,49 +46,32 @@ class EmailCollectPage extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { authenticating, authToken, onEmailChanged, onEmailViewLayout, emailToVerify } = this.props;
|
const { onEmailViewLayout } = this.props;
|
||||||
|
|
||||||
let content;
|
const content = (
|
||||||
if (this.state.authenticationFailed) {
|
<View onLayout={onEmailViewLayout}>
|
||||||
// Ask the user to try again
|
<Text style={firstRunStyle.title}>Setup account</Text>
|
||||||
content = (
|
<TextInput style={firstRunStyle.emailInput}
|
||||||
<View>
|
placeholder={this.state.placeholder}
|
||||||
<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>
|
underlineColorAndroid="transparent"
|
||||||
</View>
|
selectionColor={Colors.NextLbryGreen}
|
||||||
);
|
value={this.state.email}
|
||||||
} else if (!authToken || authenticating || this.state.verifying) {
|
onChangeText={text => this.handleChangeText(text)}
|
||||||
content = (
|
onFocus={() => {
|
||||||
<View style={firstRunStyle.centered}>
|
if (!this.state.email || this.state.email.length === 0) {
|
||||||
<ActivityIndicator size="large" color={Colors.White} style={firstRunStyle.waiting} />
|
this.setState({ placeholder: '' });
|
||||||
<Text style={firstRunStyle.paragraph}>Please wait while we get some things ready...</Text>
|
}
|
||||||
</View>
|
}}
|
||||||
);
|
onBlur={() => {
|
||||||
} else {
|
if (!this.state.email || this.state.email.length === 0) {
|
||||||
content = (
|
this.setState({ placeholder: 'you@example.com' });
|
||||||
<View onLayout={onEmailViewLayout}>
|
}
|
||||||
<Text style={firstRunStyle.title}>Setup account</Text>
|
}}
|
||||||
<TextInput style={firstRunStyle.emailInput}
|
/>
|
||||||
placeholder={this.state.placeholder}
|
<Text style={firstRunStyle.paragraph}>An account will allow you to earn rewards and keep your account and settings synced.</Text>
|
||||||
underlineColorAndroid="transparent"
|
<Text style={firstRunStyle.infoParagraph}>This information is disclosed only to LBRY, Inc. and not to the LBRY network.</Text>
|
||||||
selectionColor={Colors.NextLbryGreen}
|
</View>
|
||||||
value={this.state.email}
|
);
|
||||||
onChangeText={text => this.handleChangeText(text)}
|
|
||||||
onFocus={() => {
|
|
||||||
if (!this.state.email || this.state.email.length === 0) {
|
|
||||||
this.setState({ placeholder: '' });
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onBlur={() => {
|
|
||||||
if (!this.state.email || this.state.email.length === 0) {
|
|
||||||
this.setState({ placeholder: 'you@example.com' });
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Text style={firstRunStyle.paragraph}>An account will allow you to earn rewards and keep your account and settings synced.</Text>
|
|
||||||
<Text style={firstRunStyle.infoParagraph}>This information is disclosed only to LBRY, Inc. and not to the LBRY network.</Text>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={firstRunStyle.container}>
|
<View style={firstRunStyle.container}>
|
||||||
|
|
|
@ -1,15 +1,108 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Lbry } from 'lbry-redux';
|
import { Lbry } from 'lbry-redux';
|
||||||
import { View, Text, Linking } from 'react-native';
|
import {
|
||||||
|
ActivityIndicator,
|
||||||
|
NativeModules,
|
||||||
|
Platform,
|
||||||
|
Text,
|
||||||
|
View
|
||||||
|
} from 'react-native';
|
||||||
|
import AsyncStorage from '@react-native-community/async-storage';
|
||||||
import Colors from 'styles/colors';
|
import Colors from 'styles/colors';
|
||||||
|
import Constants from 'constants';
|
||||||
import firstRunStyle from 'styles/firstRun';
|
import firstRunStyle from 'styles/firstRun';
|
||||||
|
|
||||||
class WelcomePage extends React.PureComponent {
|
class WelcomePage extends React.PureComponent {
|
||||||
|
static MAX_STATUS_TRIES = 60;
|
||||||
|
|
||||||
|
state = {
|
||||||
|
authenticationStarted: false,
|
||||||
|
authenticationFailed: false,
|
||||||
|
sdkStarted: false,
|
||||||
|
statusTries: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
const { authenticating, authToken } = this.props;
|
||||||
|
|
||||||
|
if (this.state.authenticationStarted && !authenticating) {
|
||||||
|
if (authToken === null) {
|
||||||
|
this.setState({ authenticationFailed: true, authenticationStarted: false });
|
||||||
|
} else {
|
||||||
|
// first_user_auth because it's the first time
|
||||||
|
AsyncStorage.getItem(Constants.KEY_FIRST_USER_AUTH).then(firstUserAuth => {
|
||||||
|
if ('true' !== firstUserAuth) {
|
||||||
|
// first_user_auth
|
||||||
|
NativeModules.Firebase.track('first_user_auth', null);
|
||||||
|
AsyncStorage.setItem(Constants.KEY_FIRST_USER_AUTH, 'true');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
// call user/new
|
||||||
|
const { generateAuthToken, authenticating, authToken } = this.props;
|
||||||
|
if (!authenticating) {
|
||||||
|
this.startAuthenticating();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
startAuthenticating = () => {
|
||||||
|
const { authenticate } = this.props;
|
||||||
|
this.setState({ authenticationStarted: true, authenticationFailed: false });
|
||||||
|
NativeModules.VersionInfo.getAppVersion().then(appVersion => {
|
||||||
|
Lbry.status().then(info => {
|
||||||
|
this.setState({ sdkStarted: true });
|
||||||
|
|
||||||
|
authenticate(appVersion, Platform.OS);
|
||||||
|
}).catch(error => {
|
||||||
|
if (this.state.statusTries >= WelcomePage.MAX_STATUS_TRIES) {
|
||||||
|
this.setState({ authenticationFailed: true });
|
||||||
|
|
||||||
|
// sdk_start_failed
|
||||||
|
NativeModules.Firebase.track('sdk_start_failed', null);
|
||||||
|
} else {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.startAuthenticating();
|
||||||
|
this.setState({ statusTries: this.state.statusTries + 1 });
|
||||||
|
}, 1000); // Retry every second for a maximum of MAX_STATUS_TRIES tries (60 seconds)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { authenticating, authToken, onWelcomePageLayout } = 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 style={firstRunStyle.centered}>
|
||||||
|
<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={onWelcomePageLayout}>
|
||||||
|
<Text style={firstRunStyle.title}>Welcome to LBRY.</Text>
|
||||||
|
<Text style={firstRunStyle.paragraph}>LBRY is a community-controlled content platform where you can find and publish videos, music, books, and more.</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={firstRunStyle.container}>
|
<View style={firstRunStyle.container}>
|
||||||
<Text style={firstRunStyle.title}>Welcome to LBRY.</Text>
|
{content}
|
||||||
<Text style={firstRunStyle.paragraph}>LBRY is a community-controlled content platform where you can find and publish videos, music, books, and more.</Text>
|
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ class FirstRunScreen extends React.PureComponent {
|
||||||
showSkip: false,
|
showSkip: false,
|
||||||
isEmailVerified: false,
|
isEmailVerified: false,
|
||||||
skipAccountConfirmed: false,
|
skipAccountConfirmed: false,
|
||||||
showBottomContainer: true,
|
showBottomContainer: false,
|
||||||
walletPassword: null,
|
walletPassword: null,
|
||||||
syncApplyStarted: false
|
syncApplyStarted: false
|
||||||
};
|
};
|
||||||
|
@ -252,6 +252,10 @@ class FirstRunScreen extends React.PureComponent {
|
||||||
this.setState({ showBottomContainer: true });
|
this.setState({ showBottomContainer: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onWelcomePageLayout = () => {
|
||||||
|
this.setState({ showBottomContainer: true });
|
||||||
|
}
|
||||||
|
|
||||||
onSkipSwitchChanged = (checked) => {
|
onSkipSwitchChanged = (checked) => {
|
||||||
this.setState({ skipAccountConfirmed: checked });
|
this.setState({ skipAccountConfirmed: checked });
|
||||||
}
|
}
|
||||||
|
@ -291,16 +295,17 @@ class FirstRunScreen extends React.PureComponent {
|
||||||
let page = null;
|
let page = null;
|
||||||
switch (this.state.currentPage) {
|
switch (this.state.currentPage) {
|
||||||
case Constants.FIRST_RUN_PAGE_WELCOME:
|
case Constants.FIRST_RUN_PAGE_WELCOME:
|
||||||
page = (<WelcomePage />);
|
page = (<WelcomePage
|
||||||
|
authenticating={authenticating}
|
||||||
|
authToken={authToken}
|
||||||
|
authenticate={authenticate}
|
||||||
|
onWelcomePageLayout={this.onWelcomePageLayout} />);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Constants.FIRST_RUN_PAGE_EMAIL_COLLECT:
|
case Constants.FIRST_RUN_PAGE_EMAIL_COLLECT:
|
||||||
page = (<EmailCollectPage
|
page = (<EmailCollectPage
|
||||||
user={user}
|
user={user}
|
||||||
showNextPage={this.showNextPage}
|
showNextPage={this.showNextPage}
|
||||||
authenticating={authenticating}
|
|
||||||
authToken={authToken}
|
|
||||||
authenticate={authenticate}
|
|
||||||
onEmailChanged={this.onEmailChanged}
|
onEmailChanged={this.onEmailChanged}
|
||||||
onEmailViewLayout={this.onEmailViewLayout} />);
|
onEmailViewLayout={this.onEmailViewLayout} />);
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -241,9 +241,10 @@ class SplashScreen extends React.PureComponent {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Start measuring the first launch time from the splash screen (time from daemon start to user interaction)
|
// Start measuring the first launch time from the splash screen
|
||||||
|
// (time to first user interaction - after first run completed)
|
||||||
AsyncStorage.getItem('hasLaunched').then(value => {
|
AsyncStorage.getItem('hasLaunched').then(value => {
|
||||||
if (value == null || value !== 'true') {
|
if ('true' !== value) {
|
||||||
AsyncStorage.setItem('hasLaunched', 'true');
|
AsyncStorage.setItem('hasLaunched', 'true');
|
||||||
// only set firstLaunchTime since we've determined that this is the first app launch ever
|
// only set firstLaunchTime since we've determined that this is the first app launch ever
|
||||||
AsyncStorage.setItem('firstLaunchTime', String(moment().unix()));
|
AsyncStorage.setItem('firstLaunchTime', String(moment().unix()));
|
||||||
|
|
|
@ -22,7 +22,6 @@ class VerificationScreen extends React.PureComponent {
|
||||||
state = {
|
state = {
|
||||||
currentPage: null,
|
currentPage: null,
|
||||||
emailSubmitted: false,
|
emailSubmitted: false,
|
||||||
isFirstRun: false,
|
|
||||||
launchUrl: null,
|
launchUrl: null,
|
||||||
showSkip: false,
|
showSkip: false,
|
||||||
skipAccountConfirmed: false,
|
skipAccountConfirmed: false,
|
||||||
|
|
|
@ -4,12 +4,15 @@ import android.content.Context;
|
||||||
import android.content.pm.PackageInfo;
|
import android.content.pm.PackageInfo;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
import com.facebook.react.bridge.Promise;
|
import com.facebook.react.bridge.Promise;
|
||||||
import com.facebook.react.bridge.ReactApplicationContext;
|
import com.facebook.react.bridge.ReactApplicationContext;
|
||||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||||
import com.facebook.react.bridge.ReactMethod;
|
import com.facebook.react.bridge.ReactMethod;
|
||||||
|
|
||||||
|
import com.google.firebase.analytics.FirebaseAnalytics;
|
||||||
|
|
||||||
import io.lbry.browser.MainActivity;
|
import io.lbry.browser.MainActivity;
|
||||||
|
|
||||||
public class FirstRunModule extends ReactContextBaseJavaModule {
|
public class FirstRunModule extends ReactContextBaseJavaModule {
|
||||||
|
@ -40,5 +43,11 @@ public class FirstRunModule extends ReactContextBaseJavaModule {
|
||||||
SharedPreferences.Editor editor = sp.edit();
|
SharedPreferences.Editor editor = sp.edit();
|
||||||
editor.putBoolean("firstRun", false);
|
editor.putBoolean("firstRun", false);
|
||||||
editor.commit();
|
editor.commit();
|
||||||
|
|
||||||
|
FirebaseAnalytics firebase = FirebaseAnalytics.getInstance(context);
|
||||||
|
if (firebase != null) {
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
firebase.logEvent("first_run_completed", bundle);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue