7

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?

Matthias
  • 1,759
  • 16
  • 34
  • 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. – jcesarmobile Apr 29 '19 at 11:50
  • @jcesarmobile 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 https://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? – Matthias Apr 29 '19 at 13:19
  • 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. – jcesarmobile Apr 29 '19 at 13:46
  • @jcesarmobile 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? – Matthias Apr 30 '19 at 08:02
  • sounds helpful, do you want to send a pull request with it? – jcesarmobile Apr 30 '19 at 10:19
  • @jcesarmobile PR is here: https://github.com/ionic-team/capacitor/pull/1465 – Matthias May 03 '19 at 07:03

2 Answers2

9

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.)

Matthias
  • 1,759
  • 16
  • 34
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.

Martin Braun
  • 10,906
  • 9
  • 64
  • 105