React Native:App Store拒绝,支持IPv6

我有一个简单的反应原生客户端用于网站,在登录页面它提供两个选项,手动输入登录代码或使用条形码扫描仪扫描它。 我已经在真实设备和模拟器中测试了应用程序很多时候它工作正常。 实际上我只测试ipv4,并使用fetch登录,我认为默认情况下支持ipv6。

当应用程序离线时,他们会通过ipv6网络说,我无法理解离线和在IPV6网络上的意义是什么?

当应用程序离线时,我向用户显示没有连接的错误。 所以我不知道它会如何崩溃。

应该添加超时来获取请求修复问题?

但由于同样的错误,应用被拒绝了3次:

表现 – 2.1

感谢您的重新提交。

当我们执行以下操作时,您的应用程序在连接到IPv6网络的运行iOS 9.3.3的iPhone上崩溃:

具体来说,点击登录仍会导致应用崩溃。

使用您的应用时会发生这种情况:

  • 离线
  • 在Wi-Fi上

我们附加了详细的崩溃日志,以帮助解决此问题。

这是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 (   ); } else { var errorCtrl = ; if(!this.state.success){ errorCtrl =  {this.state.message} ; } ///// Check login type if(this.state.barcode){ return(    Please use QR-Scanner to login,{'\n'} or enter the Login code manually.   Use QR-Scanner    Want to enter code manually?   {errorCtrl}   ); } else { return(    Please use QR-Scanner to login,{'\n'} or enter the Login code manually.   this.setState({logincode: text})} style={styles.loginInput} placeholder={this.state.logincode}>   Log in    Want to use Barcode?   {errorCtrl}   ); } ///// } } 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; 

这是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(); 

这是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 (  ); } _renderRegister() { return (  ); } _renderProfile() { return (  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(  ); } else { console.log('index: User is logged in lets show the content'); return (   { this.setState({ selectedTab: "Courses", }); this.popAll(); }}> {this._renderCourses()}   { this.setState({ selectedTab: "Register", }); this.popAll(); }}> {this._renderRegister()}   { this.setState({ selectedTab: "Profile", }); this.popAll(); }}> {this._renderProfile()}   ); } } } var styles = StyleSheet.create({ tabContent: { flex: 1, alignItems: "center", }, tabText: { color: "white", margin: 50, }, wrapper: { flex: 1, backgroundColor: '#00a2dd', } }); AppRegistry.registerComponent('OpenCampus', () => OpenCampus); 

更新:这是苹果的崩溃日志: http : //www.ataomega.com/temp..suczkfac.crash http://www.ataomega.com/temp..hsbgdlod.crash

您应该测试您的应用程序的ipv6兼容性。 这是一个教程,解释了如何做到这一点。

  • 启动OS X 10.11
  • 确保Mac已连接到Internet,但不能通过Wi-Fi连接。
  • 从Dock,LaunchPad或Apple菜单启动系统偏好设置。
  • 按Option键,然后单击“共享”。 不要释放Option键。
  • 在共享服务列表中选择“Internet共享”。
  • 释放Option键。
  • 选择“创建NAT64网络”复选框。
  • 选择提供Internet连接的网络接口,例如Thunderbolt以太网。
  • 选择Wi-Fi复选框。
  • 单击“Wi-Fi选项”,然后配置网络的网络名称和安全选项。
  • 设置本地Wi-Fi网络选项
  • 选中“Internet共享”复选框以启用本地网络。
  • 当系统提示您确认要开始共享时,请单击“开始”。
  • 共享处于活动状态后,您应该会看到一个绿色状态指示灯和一个标记为Internet Sharing:On的标签。 在Wi-Fi菜单中,您还会看到一个向上的小箭头,表示已启用Internet共享。 您现在拥有IPv6 NAT64网络,可以从其他设备连接到该网络以测试您的应用。