1

My question is simple.

I make a call using fetch in my CORDOVA app like so, from the ./www/js/index.js script:

const response = await fetch('http://localhost:3000/poc3/convert', {
  method: 'POST',
  body: formData,
  headers: { 'Accept': 'audio/mpeg' },
});

I get this in the logs:

Error: TypeError: Failed to fetch

I know the API endpoint works 100%.

I know the system works with a fetch towards the prod URL endpoint (https://mywebsite/poc3/convert).

I think this fetch error is because I need to enable something from the config.xml file.

Below are all the versions and configs I am running:

   Cordova Packages:
    
        cli: 12.0.0
            common: 5.0.1
            create: 5.0.0
            lib: 12.0.2
                common: 5.0.1
                fetch: 4.0.0
                serve: 4.0.1
    
    Project Installed Platforms:
    
        android: 14.0.0
    
    Project Installed Plugins:
    
        cordova-plugin-advanced-http: 3.3.1
        cordova-plugin-file: 8.1.3
        cordova-plugin-file-transfer: 2.0.0
    
    Environment:
    
        OS: Ventura 13.1 (22C65) (darwin 22.2.0) x64
        Node: v18.19.0
        npm: 10.2.3
    
    android Environment:
    
        android:
    [=======================================] 100% Fetch remote repository...       
    Available Android targets:
    ----------
    id: 1 or "android-35"
         Name: Android API 35, extension level 13
         Type: Platform
         API level: 35
         Revision: 2
    
    
    Project Setting Files:
    
        config.xml:
    <?xml version='1.0' encoding='utf-8'?>
    <widget id="com.mywebsite" version="1.0.0" xmlns="http://www.w3.org/ns/widgets"
        xmlns:cdv="http://cordova.apache.org/ns/1.0"
        xmlns:android="http://schemas.android.com/apk/res/android">
        <name>mywebsite</name>
        <description>Sample Apache Cordova App</description>
        <author email="[email protected]" href="https://cordova.apache.org">
            Apache Cordova Team
        </author>
        <content src="index.html" />
        <allow-intent href="https://mywebsite.com/*" />
    
        <!-- Localhost API calls enabled in development for testing search code: #000 for all enables -->
        <access origin="http://localhost:3000" />
        <!-- <access origin="http://*/*" /> -->
    
        <access origin="https://mywebsite.com" />
    
        <allow-navigation href="https://mywebsite.com/*" />
    
        <preference name="AndroidInsecureFileModeEnabled" value="true" />
        <preference name="AndroidExtraFilesystems" value="files,files-external,cache" />
        <preference name="AndroidPersistentFileLocation" value="Compatibility" />
        <platform name="android">
    
            <config-file target="AndroidManifest.xml" parent="/manifest">
                <uses-permission android:name="android.permission.INTERNET" />
                <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
                <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
                    android:maxSdkVersion="28" />
                <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
                    android:maxSdkVersion="30" />
            </config-file>
    
        </platform>
    </widget>
    
        package.json:
    --- Start of Cordova JSON Snippet ---
    {
      "platforms": [
        "android"
      ],
      "plugins": {
        "cordova-plugin-file": {
          "ANDROIDX_WEBKIT_VERSION": "1.4.0"
        },
        "cordova-plugin-advanced-http": {
          "ANDROIDBLACKLISTSECURESOCKETPROTOCOLS": "SSLv3,TLSv1"
        },
        "cordova-plugin-file-transfer": {}
      }
    }
    --- End of Cordova JSON Snippet ---

Extra notes:

  1. The HTML meta is set like so: connect-src 'self' http://localhost:3000 https://mywebsite.com;
  2. I tried to editing the inner configuration files in .xml in these files: ./platforms/android/app/src/main/AndroidManifest.xml and ./platforms/android/app/src/main/res/xml/config.xml, but the build process RESETS the configurations to DEFAULT. (no effect)
  3. I tried to add BELOW in the main ./config.xml. And it did not work either

BELOW:

    <!-- #000 -->
    <!-- <allow-navigation href="http://localhost:3007/*" />
    <allow-intent href="http://localhost:3007/*" />
    <preference name="AndroidInsecureFileModeEnabled" value="true" /> -->

    <config-file target="AndroidManifest.xml" parent="/manifest">
        <uses-permission android:name="android.permission.INTERNET" />
        <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
            android:maxSdkVersion="28" />
        <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
            android:maxSdkVersion="30" />
        <!-- #000 -->
        <!-- <application android:usesCleartextTraffic="true" /> -->
    </config-file>

        


    <!-- #000 -->
    <!-- <edit-config file="AndroidManifest.xml" mode="merge" target="/manifest/application">
        <application android:usesCleartextTraffic="true" />
    </edit-config> -->

    <!-- #000 -->
    <!-- <config-file parent="/*" target="res/xml/config.xml">
        <network-security-config>
            <base-config cleartextTrafficPermitted="true" />
        </network-security-config>
    </config-file> -->

</platform>

1 Answer 1

1

If you are struggling with a similar issue, I figured out the solution.

Turns out, a conceptual problem was overlooked by me.

Because the Android app was being run and tested on my physical Android device, a call to "localhost" would reference the actual phone and not the computer it was connected to.

So, obviously, the endpoint was running on the connected computer and not on the phone, hence the fetch failed.

If you are using an emulator to test your Android app, I think the same issue persists because the emulator also "thinks" that localhost is the actual emulated physical device.

As for a solution to still hit the local endpoint on your computer from your testing Android context, it is possible with the edits below:

  1. Use a plugin called: cordova-plugin-advanced-http

Then make sure in your code you use an API call like so instead of fetch. Path: ./www/js/index.js

async function uploadAndConvert(arrayBuffer, fileName) {
  const formData = new FormData();
  const audioBlob = new Blob([arrayBuffer], { type: 'audio/aiff' });
  formData.append('file', audioBlob, fileName);

  return new Promise((resolve, reject) => {
    cordova.plugin.http.sendRequest(
      'http://XXX.XXX.X.XX:3000/poc3/convert',
      {
        method: 'post',
        data: formData,
        headers: { 'Accept': 'audio/mpeg' },
        serializer: 'multipart',
        responseType: 'arraybuffer'
      },
      (response) => {
        if (response.status >= 400) {
          let serverError = 'Unknown server error';
          try {
            // Attempt to parse error response as text
            serverError = String.fromCharCode.apply(null, new Uint8Array(response.data.slice(0, 256)));
          } catch (e) {}
          console.error(`Server Error ${response.status}:`, serverError);
          reject(new Error(`Server responded with error ${response.status}`));
          return;
        }
        resolve(new Blob([response.data], { type: 'audio/mpeg' }));
      },
      (error) => {
        // Extract meaningful error details from the plugin's error object
        const errorMessage = error.error || error.message || JSON.stringify(error);
        reject(new Error(`Request failed: ${errorMessage}`));
      }
    );
  });
}
  1. In the API call in your code, you reference the endpoint through the computer's IP address.

Use this command if you are on Mac to identify your IP address:

ifconfig | grep "inet " | grep -v 127.0.0.1
  1. You need to allow connections to http://XXX.XXX.X.XX:3000 through the HTML meta tag in ./www/index.html:

.

<meta http-equiv="Content-Security-Policy"
<!-- all your configurations for content security policy -->
content="connect-src http://XXX.XXX.X.XX:3000"
<!-- all your configurations for content security policy -->
>

The meta Content-Security-Policy sets security rules for what resources (scripts, APIs, etc.) your webpage can load or connect to.

  1. Finally, in the ./config.xml add this:

.

<platform name="android">
<!-- other permissions -->
        <edit-config file="app/src/main/AndroidManifest.xml" mode="merge" target="/manifest/application">
            <application android:usesCleartextTraffic="true" />
        </edit-config>
<!-- other permissions -->
</platform>

This allows unencrypted HTTP traffic for Android (disables HTTPS requirement).

The android:usesCleartextTraffic="true" permits insecure HTTP connections (needed for local/dev servers without SSL).

It is required if your app communicates with http:// servers (e.g., http://XXX.XXX.X.XX:3000 from your CSP).

Use this only in development—remove for production apps.

With all these edits, your new API call from your Android emulator or Android physical device should function.

Hope this solution works and is insightful!

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.