Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React Native: App Store Rejection, Support for IPv6

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

like image 310
Ata Mohammadi Avatar asked Aug 05 '16 07:08

Ata Mohammadi


People also ask

What does React Native not support?

React Native does not support parallel threading or multiprocessing. React Native consists of single Javascript thread due to which you might observe some slow performance when you want to execute some processes in parallel. Your application might suffer due to this.

Why did airbnb dropped React Native?

They wanted things like geolocation, internationalization, and many other complex features, that are not often needed by other types of applications. This was the reason why React Native didn't bode well for them anymore.

Is React Native declining?

This question has created a lot of buzz in the field of mobile app development platforms. Part reason for the decline in React Native's popularity is due to the emergence of Google-backed Flutter that slowly but gradually started gaining wider acceptance among developers and clients alike.

Will Facebook stop supporting React Native?

As of January 19, 2021, Facebook React Native SDK will no longer be officially supported by Facebook.


1 Answers

You should test your app for ipv6 compatibility. Here is a tutorial that explains how to do that.

  • Boot OS X 10.11
  • Make sure your Mac is connected to the Internet, but not through Wi-Fi.
  • Launch System Preferences from your Dock, LaunchPad, or the Apple menu.
  • Press the Option key and click Sharing. Don’t release the Option key yet.
  • Select Internet Sharing in the list of sharing services.
  • Release the Option key.
  • Select the Create NAT64 Network checkbox.
  • Choose the network interface that provides your Internet connection, such as Thunderbolt Ethernet.
  • Select the Wi-Fi checkbox.
  • Click Wi-Fi Options, and configure the network name and security options for your network.
  • Setting up local Wi-Fi network options
  • Select the Internet Sharing checkbox to enable your local network.
  • When prompted to confirm you want to begin sharing, click Start.
  • Once sharing is active, you should see a green status light and a label that says Internet Sharing: On. In the Wi-Fi menu, you will also see a small, faint arrow pointing up, indicating that Internet Sharing is enabled. You now have an IPv6 NAT64 network and can connect to it from other devices in order to test your app.
like image 175
Simon Epskamp Avatar answered Oct 05 '22 21:10

Simon Epskamp