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:
parent
37a154e4e6
commit
e721c08c7f
|
@ -12,5 +12,5 @@ import {Driver} from './src/driver';
|
|||
|
||||
const scope = self as any as ServiceWorkerGlobalScope;
|
||||
|
||||
const adapter = new Adapter();
|
||||
const adapter = new Adapter(scope);
|
||||
const driver = new Driver(scope, adapter, new CacheDatabase(scope, adapter));
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
|
||||
|
|
|
@ -587,7 +587,7 @@ import {async_beforeEach, async_fit, async_it} from './async';
|
|||
serverUpdate.assertNoOtherRequests();
|
||||
|
||||
let keys = await scope.caches.keys();
|
||||
let hasOriginalCaches = keys.some(name => name.startsWith(`ngsw:${manifestHash}:`));
|
||||
let hasOriginalCaches = keys.some(name => name.startsWith(`ngsw:/:${manifestHash}:`));
|
||||
expect(hasOriginalCaches).toEqual(true);
|
||||
|
||||
scope.clients.remove('default');
|
||||
|
@ -600,7 +600,7 @@ import {async_beforeEach, async_fit, async_it} from './async';
|
|||
expect(await makeRequest(scope, '/foo.txt')).toEqual('this is foo v2');
|
||||
|
||||
keys = await scope.caches.keys();
|
||||
hasOriginalCaches = keys.some(name => name.startsWith(`ngsw:${manifestHash}:`));
|
||||
hasOriginalCaches = keys.some(name => name.startsWith(`ngsw:/:${manifestHash}:`));
|
||||
expect(hasOriginalCaches).toEqual(false);
|
||||
});
|
||||
|
||||
|
@ -938,13 +938,21 @@ import {async_beforeEach, async_fit, async_it} from './async';
|
|||
|
||||
describe('cleanupOldSwCaches()', () => {
|
||||
async_it('should delete the correct caches', async() => {
|
||||
const oldSwCacheNames = ['ngsw:active', 'ngsw:staged', 'ngsw:manifest:a1b2c3:super:duper'];
|
||||
const oldSwCacheNames = [
|
||||
// Example cache names from the beta versions of `@angular/service-worker`.
|
||||
'ngsw:active',
|
||||
'ngsw:staged',
|
||||
'ngsw:manifest:a1b2c3:super:duper',
|
||||
// Example cache names from the beta versions of `@angular/service-worker`.
|
||||
'ngsw:a1b2c3:assets:foo',
|
||||
'ngsw:db:a1b2c3:assets:bar',
|
||||
];
|
||||
const otherCacheNames = [
|
||||
'ngsuu:active',
|
||||
'not:ngsw:active',
|
||||
'ngsw:staged:not',
|
||||
'NgSw:StAgEd',
|
||||
'ngsw:manifest',
|
||||
'ngsw:/:active',
|
||||
'ngsw:/foo/:staged',
|
||||
];
|
||||
const allCacheNames = oldSwCacheNames.concat(otherCacheNames);
|
||||
|
||||
|
|
|
@ -74,6 +74,7 @@ export class MockClients implements Clients {
|
|||
}
|
||||
|
||||
export class SwTestHarness implements ServiceWorkerGlobalScope, Adapter, Context {
|
||||
readonly cacheNamePrefix: string;
|
||||
readonly clients = new MockClients();
|
||||
private eventHandlers = new Map<string, Function>();
|
||||
private skippedWaiting = true;
|
||||
|
@ -115,6 +116,8 @@ export class SwTestHarness implements ServiceWorkerGlobalScope, Adapter, Context
|
|||
|
||||
constructor(private server: MockServerState, readonly caches: MockCacheStorage) {
|
||||
this.time = Date.now();
|
||||
const baseHref = new URL(this.registration.scope).pathname;
|
||||
this.cacheNamePrefix = 'ngsw:' + baseHref;
|
||||
}
|
||||
|
||||
async resolveSelfMessages(): Promise<void> {
|
||||
|
|
Loading…
Reference in New Issue