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

3

7

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?

Freud answered 5/2, 2020 at 2:23 Comment(2)
From https://mcmap.net/q/1628455/-the-page-is-being-unloaded-error I see superagent is the source of that error which mentions “Origin is not allowed by Access-Control-Allow-Origin” — specifically the code at github.com/visionmedia/superagent/blob/master/src/…. But that “Origin is not allowed by Access-Control-Allow-Origin” part of the message is misleading, because Node doesn’t enforce the same-origin policy; therefore CORS and the Access-Control-Allow-Origin header aren’t relevant. So the actual cause of the error is something else, not CORS.Hulbard
The answers at https://mcmap.net/q/1628456/-strange-route-behaviour-angularjs-with and https://mcmap.net/q/1628457/-reactjs-with-react-router-strange-routing-behaviour-on-chrome may or may not be relevant here: “The cause is ANY button click getting treated as a submit and the form not having a submit action target. One solution is to add a preventDefault event handler.”Hulbard
F
2

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)
Freud answered 7/2, 2020 at 4:6 Comment(0)
R
0

For me the issue was with the ios/MyProject.xcodeproj/project.pbxproj ALLOWED_URL_PREFIXES (for Android it is android/app/src/main/AndroidManifest.xml AllowedUrlPrefixes)

        <meta-data
            android:name="AllowedUrlPrefixes"
            android:value='https://bitpay.com,...' />

I was missing my domain on this list and XMLHttpRequest (used by superagent library) always returned 0 status code :(

Responsum answered 18/7 at 12:55 Comment(0)
A
-1

http client in react-native is basically native http client with JS bridge, it has no CORS restriction like the web, so you don't need to allow CORS in your express server.

In terms of superagent, it seems you need some tweaking to use it in react-native, checkout this issue

Accipitrine answered 5/2, 2020 at 2:59 Comment(1)
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?Freud

© 2022 - 2024 — McMap. All rights reserved.