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 · 32 comments
Closed

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

skydever opened this issue Jan 8, 2018 · 32 comments
Labels
area: service-worker Issues related to the @angular/service-worker package freq1: low type: bug/fix
Milestone

Comments

@skydever
Copy link

skydever 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:

@kara kara added the area: service-worker Issues related to the @angular/service-worker package label Jan 8, 2018
@alxhub alxhub added feature Issue that requests a new feature type: bug/fix freq1: low severity2: inconvenient and removed feature Issue that requests a new feature labels Jan 9, 2018
@skydever
Copy link
Author

@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
Copy link
Author

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
Copy link
Author

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

@skydever
Copy link
Author

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
Copy link
Author

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
Copy link
Author

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
Copy link
Author

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
Copy link

@skydever - Did you find any solution for this?

@skydever
Copy link
Author

skydever 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
Copy link

@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
Copy link
Author

skydever 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
Copy link

gaurav2887 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
Copy link

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
Copy link
Member

Splaktar commented May 1, 2018

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

@rjwijnen
Copy link

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

@gkalpak
Copy link
Member

gkalpak 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
Copy link
Contributor

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
Copy link
Member

gkalpak 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
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
suffing baseHref with ngsw string separates the cahce files

Fixes issue angular#21388
gkalpak pushed a commit to sheikalthaf/angular that referenced this issue Nov 21, 2018
…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 pushed a commit to sheikalthaf/angular that referenced this issue Nov 21, 2018
…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 pushed a commit to sheikalthaf/angular that referenced this issue Nov 21, 2018
…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 pushed a commit to sheikalthaf/angular that referenced this issue Nov 22, 2018
…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
Copy link

canhamd 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
Copy link
Contributor

petersalomonsen 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
Copy link
Member

gkalpak 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
Copy link
Contributor

@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 pushed a commit to sheikalthaf/angular that referenced this issue Jan 16, 2019
…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 pushed a commit to sheikalthaf/angular that referenced this issue Jan 16, 2019
…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 pushed a commit to sheikalthaf/angular that referenced this issue Mar 20, 2019
…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 pushed a commit to sheikalthaf/angular that referenced this issue Mar 20, 2019
…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 pushed a commit to sheikalthaf/angular that referenced this issue Mar 20, 2019
…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 as completed in e721c08 Mar 21, 2019
@skydever
Copy link
Author

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

@nsanzumuhire
Copy link

it still don't work!

@tplk
Copy link

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

@nsanzumuhire
Copy link

@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
Copy link
Contributor

@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));

@nsanzumuhire
Copy link

@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
Copy link
Contributor

petersalomonsen 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

@nsanzumuhire
Copy link

@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
Copy link
Contributor

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

wKoza pushed a commit to wKoza/angular that referenced this issue Apr 17, 2019
…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
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 14, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: service-worker Issues related to the @angular/service-worker package freq1: low type: bug/fix
Projects
None yet
Development

No branches or pull requests