ionic-team / capacitor

Build cross-platform Native Progressive Web Apps for iOS, Android, and the Web ⚡️
https://capacitorjs.com
MIT License
12.21k stars 1.01k forks source link

bug: API requests failing or documentation unclear on correct behavior #6198

Open kyllerss opened 1 year ago

kyllerss commented 1 year ago

Bug Report

After reading the documentation as to how Capacitor works behind the scenes, it is unclear as to what the expected behavior should be when invoking APIs. Here is my understanding vs what I am seeing on my end:

Something that I am finding confusing which requires clarification in the documentation is how the capacitor router works when it is trying to distinguish requests intended for a remote server endpoint versus the static-asset-serving instance within Capacitor. Often static assets are hosted in the same domain as the API endpoints.

I assume that all requests go through the Capacitor router (static and API endpoints) and that it would determine if the requested endpoint matched an existing static asset and forward the request off to the matching server endpoint if it didn't find a matching static asset. Either this is a misunderstanding on my behalf on how the internal localhost instance works and could use some clarification in the documentation, or my assumption is correct and I have some misconfiguration on my end that needs fixing.

This is what I am seeing when setting the hostname parameter to match my single domain:

... <static assets successfully retrieved>
2022-12-28 10:44:16.515  6516-6576  Capacitor               com.my-domain                        D  Handling local request: https://www.my-domain.com/_app/immutable/assets/LoadMoreControl-be86b520.css
2022-12-28 10:44:16.585  6516-6576  Capacitor               com.my-domain                        D  Handling local request: https://www.my-domain.com/tracks.json?page_size=15&page_num=0
2022-12-28 10:44:16.586  6516-6576  Capacitor               com.my-domain                        E  Unable to open asset URL: https://www.my-domain.com/tracks.json?page_size=15&page_num=0

Based on the above error (Unable to open asset URL: <api endpoint>), it seems as if Capacitor is treating all URLs as static assets. If that is the case, then what is the expected best practice for the hostname setting? How can someone make API endpoint calls when the hostname setting is set?

Would be helpful if some best practices were documented on how to handle cookies, remote endpoints, and static assets that would clarify similar misunderstandings.

Capacitor Version

💊   Capacitor Doctor  💊 

Latest Dependencies:

  @capacitor/cli: 4.6.1
  @capacitor/core: 4.6.1
  @capacitor/android: 4.6.1
  @capacitor/ios: 4.6.1

Installed Dependencies:

  @capacitor/ios: not installed
  @capacitor/cli: 4.6.1
  @capacitor/android: 4.6.1
  @capacitor/core: 4.6.1

[success] Android looking great! 👌

Platform(s)

Android

Current Behavior

Non-static assets requests are rejected.

Expected Behavior

Unclear expectations when setting hostname server property.

Code Reproduction

Other Technical Details

npm --version output: 8.19.2

node --version output: v16.18.0

Additional Context

Ideally, Capacitor would serve static assets when present, and proxy request to endpoint when missing (as would be the case with dynamic URLs, or API endpoints).

I have a locally-running webserver that is serving the endpoints over HTTPS. My capacitor.config.json is as follows (obfuscated):

{
  "appId": "com.my-domain",
  "appName": "MyDomain",
  "bundledWebRuntime": false,
  "webDir": "dist",
  "plugins": {
    "SplashScreen": {
      "launchShowDuration": 0
    }
  },
  "server": {
    "hostname": "www.my-domain.com",
    "androidScheme": "https"
  }
}

I am using SvelteKit with a custom script that handles the build process. I also have an entry in my /etc/hosts file for www.my-domain.com along with a self-signed cert I have installed system-wide.

silviogutierrez commented 1 year ago

@kyllerss setting the hostname to get around CORS seems like a magic bullet, but I don't believe it's meant to be used this way. That's why Capacitor is including a patched fetch that ignores CORS / cross origin cookie issues. It's currently buggy though (#6177) so a lot of people use this plugin: https://github.com/silkimen/cordova-plugin-advanced-http . If using the latter, watch out for: https://github.com/silkimen/cordova-plugin-advanced-http/issues/492

You can sort of see the documentation here: https://capacitorjs.com/docs/config

Under server.iosScheme it clarifies that Can't be set to schemes that the WKWebView already handles, such as http or https so that means that, at least on iOS, you'd be unable to share cookies because the schemes wouldn't match. That is, your faked server origin would be something like capacitor://www.my-domain.com/ hitting something like https://www.my-domain.com/ and those are not the same origin.

That's my understanding, but it could be wrong. If just "faking" the hostname is all it takes to bypass all CORS/cookie/fetch issues that'd be amazing.

kyllerss commented 1 year ago

Thanks for the answer @silviogutierrez . It's disappointing that there isn't a simple solution to this issue. I suppose my confusion comes from using SvelteKit, which handles much of the communication between client and servers implicitly (SvelteKit handles a page's SPA lifecycle by orchestrating server fetches and managing state behind the scenes). Unless someone explicitly fetches state from a given server endpoint, they will be relying on SvelteKit's internals to send server requests which will end up going to the localhost instance in the packaged binary (someone please correct me if SvelteKit has some way of addressing this). This nuance regarding what the WebView considers the host context will cause people some confusion and frustration.

For the sake of others out there, I think it would be a good idea to add a note to the documentation that points out the issues that people will run into with SvelteKit-style frameworks (ie. frameworks that glue together back-end code and front-end code seemlessly). Perhaps it is not an emergency now, but I can imagine this will continue to come up as these frameworks gain more adoption.

alexcroox commented 1 year ago

I was caught in this trap when trying to call my API which was on example.com/api and my hostname was set to example.com. It was routing all API requests to itself. Needed to move the API to a subdomain, will likely have to do that for all assets too. Combine that with plugins/features failing due to having an insecure host on iOS if you do change it, I might have to stick with localhost. Shame I won't be able to use autofill/password manager integration as a result however

emmernme commented 12 months ago

This is also the recommended setup to support password autofilling, ref. https://capacitorjs.com/docs/guides/autofill-credentials. This makes it very hard to achieve both password autofill & e.g. serving images or perform API calls to & from the same domain in the same app.

pdrhlik commented 8 months ago

I had the exact same issue like @alexcroox @emmernme. I set up password autofilling by following the docs and it wasn't working properly on Android. I ended moving my API from domain.com/api/ to api.domain.com/api/. I also had to start serving some of my assets through api.domain.com. So it's possible to make it work but it's not really straightforward. It would at least help if there was a mention in the official docs. There's even a pull request in the docs repo but it's been waiting for a review for half a year.

alexcroox commented 8 months ago

A fix might be to allow a URL path to be ignored from the internal routing. Eg /api/*

alexconstantin commented 6 months ago

My backend automatically redirects https://host.com to https://www.host.com. I was able to use https://www.host.com for the autofill-credentials and https://host.com for the backend API. Not a great workaround, but it works. It would be great if we had a correct solution for this.

ryaa commented 1 month ago

are there any plans to resolve this issue? for example, to enable configuring URL path to be ignored from the internal routing, as @alexcroox suggested.

akash-melkeri commented 3 weeks ago

I was facing exact problem a minute ago. I realized that we don't need hostname to solve cors for calling apis from different server. I updated it like this:

"server": {
  "androidScheme": "https",
  "allowNavigation": [
    "domain.com",
    "*.domain.com"
  ]
}

My api is running at domain.com/api. After updating like this it worked for me.