Search code examples
javascriptreact-nativesuperagent

“the network is offline, Origin is not allowed by Access-Control-Allow-Origin, the page is being unloaded” from superagent in Android React Native


I'm using superagent to upload files from my React Native app. It works perfectly fine on iOS, but on Android it gives this error:

Possible causes: the network is offline, Origin is not allowed by Access-Control-Allow-Origin, the page is being unloaded, etc.

I've created a minimal example here https://snack.expo.io/@twohill/upload-example and copied the code below in case the snack goes away:

import React, { Component } from 'react';
import { Text, View, StyleSheet, TouchableOpacity } from 'react-native';
import Constants from 'expo-constants';
import * as DocumentPicker from 'expo-document-picker';
import superagent from 'superagent';

import AssetExample from './components/AssetExample';
import { Card } from 'react-native-paper';

const upload = (file, setMessage) => {
  const { name } = file;

  superagent.post('https://example.org/uploads') // <-- change this URL to your server that accepts uploads and is CORS enabled
    .set('Authorization', 'BEARER xxxyyy') // <-- JWT token here
    .attach('uri', file, name)
    .then(
      result => setMessage(JSON.stringify({result})),
      error => setMessage(JSON.stringify({error}))
      );
};

const pickDocuments = async (setMessage) => {
  const file = await DocumentPicker.getDocumentAsync({ copyToCacheDirectory: true });
  if (file.type === "success") {
    upload(file, setMessage);
  }
}

export default class App extends Component {
  state = {
    message: null,
  }
  render() {
    const { message } = this.state;
    return (

      <View style={styles.container}>
       <TouchableOpacity onPress={() => pickDocuments(message => this.setState({message}))}>
        <Text style={styles.paragraph}>
          Tap to upload a file
        </Text>
        <Card>
          <AssetExample />
        </Card>
        <Card><Text>{message}</Text></Card>
         </TouchableOpacity>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    paddingTop: Constants.statusBarHeight,
    backgroundColor: '#ecf0f1',
    padding: 8,
  },
  paragraph: {
    margin: 24,
    fontSize: 18,
    fontWeight: 'bold',
    textAlign: 'center',
  },
});

If I console.log the error it gives the following:

Request has been terminated
Possible causes: the network is offline, Origin is not allowed by Access-Control-Allow-Origin, the page is being unloaded, etc.
* http://192.168.1.3:19001/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&minify=false&hot=false:282311:24 in crossDomainError
- node_modules\@sentry\utils\dist\instrument.js:224:24 in <anonymous>
- node_modules\event-target-shim\dist\event-target-shim.js:818:39 in EventTarget.prototype.dispatchEvent
- node_modules\react-native\Libraries\Network\XMLHttpRequest.js:566:23 in setReadyState
- node_modules\react-native\Libraries\Network\XMLHttpRequest.js:388:25 in __didCompleteResponse
- node_modules\react-native\Libraries\vendor\emitter\EventEmitter.js:190:12 in emit
- node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:436:47 in __callFunction
- node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:111:26 in __guard$argument_0
- node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:384:10 in __guard
- node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:110:17 in __guard$argument_0
* [native code]:null in callFunctionReturnFlushedQueue

As far as I can tell, on Android the app never tries an upload. My server runs express and has the cors middleware enabled with the default configuration

{
  "origin": "*",
  "methods": "GET,HEAD,PUT,PATCH,POST,DELETE",
  "preflightContinue": false,
  "optionsSuccessStatus": 204
}

Any ideas what to do here? I get the feeling that the Android is baulking at the "*" origin, but have no idea what to put in place for a mobile app.

Or am I barking up the wrong tree completely?


Solution

  • Thanks for everyone's help. With the confirmation that the error isn't likely to be CORS I did some digging with the debugger and found the actual error: Binary FormData part needs a content-type header

    screenshot of XMLHttpRequest

    From there, I was able to do even more digging, and found that I was being led astray (at least on Android) by the documentation https://visionmedia.github.io/superagent/#attaching-files as the options map I was sending was being ignored

    debugger showing underlying FormData append call

    With the help of the react-native-mime-types package the fixed code looks like this:

      //snip
      const fileWithMime = { ...file, type: mime.lookup(name) };
    
      request.post(`${SERVER_URL}/uploads`)
        .set('Authorization', cookie)
        .withCredentials()
        .attach('uri', fileWithMime)