Short introduction:
Facebook signing in is failing after clicking “login” on facebook modal. The problem is everything works fine:
- in Expo client Android/iOS
- on iOS (production build from AppStore)
- on Android emulator (signed apk)
Does not work:
- on physical Android device, apk downloaded from Google Play
I don’t know how to solve this if I don’t have any stacktrace. I added error tracking (Sentry.io), but I don’t get any data when the login process is failing, but tracking works fine.
Environment
[18:08:47] Generating diagnostics report...
[18:08:47] You can join our slack here: https://slack.expo.io/.
Expo CLI 2.2.4 environment info:
System:
OS: macOS 10.14.3
Shell: 5.3 - /bin/zsh
Binaries:
Node: 11.3.0 - /usr/local/bin/node
Yarn: 1.12.3 - /usr/local/bin/yarn
npm: 6.4.1 - /usr/local/bin/npm
Watchman: 4.9.0 - /usr/local/bin/watchman
IDEs:
Xcode: 10.1/10B61 - /usr/bin/xcodebuild
npmPackages:
expo: ^31.0.4 => 31.0.6
react: 16.5.0 => 16.5.0
react-native: https://github.com/expo/react-native/archive/sdk-31.0.1.tar.gz => 0.57.1
react-navigation: ^2.18.2 => 2.18.3
npmGlobalPackages:
expo-cli: 2.2.4
Diagnostics report:
https://exp-xde-diagnostics.s3.amazonaws.com/revcode.it-e04eefd8-88a5-4b5d-8ad2-5b22bc6899cb.tar.gz
App.json
{
"expo": {
"name": "<app_display_name>",
"description": "Production",
"slug": "<app_name>",
"privacy": "unlisted",
"sdkVersion": "31.0.0",
"platforms": [
"ios",
"android"
],
"version": "1.0.13",
"orientation": "portrait",
"facebookScheme": "<fb_scheme>",
"facebookAppId": "<app_id>",
"facebookDisplayName": "<app_display_name>",
"scheme": "<app_name>",
"icon": "./assets/images/icon.png",
"splash": {
"image": "./assets/images/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"updates": {
"fallbackToCacheTimeout": 0
},
"assetBundlePatterns": [
"**/*"
],
"ios": {
"supportsTablet": false,
"bundleIdentifier": "<bundle>",
"infoPlist": {
"LSApplicationQueriesSchemes": [
"<app_name>"
],
"NSPhotoLibraryUsageDescription": "Upload photo for services to attract potential attendees"
}
},
"android": {
"permissions": [], << Needed to publish on Google Play without warning about SMS warnings
"versionCode": 11,
"package": "<package>",
"config": {
"googleMaps": {
"apiKey": "<key>"
}
}
},
"hooks": {
...
}
}
Code:
// @flow
import React from 'react';
import {Alert, AsyncStorage, Image, Linking, ScrollView, StyleSheet, Switch, Text, View} from "react-native";
import Button from "../components/ui/Button";
import ButtonStyles from "../constants/ButtonStyles";
import {backgroundColor, brandSecondary, linkColor, text300, text500Default, themePrimary} from "../constants/Colors";
import {Font} from "../constants/TextStyles";
import {storageTokenKey} from "../constants/Common";
import {MainRoute} from "../navigation/AppNavigator";
import type {User} from "../api/authApi";
import {authenticate, fetchMe} from "../api/authApi";
import {connect} from "react-redux";
import {bindActionCreators} from "redux";
import {loginSuccess} from "../redux/action/accountActions";
import {fetchSystemStatus, SystemHealth} from "../api/utilApi";
import _ from "lodash";
import {PRIVACY_POLICY_URL, TOS_URL} from "../constants/GDPRUrls";
type Props = {
loginSuccess: (user: User) => void,
navigation: any
}
type State = {
isLoading: boolean,
isGDPRChecked: boolean,
systemStatus: string
}
type FacebookProfile = {
id: number,
email: string,
name: string
}
const APP_ID = '<my_facebook_id>';
const FACEBOOK_OPTIONS = {
permissions: ['public_profile', 'email'],
};
class LoginScreen extends React.Component<Props, State> {
static navigationOptions = {
header: null,
};
constructor(props) {
super(props);
this.state = {
isLoading: false,
isGDPRChecked: false,
systemStatus: SystemHealth.UNKNOWN
}
}
openApplication = (): void => this.props.navigation.navigate(MainRoute.APPLICATION);
loginUser = async (): Promise<void> => {
return fetchMe()
.then(response => response.data)
.then(this.props.loginSuccess);
};
fetchFacebookProfile = async (accessToken: string): Promise<FacebookProfile> => {
return fetch(`https://graph.facebook.com/me?access_token=${accessToken}&fields=id,name,email`)
.then(response => response.json());
};
openFacebookLogin = async (): Promise<string> => {
const {type, token} = await Expo.Facebook.logInWithReadPermissionsAsync(APP_ID, FACEBOOK_OPTIONS);
if (type === 'success') {
return token;
}
if (type === 'cancel') {
throw Error("Login canceled");
}
throw Error("Unknown error");
};
async componentDidMount(): Promise<void> {
this.setState({isLoading: false});
const systemStatus = await fetchSystemStatus();
console.log("System:", systemStatus);
this.setState({systemStatus});
if (systemStatus !== SystemHealth.UP) {
this.showMessageAboutSystemHealth()
}
}
tryAuthenticate = async () => {
let email: string = "";
let facebookAccessToken: string = "";
try {
facebookAccessToken = await this.openFacebookLogin();
this.setState({isLoading: true});
const facebookProfile = await this.fetchFacebookProfile(facebookAccessToken);
email = facebookProfile.email;
console.log("Facebook email:", email);
} catch (error) {
console.error(error);
const message = _.get(error, 'message', 'No error message.');
throw Error(message + " Please try again later. Code: 102");
}
try {
const apiToken = await authenticate(email, facebookAccessToken);
await AsyncStorage.setItem(storageTokenKey, apiToken);
console.log("API token:", apiToken);
} catch (error) {
console.error(error);
const message = _.get(error, 'response', 'No error message.');
throw Error(message + " Please try again later. Code: 101");
}
try {
await this.loginUser();
console.log("Login success");
} catch (error) {
console.error(error);
const message = _.get(error, 'response.msg', 'No error message.');
throw Error(message + " Please try again later. Code: 100");
}
};
onFacebookLoginPress = async (): Promise<void> => {
try {
await this.tryAuthenticate();
this.openApplication();
} catch (error) {
this.setState({isLoading: false});
Alert.alert("Login error", error.message);
}
};
openLink = (url) => {
Linking.canOpenURL(url)
.then(supported => {
if (supported) {
Linking.openURL(url);
}
});
};
onTermsOfServicesPress = () => this.openLink(TOS_URL);
onPrivacyPolicyPress = () => this.openLink(PRIVACY_POLICY_URL);
showMessageAboutSystemHealth = (): void => Alert.alert("System unavailable", "System right now is unavailable. Please try again in few minutes.");
onDisabledButtonPress = (): void => this.showMessageAboutSystemHealth();
render() {
const {isLoading, systemStatus, isGDPRChecked} = this.state;
const isSystemUp = !(systemStatus !== SystemHealth.UP);
return (
<ScrollView>
<View style={styles.container}>
<View
style={styles.headerContainer}
>
<Text style={[styles.title, {
color: text500Default
}
]}>
Some
</Text>
<Text style={[styles.title, {
color: brandSecondary
}
]}>
App
</Text>
</View>
<Image
source={require('../assets/images/login-image.jpeg')}
style={styles.image}
/>
<Text style={styles.text}>some text
</Text>
<Button
onPress={this.onFacebookLoginPress}
style={ButtonStyles.primaryRound}
onPressDisabled={this.onDisabledButtonPress}
disabled={!isGDPRChecked}
loading={isLoading}
>
Sign in with Facebook
</Button>
<View
style={{
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: 10
}}
>
<Button
style={[ButtonStyles.linkSmall]}
onPress={this.onTermsOfServicesPress}
textStyle={{
color: linkColor,
fontSize: 14
}}>
Terms of services
</Button>
<Button
style={[ButtonStyles.linkSmall]}
onPress={this.onPrivacyPolicyPress}
textStyle={{
color: linkColor,
fontSize: 14
}}>
Privacy policy
</Button>
</View>
<View
style={{
justifyContent: 'center',
alignItems: 'center',
paddingHorizontal: 25,
}}
>
<View
style={{
flexDirection: 'row',
alignItems: 'center',
}}>
<Switch
value={isGDPRChecked && isSystemUp}
onValueChange={(isGDPRChecked) => this.setState({isGDPRChecked})}
>
</Switch>
<Text
style={styles.agreementText}
>
I hereby agree to have my personal data processed for the purposes of realization of the portal’s
services.
</Text>
</View>
</View>
</View></ScrollView>
);
}
}
const mapStateToProps = state => ({
account: state.account,
});
const mapDispatchToProps = dispatch => (bindActionCreators(
{loginSuccess},
dispatch,
));
export default connect(mapStateToProps, mapDispatchToProps)(LoginScreen);
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 40,
justifyContent: 'space-around',
backgroundColor: backgroundColor,
alignItems: 'center'
},
agreementText: {
fontSize: 12,
color: text300,
marginLeft: 10
},
agreementLink: {
fontSize: 12,
color: linkColor,
},
title: {
fontSize: 40,
textAlign: 'center',
fontFamily: Font.avenirNextDemiBold
},
text: {
color: themePrimary,
fontSize: 16,
fontFamily: Font.avenirNextMedium,
textAlign: 'justify',
marginBottom: 40,
},
image: {
width: 180,
height: 180,
borderRadius: 90,
},
headerContainer: {
flexDirection: 'row'
}
});