I have a simple react-native client for a website, in login page it provides two option, enter login code manually or scan it using barcode scanner. i've tested the app in real device and emulator lot's of times it's working fine. Actually i test only over ipv4, and for login im using fetch, which i think is supporting ipv6 by default.
They say over ipv6 network when app was offline, i cannot understand what does it mean to be OFFLINE and be on IPV6 network?
When app is offline, i'm showing error to user that there is no connectivity. so i don't know how it can crash.
should adding a timeout to fetch request fix the issue?
But the app being rejected 3 times due to same error :
Performance - 2.1
Thank you for your resubmission.
Your app crashes on iPhone running iOS 9.3.3 connected to an IPv6 network when we:
Specifically, tapping the login still leads the app to crash.
This occurred when your app was used:
- Offline
- On Wi-Fi
We have attached detailed crash logs to help troubleshoot this issue.
Here is the login.js :
'use strict';
import React, { Component } from 'react';
import {
Text,
View,
Image,
TextInput,
TouchableOpacity,
ActivityIndicatorIOS,
StyleSheet,
Dimensions,
AlertIOS,
NetInfo,
} from 'react-native';
import Camera from 'react-native-camera';
var { width, height } = Dimensions.get('window');
class Login extends Component {
constructor(props){
super(props);
this.state = {
showProgress: false,
showCamera: false,
cameraType: Camera.constants.Type.back,
barcode: true,
isConnected: false,
}
}
componentWillMount(){
NetInfo.isConnected.fetch().done((data) => {
this.setState({
isConnected: data
})
});
}
_onBarCodeRead(e) {
this.setState({
showCamera: false,
barcodeData: e.data,
logincode: e.data,
success: true,
});
this.onLoginPressed();
}
render(){
if(this.state.showCamera) {
return (
<Camera
ref="cam"
style={styles.container}
onBarCodeRead={this._onBarCodeRead.bind(this)}
type={this.state.cameraType}>
</Camera>
);
} else {
var errorCtrl = <View />;
if(!this.state.success){
errorCtrl = <Text style={styles.error}>
{this.state.message}
</Text>;
}
///// Check login type
if(this.state.barcode){
return(
<View style={styles.container}>
<Image style={styles.logo} source={require('image!logo')} />
<Text style={styles.heading}>
Please use QR-Scanner to login,{'\n'}
or enter the Login code manually.
</Text>
<TouchableOpacity
onPress={this.onQrPressed.bind(this)}
style={styles.button}>
<Text style={styles.buttonText}>Use QR-Scanner</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={this.toManuall.bind(this)}
>
<Text style={styles.change}>
Want to enter code manually?
</Text>
</TouchableOpacity>
{errorCtrl}
<ActivityIndicatorIOS
animating={this.state.showProgress}
size="large"
style={styles.loader}
/>
</View>
);
} else {
return(
<View style={styles.container}>
<Image style={styles.logo} source={require('image!logo')} />
<Text style={styles.heading}>
Please use QR-Scanner to login,{'\n'}
or enter the Login code manually.
</Text>
<TextInput onChangeText={(text)=> this.setState({logincode: text})} style={styles.loginInput} placeholder={this.state.logincode}>
</TextInput>
<TouchableOpacity onPress={this.onLoginPressed.bind(this)} style={styles.button} >
<Text style={styles.buttonText}>Log in</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={this.toBarcode.bind(this)}
>
<Text style={styles.change}>
Want to use Barcode?
</Text>
</TouchableOpacity>
{errorCtrl}
<ActivityIndicatorIOS
animating={this.state.showProgress}
size="large"
style={styles.loader}
/>
</View>
);
}
/////
}
}
onLoginPressed(){
if(this.state.isConnected){
/// do the validation
var valid = false;
if(this.state.logincode != undefined && this.state.logincode.includes('opencampus://') && this.state.logincode.includes('access_token=') && this.state.logincode.includes('refresh_token=') && this.state.logincode.includes('id=') && this.state.logincode.includes('name=') && this.state.logincode.includes('scheme=')){
var valid = true;
}
if(valid){
console.log('Login.ios: Attempting to log in with logincode ' + this.state.logincode);
this.setState({showProgress: true});
console.log('Login.ios: calling AuthService class');
var AuthService = require('./AuthService');
AuthService.login({
logincode: this.state.logincode
}, (results)=> {
this.setState(Object.assign({
showProgress: false
}, results));
console.log('Login.ios: AuthService execution finished.', results);
if(results.success && this.props.onLogin){
this.props.onLogin(results);
}
});
} else {
AlertIOS.alert(
'Invalid Input',
'Login code you entered is not valid. Be sure to paste the whole string starting with opencampus://'
);
}
} else {
AlertIOS.alert(
'No Connection',
'Please check your internet connection.'
);
}
}
onQrPressed(){
this.setState({
showCamera: true,
});
}
toManuall(){
this.setState({
barcode: false,
});
}
toBarcode(){
this.setState({
barcode: true,
});
}
}
var styles = StyleSheet.create({
container: {
backgroundColor: '#00a2dd',
paddingTop: 40,
padding: 10,
alignItems: 'center',
flex: 1,
justifyContent: 'center'
},
logo: {
width: 141,
height: 137,
},
heading: {
fontSize: 18,
margin: 10,
marginBottom: 20,
color: '#FFFFFF',
paddingTop: 50,
},
change: {
fontSize: 12,
color: '#FFFFFF',
marginTop:10,
},
loginInput: {
height: 50,
marginTop: 10,
padding: 4,
fontSize: 18,
borderWidth: 1,
borderColor: '#FFFFFF',
borderRadius: 0,
color: '#FFFFFF'
},
button: {
height: 50,
backgroundColor: '#48BBEC',
borderColor: '#48BBEC',
alignSelf: 'stretch',
marginTop: 10,
justifyContent: 'center',
alignItems: 'center',
borderRadius: 5
},
buttonText: {
color: '#fff',
fontSize: 24
},
loader: {
marginTop: 20
},
error: {
color: 'red',
paddingTop: 10
}
});
module.exports = Login;
Here is AuthService.js :
'use strict';
import React, { Component } from 'react';
var SQLite = require('react-native-sqlite-storage');
var DeviceInfo = require('react-native-device-info');
class AuthService extends Component {
constructor(props) {
super(props);
this.state = {
showProgress: false
}
this.errorCB = this.errorCB.bind(this);
this.successCB = this.successCB.bind(this);
}
errorCB(err) {
console.log("Auth Service: error: ", err);
this.state.progress.push("Error: " + (err.message || err));
return false;
}
successCB() {
}
login(creds, cb){
var db = SQLite.openDatabase({name : "oc.db", location: 'default'}, this.successCB.bind(this), this.errorCB.bind(this));
var sql = 'CREATE TABLE IF NOT EXISTS users ('
+ 'access_token text NOT NULL,'
+ 'refresh_token text NOT NULL,'
+ 'userName text NOT NULL,'
+ 'userId text NOT NULL,'
+ 'userMail text NOT NULL,'
+ 'userSignature text NOT NULL,'
+ 'userSignatureFormat text NOT NULL,'
+ 'userCreated text NOT NULL,'
+ 'userAccess text NOT NULL,'
+ 'userLogin text NOT NULL,'
+ 'userStatus text NOT NULL,'
+ 'userTimezone text NOT NULL,'
+ 'userLanguage text NOT NULL,'
+ 'userRoles text NOT NULL,'
+ 'deviceId text NOT NULL,'
+ 'deviceName text NOT NULL,'
+ 'host text NOT NULL,'
+ 'active text NOT NULL'
+ ');';
db.executeSql(sql, [],
this.successCB.bind(this),
this.errorCB.bind(this)
);
var LCode = creds.logincode;
var codeSplited = LCode.split("://");
var codeSplited2 = codeSplited[1].split("?");
var appName = codeSplited[0];
var serverName = codeSplited2[0];
var splitedVars = codeSplited2[1].split("&");
var access_token = splitedVars[0].split("=");
var access_token = access_token[1];
var refresh_token = splitedVars[1].split("=");
var refresh_token = refresh_token[1];
var uid = splitedVars[2].split("=");
var uid = uid[1];
var uname = splitedVars[3].split("=");
var uname = uname[1];
var scheme = splitedVars[4].split("=");
var scheme = scheme[1];
var device_id = DeviceInfo.getUniqueID();
var device_name = DeviceInfo.getDeviceName();
var locale = DeviceInfo.getDeviceLocale();
console.log('AuthService: Try to fetch from : ', serverName);
console.log('request body: ', JSON.stringify({
uid: uid,
refresh_token: refresh_token,
token: access_token,
device: device_id,
device_name: device_name,
}));
fetch(scheme + '://' + serverName, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'language': locale,
'Authorization': 'Bearer ' + access_token,
},
body: JSON.stringify({
uid: uid,
refresh_token: refresh_token,
token: access_token,
device: device_id,
device_name: device_name,
})
})
.then((response)=> {
return response;
})
.then((response)=> {
return response.json();
})
.then((results)=> {
console.log(results);
if(results['result'] == 1){
console.log('Auth Service: Login was successfull');
// User data
var userName = results['session']['user']['name'];
var userId = results['session']['user']['uid'];
var userMail = results['session']['user']['mail'];
var userSignature = results['session']['user']['signature'];
var userSignatureFormat = results['session']['user']['signature_format'];
var userCreated = results['session']['user']['created'];
var userAccess = results['session']['user']['access'];
var userLogin = results['session']['user']['login'];
var userStatus = results['session']['user']['status'];
var userTimezone = results['session']['user']['timezone'];
var userLanguage = results['session']['user']['language'];
var userRoles = results['session']['user']['roles']['2'];
var host = results['session']['user']['host'];
var active = 'yes';
//var userPicture = results['session']['user']['picture'];
console.log('Auth Service: Lets save user data to database');
var query = "INSERT INTO users (access_token, refresh_token, userName, userId, userMail, userSignature, userSignatureFormat, userCreated, userAccess, userLogin, userStatus, userTimezone, userLanguage, userRoles, deviceId, deviceName, host, active) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
var params = [access_token, refresh_token, userName,userId,userMail,userSignature,userSignatureFormat,userCreated,userAccess,userLogin,userStatus,userTimezone,userLanguage,userRoles,device_id,device_name,host,active];
db.executeSql(query,params,
this.successCB.bind(this),
this.errorCB.bind(this)
);
return cb({
success: true,
userData: results['session']['user']
});
} else if(results['result'] == 0){
console.log('Auth Service: Login failed message is ' + results['message']);
return cb({
success: false,
message: results['message']
});
} else {
console.log('Auth Service: Login failed error is ' + results['error_description']);
return cb({
success: false,
message: results['error_description']
});
}
})
.catch((err)=> {
console.log('AuthService: ' + err);
return cb(err);
})
.done();
}
}
module.exports = new AuthService();
And here is Index.js :
"use strict";
import React, {Component, PropTypes} from 'react';
import {
AppRegistry,
NavigatorIOS,
StyleSheet,
TabBarIOS,
View,
Text,
StatusBar,
} from 'react-native';
var CourseList = require("./app/CourseList");
var Profile = require("./app/Profile");
import Icon from 'react-native-vector-icons/Ionicons';
var SQLite = require('react-native-sqlite-storage');
var Login = require("./app/Login");
var db = SQLite.openDatabase({name : "oc.db", location: 'default'});
StatusBar.setBarStyle('light-content');
class OpenCampus extends Component {
constructor(props) {
super(props);
this.state = {
selectedTab: "Courses",
isLoggedIn: false,
userId: null,
};
}
componentWillMount(){
var query = "SELECT * FROM users WHERE active='yes'";
var params = [];
db.transaction((tx) => {
tx.executeSql(query,params, (tx, results) => {
var len = results.rows.length;
if(len > 0){
let row = results.rows.item(0);
this.setState({
isLoggedIn: true,
userId: row.userId
});
}
}, function(){
console.log('index: Something went wrong');
});
});
}
onLogin(results) {
this.setState({
isLoggedIn: true,
});
}
logout() {
console.log("Logout called from index");
var query = "DELETE FROM users WHERE userId=?";
var params = [this.state.userId];
db.transaction((tx) => {
tx.executeSql(query,params, (tx, results) => {
///// check if there is other accounts on database, if yes, make first row active
var query = "SELECT * FROM users WHERE active='yes'";
var params = [];
db.transaction((tx) => {
tx.executeSql(query,params, (tx, results) => {
var len = results.rows.length;
if(len > 0){
let row = results.rows.item(0);
userId = row.userId;
///// Set new user active
var query = "UPDATE users SET active='yes' WHERE userId=?";
var params = [userId];
db.transaction((tx) => {
tx.executeSql(query,params, (tx, results) => {
console.log('index: Active Account Changed');
}, function(){
console.log('index: Something went wrong');
});
});
///////
this.setState({
isLoggedIn: true,
userId: userId,
});
} else {
this.setState({
isLoggedIn: false,
userId: null,
});
}
}, function(){
console.log('index: Something went wrong');
});
});
/////
}, function(){
console.log('index: Something went wrong when logging out');
});
});
}
_renderCourses() {
return (
<NavigatorIOS style={styles.wrapper}
barTintColor='#00a2dd'
titleTextColor='#fff'
tintColor='#ffffff'
ref='RCourses'
initialRoute={{
component: CourseList,
title: 'Courses',
passProps: {filter: 'Courses'},
}}
/>
);
}
_renderRegister() {
return (
<NavigatorIOS style={styles.wrapper}
barTintColor='#00a2dd'
titleTextColor='#fff'
tintColor='#ffffff'
ref='RRegister'
initialRoute={{
component: CourseList,
title: 'Register',
passProps: {filter: 'Register'},
}}
/>
);
}
_renderProfile() {
return (
<NavigatorIOS style={styles.wrapper}
barTintColor='#00a2dd'
titleTextColor='#fff'
tintColor='#ffffff'
ref='RProfile'
initialRoute={{
component: Profile,
title: 'Profile',
passProps: {filter: 'Profile'},
rightButtonTitle: 'Logout',
onRightButtonPress: () => this.logout(),
leftButtonTitle: 'Add Account',
onLeftButtonPress: () => this.addnew(),
}}
/>
);
}
addnew() {
console.log('Send user to login page to add new account');
//// Set old user to inactive
var query = "UPDATE users SET active='no' WHERE active='yes'";
var params = [this.state.userId];
db.transaction((tx) => {
tx.executeSql(query,params, (tx, results) => {
//// Set login status to false so login screen will be shown
console.log(results);
this.setState({
isLoggedIn: false,
userId: null,
});
}, function(){
console.log('index: Something went wrong when adding new account');
});
});
}
popAll(){
if(typeof this.refs.RCourses !== typeof undefined){
this.refs.RCourses.popToTop();
}
if(typeof this.refs.RRegister !== typeof undefined){
this.refs.RRegister.popToTop();
}
if(typeof this.refs.RProfile !== typeof undefined){
this.refs.RProfile.popToTop();
}
}
render() {
if(!this.state.isLoggedIn){
console.log('index: User not logged in. redirecting to Login page.');
return(
<Login onLogin={this.onLogin.bind(this)} />
);
} else {
console.log('index: User is logged in lets show the content');
return (
<TabBarIOS tintColor={"#00a2dd"}>
<Icon.TabBarItem
title="Courses"
iconName="ios-list-outline"
selectedIconName="ios-list-outline"
selected={this.state.selectedTab === "Courses"}
onPress={() => {
this.setState({
selectedTab: "Courses",
});
this.popAll();
}}>
{this._renderCourses()}
</Icon.TabBarItem>
<Icon.TabBarItem
title="Register"
iconName="ios-book"
selectedIconName="ios-book"
selected={this.state.selectedTab === "Register"}
onPress={() => {
this.setState({
selectedTab: "Register",
});
this.popAll();
}}>
{this._renderRegister()}
</Icon.TabBarItem>
<Icon.TabBarItem
title="Profile"
iconName="ios-person"
selectedIconName="ios-person"
selected={this.state.selectedTab === "Profile"}
onPress={() => {
this.setState({
selectedTab: "Profile",
});
this.popAll();
}}>
{this._renderProfile()}
</Icon.TabBarItem>
</TabBarIOS>
);
}
}
}
var styles = StyleSheet.create({
tabContent: {
flex: 1,
alignItems: "center",
},
tabText: {
color: "white",
margin: 50,
},
wrapper: {
flex: 1,
backgroundColor: '#00a2dd',
}
});
AppRegistry.registerComponent('OpenCampus', () => OpenCampus);
UPDATE : here is the crash log by apple : http://www.ataomega.com/temp..suczkfac.crash http://www.ataomega.com/temp..hsbgdlod.crash