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

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
  • Loading branch information
sheikalthaf authored and gkalpak committed Jan 16, 2019
1 parent ce9d5fb commit 6e246c4
Show file tree
Hide file tree
Showing 7 changed files with 30 additions and 20 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));
6 changes: 6 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,12 @@
* from the global scope.
*/
export class Adapter {
cacheNamePrefix: string;
constructor(scope: ServiceWorkerGlobalScope) {
// Suffixing the baseHref with `ngsw` string to avoid clash of cache files
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 @@ -694,7 +694,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 @@ -913,9 +913,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
22 changes: 12 additions & 10 deletions packages/service-worker/worker/test/happy_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ const manifestOld: ManifestV5 = {
describe('Driver', () => {
let scope: SwTestHarness;
let driver: Driver;
let baseHref = '/';

beforeEach(() => {
server.reset();
Expand Down Expand Up @@ -306,7 +307,7 @@ const manifestOld: ManifestV5 = {
expect(await makeRequest(scope, '/foo.txt')).toEqual('this is foo');
await driver.initialized;
server.clearRequests();
expect(await makeRequest(scope, 'http://localhost/foo.txt')).toEqual('this is foo');
expect(await makeRequest(scope, `http://localhost${baseHref}foo.txt`)).toEqual('this is foo');
server.assertNoOtherRequests();
});

Expand Down Expand Up @@ -569,7 +570,8 @@ const manifestOld: ManifestV5 = {
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:${baseHref}:${manifestHash}:`));
expect(hasOriginalCaches).toEqual(true);

scope.clients.remove('default');
Expand All @@ -582,7 +584,7 @@ const manifestOld: ManifestV5 = {
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:${baseHref}:${manifestHash}:`));
expect(hasOriginalCaches).toEqual(false);
});

Expand Down Expand Up @@ -825,7 +827,7 @@ const manifestOld: ManifestV5 = {
});

async_it('redirects to index on a request to the origin URL request', async() => {
expect(await navRequest('http://localhost/')).toEqual('this is foo');
expect(await navRequest(`http://localhost${baseHref}`)).toEqual('this is foo');
server.assertNoOtherRequests();
});

Expand Down Expand Up @@ -911,7 +913,7 @@ const manifestOld: ManifestV5 = {
});

async_it('strips registration scope before checking `navigationUrls`', async() => {
expect(await navRequest('http://localhost/ignored/file1'))
expect(await navRequest(`http://localhost${baseHref}ignored/file1`))
.toBe('this is not handled by the SW');
serverUpdate.assertSawRequestFor('/ignored/file1');
});
Expand All @@ -922,11 +924,11 @@ const manifestOld: ManifestV5 = {
async_it('should delete the correct caches', async() => {
const oldSwCacheNames = ['ngsw:active', 'ngsw:staged', 'ngsw:manifest:a1b2c3:super:duper'];
const otherCacheNames = [
'ngsuu:active',
'not:ngsw:active',
'ngsw:staged:not',
'NgSw:StAgEd',
'ngsw:manifest',
`ngsuu:${baseHref}:active`,
`not:${baseHref}:ngsw:active`,
`ngsw:${baseHref}:staged:not`,
`NgSw:${baseHref}:StAgEd`,
`ngsw:${baseHref}:manifest`,
];
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 @@ -77,6 +77,7 @@ export class SwTestHarness implements ServiceWorkerGlobalScope, Adapter, Context
readonly clients = new MockClients();
private eventHandlers = new Map<string, Function>();
private skippedWaiting = true;
cacheNamePrefix: string;

private selfMessageQueue: any[] = [];
autoAdvanceTime = false;
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 6e246c4

Please sign in to comment.