Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bug(ServiceWorker): multiple apps with ServiceWorker on one domain #21388

Closed
skydever opened this issue Jan 8, 2018 · 31 comments

Comments

Projects
None yet
@skydever
Copy link

commented Jan 8, 2018

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[x] Bug report  
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

Current behavior

The installation of a 2nd App/ServiceWorker (with different baseHref) purges the Cache Storage entries for a previously installed App/ServiceWorker, if they are on the same domain. This breaks the 1st installed App. It is not reachable anymore - you will always be redirected to the latest installed App.

Expected behavior

The installation of multiple Apps/ServiceWorkers (with different baseHref) on one domain should be possible.

Minimal reproduction of the problem with instructions

I created the following repo to reproduce the issue: https://github.com/skydever/repro-multiple-ng-apps-one-domain-serviceworker. Follow the steps of the README there.

What is the motivation / use case for changing the behavior?

I have to deploy multiple Angular Apps on one domain.

Environment


Angular version: 5.1.3


Browser:
- [x] Chrome (desktop) version XX
- [ ] Chrome (Android) version XX
- [ ] Chrome (iOS) version XX
- [ ] Firefox version XX
- [ ] Safari (desktop) version XX
- [ ] Safari (iOS) version XX
- [ ] IE version XX
- [ ] Edge version XX
 
For Tooling issues:
- Node version: v8.9.1  
- Platform: Windows 

Others:

@skydever

This comment has been minimized.

Copy link
Author

commented Jan 12, 2018

@alxhub Could this be a valid solution at db-cache.ts?

export class CacheDatabase implements Database {
  private cacheScope: String; // to distinct between multiple scopes
  private tables = new Map<string, Promise<CacheTable>>();

  constructor(
    private scope: ServiceWorkerGlobalScope,
    private adapter: Adapter
  ) {
    this.cacheScope = this.scope.scope !== './' ? `:${this.scope.scope}` : '';
  }

  delete(name: string): Promise<boolean> {
    if (this.tables.has(name)) {
      this.tables.delete(name);
    }
    return this.scope.caches.delete(`ngsw:db${this.cacheScope}:${name}`);
  }

  list(): Promise<string[]> {
    return this.scope.caches
      .keys()
      .then(keys =>
        keys.filter(key => key.startsWith(`ngsw:db${this.cacheScope}:`))
      );
  }

  open(name: string): Promise<Table> {
    if (!this.tables.has(name)) {
      const table = this.scope.caches
        .open(`ngsw:db${this.cacheScope}:${name}`)
        .then(cache => new CacheTable(name, cache, this.adapter));
      this.tables.set(name, table);
    }
    return this.tables.get(name)!;
  }
}

... but I have to provide different scopes, and the scope will always be ./ by default (and wanted so to intercept all requests down the url path where the service worker script is located) ... maybe get the baseHref somehow? but how can I obtain it safely not doing something like document.getElementsByTagName('base')[0].href?

@skydever

This comment has been minimized.

Copy link
Author

commented Jan 13, 2018

found this at browser_adapter.ts:

let baseElement: HTMLElement|null = null;
function getBaseElementHref(): string|null {
  if (!baseElement) {
    baseElement = document.querySelector('base') !;
    if (!baseElement) {
      return null;
    }
  }
  return baseElement.getAttribute('href');
}

it is related to the platform that is used and I don't know how to wire this up ...

@skydever

This comment has been minimized.

Copy link
Author

commented Jan 13, 2018

just get it in a safe way using document if available, sw are only available in the platform browser ...

@skydever

This comment has been minimized.

Copy link
Author

commented Jan 14, 2018

the CacheDatabase is service worker code, so no dom access there ... I can manipulate the ngsw-worker.js after the build process to include the app/baseHref info in CacheDatabase, by replacing the 3 ngsw:db: entries with ngsw:db:[app-base-href]: ...

@skydever

This comment has been minimized.

Copy link
Author

commented Jan 17, 2018

I created an issue at the Angular CLI repo too. both are involved somehow, the baseHref option of the CLI needs to be passed to the service worker. I don't think that the service worker is able to do that without information from the CLI?

@ngbot ngbot bot added this to the Backlog milestone Jan 23, 2018

@skydever

This comment has been minimized.

Copy link
Author

commented Jan 24, 2018

replacing the ngsw:db: key seems not to work (even replacing the 6 occurences of ngsw: with a unique key does not work, but the distinction is better if you look at the Cache Storage at the dev tools - there are some entries without the ngsw:db prefix if you just replace the 3 occurences of ngsw:db) - the content of the control entry in the CacheDatabase still gets overwritten from another app ...

@skydever

This comment has been minimized.

Copy link
Author

commented Jan 26, 2018

did some more research, maybe this is helpful somehow. after renaming ngsw: (6 entries in ngsw-worker.js) to ngsw:swAppOne and ngsw:swAppTwo it seems to work, but the Cache Storage seems like to be in an invalid state ... I created a branch in my repo for that, just npm install in sw-app-one and run npm run start-sw and open the pages at sw-app-one and sw-app-two, then have a look into the Cache Storage in the dev tools - the entries ngsw:sw-app-two:db:control and ngsw:sw-app-one:db:control have the same values for assignments, latest and manifests - an image of it here.

Afterwards I also renamed the entries assigments to assigmentsSwAppOne and so on (the other way round in the commit, was to create a reproduction of the weird state). The result looks clean now, with all the renaming. but still weird somehow, I also refreshed the cache in the dev tools ...

@ngbot ngbot bot modified the milestones: Backlog, needsTriage Feb 26, 2018

@gaurav2887

This comment has been minimized.

Copy link

commented Mar 5, 2018

@skydever - Did you find any solution for this?

@skydever

This comment has been minimized.

Copy link
Author

commented Mar 6, 2018

hi @gaurav2887

my solution was a bit hacky and I don't use it in production at the moment, but it seemed to be working - maybe you can give it a try and report your results with the current version?

I created a post-build script that does some replacing in the dist/ngsw-worker.js file. The Cache Storage entries somehow got mixed up between multiple apps, one app is overriding entries from another app. I replace the following for each app that will be deployed on the same domain (eg. app-one):

  • ngsw: -> ngsw:app-one: (6 occurences)
  • 'manifests' -> 'manifestsAppOne' (3 occurences)
  • 'assignments' -> 'assignmentsAppOne' (3 occurences)
  • 'latest' -> 'latestAppOne' (3 occurences)

This way each app has unique keys for the Cache Storage and one app will not override entries from another app. I am not sure about any side effects, but the last time I tried it seemed to be working.

@gaurav2887

This comment has been minimized.

Copy link

commented Mar 6, 2018

@skydever - Thanks for the reply. I need something for prod as we are serving multiple apps under one domain and service worker is not working that way.

@skydever

This comment has been minimized.

Copy link
Author

commented Mar 6, 2018

@gaurav2887 what are the problems you are facing? do the service worker of the apps register? do you get the redirect error (always to the latest installed app)? did you try the hack I provided? maybe also relevant: #20970 #20405, AngularCLI: #8515 #8516

a possible solution could be the usage of sub domains (app1.domain.com, app2.domain.com) because then you have different origins and one app should not be able to manipulate the CacheStorage of another app...

I am looking forward to production ready ServieWorker support as well ...

@gaurav2887

This comment has been minimized.

Copy link

commented Mar 7, 2018

@skydever - Service worker works fine for one app but does not get loaded for other app as it does not have baseHref while loading the ngsw.json for other app. So the first default root app works fine with service worker and I have to add dataGroups to ask for fresh copy of other app. We would like to use one domain and allow multiple apps loading based on the route (/app1,/app2). Making manual changes ngsw-worker.js will not be good for prod so I will probably wait for the fix which will allow us to have ngsw.json loaded per app.

@dcatoday

This comment has been minimized.

Copy link

commented Apr 12, 2018

I am also seeing problems with registering multiple service workers on one domain. As it stands I'm not able to successfully register one service worker that isn't located in the root folder. I am trying to add --base-href and --deploy-url and run a node server with the app in a specified folder. The ngsw seems to configure but it doesn't cache any of the application files.

@Splaktar

This comment has been minimized.

Copy link
Member

commented May 1, 2018

This has more information, but it seems like a duplicate of #20405.

@rjwijnen

This comment has been minimized.

Copy link

commented Jun 26, 2018

Any news on this issue? This is blocking the enabling of service worker for our apps.

@gkalpak

This comment has been minimized.

Copy link
Member

commented Jun 26, 2018

Nobody is actively working on this afaict (there are other higher priority stuff going on).
If anyone wants to investigate this further, I'd be happy to review (but it won't be a trivial undertaking 😱).

@petersalomonsen

This comment has been minimized.

Copy link
Contributor

commented Oct 19, 2018

I had success with a workaround of editing the ngsw-worker.js after the build, and then replace every occurence of ngsw: with ngsw-your_unique_app_id:

Then the caches prefix will be unique per app, and I'm able to switch apps without being redirected to the latest installed app.

Anyone working on a pull request for this - or has it become better in Angular 7?

@gkalpak

This comment has been minimized.

Copy link
Member

commented Oct 19, 2018

I don't think anyone is working on it and neither have things changed in Angular 7 (afaik).
If you want to take a stub at it, I'd be happy to review.

sheikalthaf added a commit to sheikalthaf/angular that referenced this issue Nov 13, 2018

feat(service-worker): support multiple apps in same domain
suffixing the baseHref with ngsw:<base-href> string to seperate caches
files app in same domain

Fixes issue angular#21388

sheikalthaf added a commit to sheikalthaf/angular that referenced this issue Nov 13, 2018

feat(service-worker): support multiple apps in one domain
suffing baseHref with ngsw string separates the cahce files

Fixes issue angular#21388

gkalpak added a commit to sheikalthaf/angular that referenced this issue Nov 21, 2018

feat(service-worker): support multiple apps on different subpaths of …
…a domain

Previously, it was not possible to have multiple apps (using
`@angular/service-worker`) on different subpaths of the same domain,
because each SW would overwrite the caches of the others (even though
their scope was different).

This commit fixes it by ensuring that the cache names created by the SW
are different for each scope.

Fixes angular#21388

gkalpak added a commit to sheikalthaf/angular that referenced this issue Nov 21, 2018

feat(service-worker): support multiple apps on different subpaths of …
…a domain

Previously, it was not possible to have multiple apps (using
`@angular/service-worker`) on different subpaths of the same domain,
because each SW would overwrite the caches of the others (even though
their scope was different).

This commit fixes it by ensuring that the cache names created by the SW
are different for each scope.

Fixes angular#21388

gkalpak added a commit to sheikalthaf/angular that referenced this issue Nov 21, 2018

feat(service-worker): support multiple apps on different subpaths of …
…a domain

Previously, it was not possible to have multiple apps (using
`@angular/service-worker`) on different subpaths of the same domain,
because each SW would overwrite the caches of the others (even though
their scope was different).

This commit fixes it by ensuring that the cache names created by the SW
are different for each scope.

Fixes angular#21388

gkalpak added a commit to sheikalthaf/angular that referenced this issue Nov 22, 2018

feat(service-worker): support multiple apps on different subpaths of …
…a domain

Previously, it was not possible to have multiple apps (using
`@angular/service-worker`) on different subpaths of the same domain,
because each SW would overwrite the caches of the others (even though
their scope was different).

This commit fixes it by ensuring that the cache names created by the SW
are different for each scope.

Fixes angular#21388
@canhamd

This comment has been minimized.

Copy link

commented Dec 3, 2018

Solution proposed by @petersalomonsen also worked for me. I would suggest adding a namespace field to ngsw-config.json, which if is present when generating ngsw-worker.js is used as a postfix to all occurences of ngsw: in that file.

i.e. if a namespace field is provided in ngsw-config.json with a value of "my-app", then perform a find/replace on ngsw-worker.js post generation (or during) replacing all occurences of ngsw: with ngsw-my-app:

allowing this namespace value to be provided in tooling would also be useful. e.g. as a question when performing ng add @angular/pwa

@petersalomonsen

This comment has been minimized.

Copy link
Contributor

commented Dec 3, 2018

@canhamd Evolved this a bit and created this script that I run after building the production bundles. It creates an unique app id by hashing the value of the index property in ngsw.json. Add the dist folder as an argument when executing the script.

const fs = require('fs');
var crypto = require('crypto');
const distfolder = process.argv[2];
const appindex = JSON.parse(fs.readFileSync(`${distfolder}/ngsw.json`)).index;
const hash = crypto.createHash('sha1');
hash.update(appindex);
const uniqueappid = hash.digest('hex').substr(0,8);

const workerfilecontents = new String(fs.readFileSync(`${distfolder}/ngsw-worker.js`));
console.log(`Replacing in ${distfolder}/ngsw-worker.js 'ngsw:' with 'ngsw-${uniqueappid}:'`);
fs.writeFileSync(`${distfolder}/ngsw-worker.js`, workerfilecontents.replace(/ngsw\:/g,`ngsw-${uniqueappid}:`));

console.log(`Replacing scope and start-url in manifest`);
const manifest = JSON.parse(fs.readFileSync(`${distfolder}/manifest.json`));
const starturl = appindex.replace("index.html", "");
manifest.scope = starturl;
manifest.start_url = starturl;
fs.writeFileSync(`${distfolder}/manifest.json`, JSON.stringify(manifest, null, 1));
@gkalpak

This comment has been minimized.

Copy link
Member

commented Dec 3, 2018

I don't think adding a value to ngsw-config.json is a good idea, as it does not guarantee uniqueness across subpaths.
Nor is hashing ngsw.json, since this will ignore the previous caches and always download all assets from the server.

The SW scope should be used instead. In case anyone has missed it, there is a PR in progress for that: #27080

@petersalomonsen

This comment has been minimized.

Copy link
Contributor

commented Dec 3, 2018

@gkalpak Correcting myself - I meant the index property value of the ngsw.json file. But very good that there's an official solution in progress for this.

gkalpak added a commit to sheikalthaf/angular that referenced this issue Jan 16, 2019

feat(service-worker): support multiple apps on different subpaths of …
…a domain

Previously, it was not possible to have multiple apps (using
`@angular/service-worker`) on different subpaths of the same domain,
because each SW would overwrite the caches of the others (even though
their scope was different).

This commit fixes it by ensuring that the cache names created by the SW
are different for each scope.

Fixes angular#21388

gkalpak added a commit to sheikalthaf/angular that referenced this issue Jan 16, 2019

feat(service-worker): support multiple apps on different subpaths of …
…a domain

Previously, it was not possible to have multiple apps (using
`@angular/service-worker`) on different subpaths of the same domain,
because each SW would overwrite the caches of the others (even though
their scope was different).

This commit fixes it by ensuring that the cache names created by the SW
are different for each scope.

Fixes angular#21388

gkalpak added a commit to sheikalthaf/angular that referenced this issue Mar 20, 2019

feat(service-worker): support multiple apps on different subpaths of …
…a domain

Previously, it was not possible to have multiple apps (using
`@angular/service-worker`) on different subpaths of the same domain,
because each SW would overwrite the caches of the others (even though
their scope was different).

This commit fixes it by ensuring that the cache names created by the SW
are different for each scope.

Fixes angular#21388

gkalpak added a commit to sheikalthaf/angular that referenced this issue Mar 20, 2019

feat(service-worker): support multiple apps on different subpaths of …
…a domain

Previously, it was not possible to have multiple apps (using
`@angular/service-worker`) on different subpaths of the same domain,
because each SW would overwrite the caches of the others (even though
their scope was different).

This commit fixes it by ensuring that the cache names created by the SW
are different for each scope.

Fixes angular#21388

gkalpak added a commit to sheikalthaf/angular that referenced this issue Mar 20, 2019

feat(service-worker): support multiple apps on different subpaths of …
…a domain

Previously, it was not possible to have multiple apps (using
`@angular/service-worker`) on different subpaths of the same domain,
because each SW would overwrite the caches of the others (even though
their scope was different).

This commit fixes it by ensuring that the cache names created by the SW
are different for each scope.

Fixes angular#21388

@matsko matsko closed this in e721c08 Mar 21, 2019

@skydever

This comment has been minimized.

Copy link
Author

commented Mar 24, 2019

🎉 awesome!! thx a lot to everybody involved in this 👍

@Kush5900

This comment has been minimized.

Copy link

commented Apr 8, 2019

it still don't work!

@tplk

This comment has been minimized.

Copy link

commented Apr 8, 2019

@Kush5900 what version have you tried it on?
Looks like it'll be released in 8.0.0 but you can already try it out in the latest beta.

@Kush5900

This comment has been minimized.

Copy link

commented Apr 9, 2019

@tplk Yeah i'm on 7.2 and i'am working on a serious project can't update to beta versions! i'm currently using @petersalomonsen work around but it seems unstable somehow. any alternative?

@petersalomonsen

This comment has been minimized.

Copy link
Contributor

commented Apr 9, 2019

@Kush5900 Are you using my workaround as in the script below?

I'm running this using the dist folder as argument after building the bundles.

const fs = require('fs');
var crypto = require('crypto');
const distfolder = process.argv[2];
const appindex = JSON.parse(fs.readFileSync(`${distfolder}/ngsw.json`)).index;
const hash = crypto.createHash('sha1');
hash.update(appindex);
const uniqueappid = hash.digest('hex').substr(0,8);

const workerfilecontents = new String(fs.readFileSync(`${distfolder}/ngsw-worker.js`));
console.log(`Replacing in ${distfolder}/ngsw-worker.js 'ngsw:' with 'ngsw:${uniqueappid}:'`);
fs.writeFileSync(`${distfolder}/ngsw-worker.js`, workerfilecontents.replace(/ngsw\:/g,`ngsw:${uniqueappid}:`));

console.log(`Replacing scope and start-url in manifest`);
const manifest = JSON.parse(fs.readFileSync(`${distfolder}/manifest.json`));
const starturl = appindex.replace("index.html", "");
manifest.scope = starturl;
manifest.start_url = starturl;
fs.writeFileSync(`${distfolder}/manifest.json`, JSON.stringify(manifest, null, 1));
@Kush5900

This comment has been minimized.

Copy link

commented Apr 9, 2019

@pedroclayman Yeah i copied the whole script and it works and i'm able to switch between languages but since then my serviceWorker stopped working in offline mode! How are you registering the service worker in app.module.ts ?

@petersalomonsen

This comment has been minimized.

Copy link
Contributor

commented Apr 9, 2019

I register my serviceworker like this:

ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production })

No absolute path. My apps work fine in offline mode. @Kush5900

@Kush5900

This comment has been minimized.

Copy link

commented Apr 9, 2019

@petersalomonsen that it is how i register my serviceworker too, sometimes it works and sometimes it produce these error:

An unknown error occurred when fetching the script.
Failed to load resource: net::ERR_INTERNET_DISCONNECTED
@petersalomonsen

This comment has been minimized.

Copy link
Contributor

commented Apr 9, 2019

hmmm, I'm not experiencing that @Kush5900 but will let you know if I do.

wKoza added a commit to wKoza/angular that referenced this issue Apr 17, 2019

feat(service-worker): support multiple apps on different subpaths of …
…a domain (angular#27080)

Previously, it was not possible to have multiple apps (using
`@angular/service-worker`) on different subpaths of the same domain,
because each SW would overwrite the caches of the others (even though
their scope was different).

This commit fixes it by ensuring that the cache names created by the SW
are different for each scope.

Fixes angular#21388

PR Close angular#27080
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.