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

13,038

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)
Share:
13,038
alt
Author by

alt

I'm a generalist IT consultant and developer. I try to learn a little about everything!

Updated on June 29, 2022

Comments

  • alt
    alt almost 2 years

    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?

  • alt
    alt over 4 years
    I had assumed that CORS wasn't required, however I also serve web clients so it's needed for that. Also I'm not sure if the issue link applies any more - otherwise wouldn't it be broken on iOS as well?