capacitor
                                
                                 capacitor copied to clipboard
                                
                                    capacitor copied to clipboard
                            
                            
                            
                        bug: service worker server.url not injecting capacitor
Bug Report
Capacitor Version
Latest Dependencies:
  @capacitor/cli: 3.3.2
  @capacitor/core: 3.3.2
  @capacitor/android: 3.3.2
  @capacitor/ios: 3.3.2
Installed Dependencies:
  @capacitor/ios: not installed
  @capacitor/cli: 3.3.2
  @capacitor/android: 3.3.2
  @capacitor/core: 3.3.2
Platform(s)
Current Behavior
Calling any native plugins when an app is served from an Angular service worker causes the plugins to fail. Using server.url to load the site on Android.

Platform ready does not help for this.
Expected Behavior
Capacitor needs to be injected when a site is served through a service worker.
Code Reproduction
app.module.ts
export function init_app(appLoadService: AppLoadService) {
  return () => appLoadService.initApp();
}
@NgModule({
  declarations: [AppComponent],
  entryComponents: [],
  imports: [
    BrowserModule,
    IonicModule.forRoot(),
    AppRoutingModule,
    ServiceWorkerModule.register('ngsw-worker.js', {
      enabled: environment.production,
      // Register the ServiceWorker as soon as the app is stable
      // or after 30 seconds (whichever comes first).
      registrationStrategy: 'registerWhenStable:30000',
    }),
  ],
  providers: [
    { provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
    {
      provide: APP_INITIALIZER,
      useFactory: init_app,
      deps: [AppLoadService],
      multi: true,
    },
  ],
  bootstrap: [AppComponent],
})
export class AppModule {}
app-load-service.ts
@Injectable({
  providedIn: 'root',
})
export class AppLoadService {
  constructor(
    private device: DeviceInfo,
    private platform: Platform
  ) {}
  async initApp(): Promise<any> {
    await this.platform.ready();
    SplashScreen.hide(); // breaks
  }
}
It actually seems that the capacitor script is not being injected after the site is served by a service worker.
I have included this on Android but still getting plugin errors:

I don't believe that this works any more.
The site is picking up that its running on Android through a service worker:
console.log('Platforms: ' + Capacitor.getPlatform());
console.log('Is Native: ' + Capacitor.isNativePlatform());
Platforms: android
Is Native: true
 

Silly question, but for a workaround, it is possible to inject capacitor script manually?
The issue, anyone have a workaround?
Please see a repo here: https://github.com/happendev/TestCapacitor
The PWA site is hosted on https://testapp.happen.zone.
Android:
- When site loaded through the Android app without a service worker, native plugins work, capacitor works.
- When site is loaded through a service worker after the site has been cached, native plugins and capacitor do not work.
Maybe I have done something wrong?
When the Android app is first installed you can see Capacitor is working:

The site files are downloaded and served directly on first run:

When the app is launched the second time, Capacitor is not injected:

The files are now served from the service worker:


I have yet to do some additional testing, but I hope this issue is not the same on iOS.
@jcesarmobile is there anything I can do to assist with this?
@dylanvdmerwe I started looking into what should be done in order to support this use case (I'll need it as well).
This is what we talked about with @thomasvidas on Discord :
me : I think we need to introduce the ServiceWorkerClient in order to intercept requests from the service worker : https://stackoverflow.com/a/59606081 and https://stackoverflow.com/questions/62354423/how-to-use-serviceworkercontroller-on-android/63491172#63491172 The shouldInterceptRequest is the same as the WebviewClient's one so it should be a matter of calling them both
@thomasvidas : That seems pretty doable. I'd assume that WebViewLocalServer.shouldInterceptRequest() could be modified to check if it should serve from the service worker or from the normal local capacitor server. Maybe it would be better to change bridge.getLocalServer()? But I'm more wary of that since this doesn't work on API 21-23 and I try to avoid touching the bridge if I can since it touches so much of the codebase.
@dylanvdmerwe Did you test on iOS a well?
@jgoux I have tried to get a service worker working so I can test this on iOS - but I did not come right with whatever needs to be enabled for this. Does anyone know what I missed in the repo? I would love to check out how the service worker is affected on iOS as well as part of this. Or is there another repo where this works that I can use for testing?
I'm facing this "problem" too....
As a temporary "workaround" (in order to solve this), I force a site reload when the ServiceWorker is Ready. I know and I also don't like the solution, but I needed to implement this 💩 while this "issue/bug/feature" is addressed.
if (Capacitor.isNativePlatform() && Capacitor.getPlatform() === 'android') {
  const reboot = localStorage.getItem('reboot')
  if (reboot !== '1') {
    localStorage.setItem('reboot', '1')
    setTimeout(() => location.reload(), 1)
  } else {
    localStorage.setItem('reboot', '0')
  }
}
This code is working for me, at the time as I write this... (is working on production).
Forgive me... 😂
Using this kind of functionality is primarily not for the app stores in our case, we really want to use this for apps that are rolled out (enterprise) to operators and employees at our clients. This would drastically reduce the building required to deploy updates to them, as all we would need to do is update the PWA.
Please advise if there is anything that I can do to assist or test to get server.url working for Android and iOS through a service worker on Capacitor.
@jgoux Do you have any more feedback on the way forward on this?
Has anyone tried anything further as we would think this kind of thing would be incredible to get working through Capacitor apps.
@dylanvdmerwe Hello Dylan,
I didn't have time to investigate yet.
Did you already try this solution : https://stackoverflow.com/a/55916297/1728627 ?
I believe @mhjam made a contribution to make it works here : https://github.com/ionic-team/capacitor/pull/1465, maybe he knows more about it? 😄
Nope, It's not working anymore.
If the path your service worker is loading contains a ".html" file extension, the the following update in WebViewLocalServer.handleProxyRequest() resolves the issue (if you also hook up a service worker client delegating to WebViewLocalServer like in https://stackoverflow.com/questions/62354423/how-to-use-serviceworkercontroller-on-android/63491172#63491172). Note that this is exactly the same hack that is in WebViewLocalServer.handleLocalRequest(), so if it's good enough for local requests it should be good enough for proxy ones too. I've opened a pull request for it: https://github.com/ionic-team/capacitor/pull/5428
                if (url.contains(".html")) {
                    isHtmlText = true;
                } else {
                    for (Map.Entry<String, String> header : headers.entrySet()) {
                        if (header.getKey().equalsIgnoreCase("Accept") && header.getValue().toLowerCase().contains("text/html")) {
                            isHtmlText = true;
                            break;
                        }
                    }
                }
@chrisharris77 No this does not appear to work, at least not when using a normal ionic app as per my repo.
https://github.com/happendev/TestCapacitor
https://testapp.happen.zone/
- Install app for the first time. The app works. It loads the files from the site and appears to install a service worker for offline. Plugins work.
- Restart the app, the app loads the files from the service worker's offline cache. Capacitor does not seem to be initialized and as such plugins fail and the app breaks when they are used.
if (Capacitor.isNativePlatform() && Capacitor.getPlatform() === 'android') { const reboot = localStorage.getItem('reboot') if (reboot !== '1') { localStorage.setItem('reboot', '1') setTimeout(() => location.reload(), 1) } else { localStorage.setItem('reboot', '0') } }
Hi @xEsk, can you provide some more context, or a repo for this? I have tried, but even with this Capacitor is not initialized which means plugins don't work when loaded from the SW offline version. Any use of the plugins breaks.
The first run (when not served by the service worker):
- Platform: Android
- Native: true.

This is the service worker cached PWA version of the app running on Android device.
As you can see, it still detects that the platform is Android and isNativePlatform is true. But it has not initialized any android plugin stuff which is why the plugins are breaking.
Is there maybe a way to force Capacitor to run in 'web' mode here (as a true PWA), as then the plugins should work? Ideally it should setup itself correctly and run in proper Android mode.
@dylanvdmerwe @webuniverseio @goforu @xEsk @chrisharris77
Any updates about this issue ?
if (Capacitor.isNativePlatform() && Capacitor.getPlatform() === 'android') { const reboot = localStorage.getItem('reboot') if (reboot !== '1') { localStorage.setItem('reboot', '1') setTimeout(() => location.reload(), 1) } else { localStorage.setItem('reboot', '0') } }
Is this part still working ? Do service workers also work with this fix ? (managing dynamic modules download)
Thanks a lot !
Is there any permanent resolution to this issue to make service workers work with capacitor plugins on Android? It is very frustrating that my Ionic6/Angular13 app works nicely with the service worker on browser but fails to register the same on Android. When I implement the workaround suggested, service worker loads but capacitor plugins fail to load on the device. Please update when there is any resolution or workaround for this.
@jcesarmobile
Do you need a budget to priorise this issue ?
ffs

Still nobody found a solution here ?
I using this answer and work for me on android with capacitor 2. https://stackoverflow.com/questions/55894716/how-to-package-a-hosted-web-app-with-ionic-capacitor/55916297#55916297
but I replace ServiceWorkerController to ServiceWorkerControllerCompat for my case.
Thanks a lot @yoyo930021
I'm ready to pay someone as freelance to develop the supported module / fix for capacitor. Who wants here ?
@jcesarmobile
Any info about updates on this issue ? Can we sponsor the fix ?
I using this answer and work for me on android with capacitor 2. https://stackoverflow.com/questions/55894716/how-to-package-a-hosted-web-app-with-ionic-capacitor/55916297#55916297
but I replace
ServiceWorkerControllertoServiceWorkerControllerCompatfor my case.
Works also on iOS ?
I using this answer and work for me on android with capacitor 2. https://stackoverflow.com/questions/55894716/how-to-package-a-hosted-web-app-with-ionic-capacitor/55916297#55916297
but I replace
ServiceWorkerControllertoServiceWorkerControllerCompatfor my case.
It is broken when sometimes. =_=
The capacitor will inject a javascript file into any HTML resources.
But the capacitor uses the Accept header value to decide on HTML resources.
This value is broken when using a service worker.
Code: https://github.com/ionic-team/capacitor/blob/main/android/capacitor/src/main/java/com/getcapacitor/WebViewLocalServer.java#L336
I can't find any other value to decide on HTML resources when service worker.
I would very much appreciate an externally hosted server PWA setup to interface properly with Capacitor's plugins on iOS and Android. I wonder if anyone above has come across any solutions?