Skip to content

Commit

Permalink
feat(service-worker): support multiple apps on different subpaths of …
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
sheikalthaf authored and wKoza committed Apr 17, 2019
1 parent f80edff commit 056b264
Show file tree
Hide file tree
Showing 7 changed files with 34 additions and 15 deletions.
2 changes: 1 addition & 1 deletion packages/service-worker/worker/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));
9 changes: 9 additions & 0 deletions packages/service-worker/worker/src/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down
4 changes: 2 additions & 2 deletions packages/service-worker/worker/src/app-version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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':
Expand All @@ -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
Expand Down
7 changes: 4 additions & 3 deletions packages/service-worker/worker/src/db-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
6 changes: 2 additions & 4 deletions packages/service-worker/worker/src/driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)));
}

Expand Down
18 changes: 13 additions & 5 deletions packages/service-worker/worker/test/happy_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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);
});

Expand Down Expand Up @@ -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);

Expand Down
3 changes: 3 additions & 0 deletions packages/service-worker/worker/testing/scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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> {
Expand Down

0 comments on commit 056b264

Please sign in to comment.