How to package a hosted web app with Ionic Capacitor
Asked Answered
O

3

8

I have a hosted, CMS-based web app that I want to package as an Android / iOS app with Ionic Capacitor. So I added

  "server": {
    "url": "https://my.domain/"
  },

to capacitor.config.json and did

import { Capacitor, Plugins } from '@capacitor/core';

console.log('Plugins', Capacitor.Plugins);

in the main Javascript file of my app (I use webpack for bundling). This basically works, i.e. the app gets loaded and displayed properly. But on the console I see that Capacitor loads the web versions of the plugins, and Device.getInfo() says that the platform is "web", not "android".

How can I make Capacitor act like it would when my app was loaded from the device's file system, and in particular how can I make it use the native versions of the plugins in this setup?

Otocyst answered 28/4, 2019 at 21:26 Comment(6)
I could be some problem on the bundler. If you use the plugin without the bundler (regular Capacitor.Plugins.Device.getInfo()), do you get the same result?. Anyway, I would advise against pointing to an url, that's supposed to be used for development, (i.e, point to a local live reload server), not to a website.Shewchuk
@Shewchuk Thanks. I probably found the cause (will write an answer after double-checking): I tested this in the emulator with a local server, and for some reason Android does not call shouldInterceptRequest() for 10.0.2.2 (which is the IP for the emulator's host). So JS injection doesn't work in this special case - unless you give this IP a name via the emulator's hosts file. --- What would you recommend for loading a CMS-driven, dynamic site (think of a news paper) into an app, instead of pointing to the remote server? Setting location.href in the local index.html?Otocyst
You don't have to use 10.0.2.2, but your server's local IP. I would not recommend embedding a dynamic site, instead I would recommend having your local assets in your app and use XHR/fetch to get content updates or a feed. Embedding websites is not allowed by Apple and I think Google Play can also reject apps because of that.Shewchuk
@Shewchuk During development 10.0.2.2 is my server's local IP. Anyway, the true cause eventually was something else, see my answer. And in general I share your concerns about embedding a web site, but the customer wants to try it this way at least. We will add some more functionality (e.g. push, offline pages) and hope to meet Apple's requirements for offering "more than a packaged website" this way. Thanks for responding! Do you think the getter for localServer would be a good enhancement for Capacitor's Bridge class in general?Otocyst
sounds helpful, do you want to send a pull request with it?Shewchuk
@Shewchuk PR is here: github.com/ionic-team/capacitor/pull/1465Otocyst
O
11

As it turned out, the reason for my troubles where that my pages had an active service worker. Capacity uses WebViewClient::shouldInterceptRequest for injecting the Javascript code that initializes the bridge to the native world, and Android does not call this callback for requests that a service worker handles. Instead, it has a separate callback for these requests that is available via a ServiceWorkerController.

So what I did was creating my own tiny plugin:

@NativePlugin
public class ServiceWorker extends Plugin {

  @RequiresApi(api = Build.VERSION_CODES.N)
  @Override
  public void load() {
    ServiceWorkerController swController = ServiceWorkerController.getInstance();

    swController.setServiceWorkerClient(new ServiceWorkerClient() {
      @Override
      public WebResourceResponse shouldInterceptRequest(WebResourceRequest request) {
        return bridge.getLocalServer().shouldInterceptRequest(request);
      }
    });
  }

}

and then it worked as expected. (I also had to add the getter for localServer to the Bridge class.)

Otocyst answered 30/4, 2019 at 7:56 Comment(1)
This solution no longer works, see my answer below.Varistor
M
0

I'm dealing with a Capacitor v2 project that also requires to have in-app navigation to a remote website which uses custom Capacitor plugins that are hosted on the Capacitor wrapper. I was also testing with a local WebServer that can be reached at 10.0.2.2 (Android emulator to localhost). The accepted solution is not relevant anymore, the code was never reached, and it was not helpful, however I did some digging into the bridge itself.

My WebServer was hosted on port 8000 locally, so the Bridge failed to have an exact match of the url within the UrlMatcher and thus was returning no handler to progress the request to http://10.0.2.2:8000 in my wrapper.

I figured it would work when I put this into my capacitor.config.json:

    "server": {
        "allowNavigation": [
            "10.0.2.2",
            "10.0.2.2:8000",
        ]
    }

The first entry is to prevent capacitor from opening a new browser instead of launching it within the frame, the 2nd is to exact match the authority segment within the UrlMatcher. Maybe it was fixed in more recent versions, but for v2 this worked.

May answered 8/11, 2022 at 1:26 Comment(0)
V
0

Anyone having similar problem I was able to solve by updating android/app/main/java/.../MainAcitivity.java

https://github.com/ionic-team/capacitor/issues/5278#issuecomment-1313531876

public class MainActivity extends BridgeActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ServiceWorkerController swController = ServiceWorkerController.getInstance();

        swController.setServiceWorkerClient(new ServiceWorkerClient() {
            @Nullable
            @Override
            public WebResourceResponse shouldInterceptRequest(WebResourceRequest request) {
                if (request.getUrl().toString().contains("index.html")) {
                    request.getRequestHeaders().put("Accept", "text/html");
                }
                return bridge.getLocalServer().shouldInterceptRequest(request);
            }
        });
    }
}

Also need to update minSdkVersion = 24 in android/variables.gradle

Varistor answered 4/10, 2023 at 12:42 Comment(6)
I'm testing this on Capacitor v6, it's not working.May
Hi @MartinBraun, I'have a similar issue. Did you find a way for Capacitor v6 in the meantime?Jessikajessup
@Jessikajessup Use server.allowNavigation with one host. If you have more than one host (i.e. example.org and *.example.org) the recent implementation is broke and you need my PR here to land (or patch yourself for the time being). The newest implementation also doesn't support wildcards in IPs if you try to make it work locally, so be more explicit. Lastly, if you try to whitelist a local website without SSL, you also need to include an additional entry with http:// (i.e. http://192.168.0.10), because their implementation is poor.May
Thank you @MartinBraun. I will try it. One follow up question: Do I still need to apply the change to MainAcitivity.java as stated above our comments?Jessikajessup
@Jessikajessup As of now, I still have this in my code (I forgot to remove it, and it won't hurt). I will try to remove it next time I work on the project, but for now, include it and try to remove it once it works to verify it yourself.May
I can confirm that the solution above combined with your insights and PR works! Finally. Thank you very much, @MartinBraun!Jessikajessup

© 2022 - 2024 — McMap. All rights reserved.