Skip to content
Permalink
Browse files

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
  • Loading branch information...
sheikalthaf authored and matsko committed Mar 20, 2019
1 parent 37a154e commit e721c08c7f8136147d428fbf29cea2354fcc700c
@@ -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> {

0 comments on commit e721c08

Please sign in to comment.
You can’t perform that action at this time.