feat(service-worker): support multiple apps on different subpaths of a domain (#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 #21388

PR Close #27080
This commit is contained in:
Sheik Althaf
2019-03-20 23:29:15 +02:00
committed by Matias Niemelä
parent 37a154e4e6
commit e721c08c7f
7 changed files with 34 additions and 15 deletions

View File

@ -13,6 +13,15 @@
* from the global scope.
*/
export class Adapter {
readonly cacheNamePrefix: string;
constructor(scope: ServiceWorkerGlobalScope) {
// Suffixing `ngsw` with the baseHref to avoid clash of cache names
// for SWs with different scopes on the same domain.
const baseHref = new URL(scope.registration.scope).pathname;
this.cacheNamePrefix = 'ngsw:' + baseHref;
}
/**
* Wrapper around the `Request` constructor.
*/

View File

@ -72,7 +72,7 @@ export class AppVersion implements UpdateSource {
this.assetGroups = (manifest.assetGroups || []).map(config => {
// Every asset group has a cache that's prefixed by the manifest hash and the name of the
// group.
const prefix = `ngsw:${this.manifestHash}:assets`;
const prefix = `${adapter.cacheNamePrefix}:${this.manifestHash}:assets`;
// Check the caching mode, which determines when resources will be fetched/updated.
switch (config.installMode) {
case 'prefetch':
@ -89,7 +89,7 @@ export class AppVersion implements UpdateSource {
.map(
config => new DataGroup(
this.scope, this.adapter, config, this.database,
`ngsw:${config.version}:data`));
`${adapter.cacheNamePrefix}:${config.version}:data`));
// This keeps backwards compatibility with app versions without navigation urls.
// Fix: https://github.com/angular/angular/issues/27209

View File

@ -23,16 +23,17 @@ export class CacheDatabase implements Database {
if (this.tables.has(name)) {
this.tables.delete(name);
}
return this.scope.caches.delete(`ngsw:db:${name}`);
return this.scope.caches.delete(`${this.adapter.cacheNamePrefix}:db:${name}`);
}
list(): Promise<string[]> {
return this.scope.caches.keys().then(keys => keys.filter(key => key.startsWith('ngsw:db:')));
return this.scope.caches.keys().then(
keys => keys.filter(key => key.startsWith(`${this.adapter.cacheNamePrefix}:db:`)));
}
open(name: string): Promise<Table> {
if (!this.tables.has(name)) {
const table = this.scope.caches.open(`ngsw:db:${name}`)
const table = this.scope.caches.open(`${this.adapter.cacheNamePrefix}:db:${name}`)
.then(cache => new CacheTable(name, cache, this.adapter));
this.tables.set(name, table);
}

View File

@ -705,7 +705,7 @@ export class Driver implements Debuggable, UpdateSource {
private async deleteAllCaches(): Promise<void> {
await(await this.scope.caches.keys())
.filter(key => key.startsWith('ngsw:'))
.filter(key => key.startsWith(`${this.adapter.cacheNamePrefix}:`))
.reduce(async(previous, key) => {
await Promise.all([
previous,
@ -924,9 +924,7 @@ export class Driver implements Debuggable, UpdateSource {
*/
async cleanupOldSwCaches(): Promise<void> {
const cacheNames = await this.scope.caches.keys();
const oldSwCacheNames =
cacheNames.filter(name => /^ngsw:(?:active|staged|manifest:.+)$/.test(name));
const oldSwCacheNames = cacheNames.filter(name => /^ngsw:(?!\/)/.test(name));
await Promise.all(oldSwCacheNames.map(name => this.scope.caches.delete(name)));
}