Skip to content

Commit

Permalink
feat(service-worker): include Cache#match options in ngsw-config
Browse files Browse the repository at this point in the history
Previously it was impossible to provide options for Cache#match. As it
is sometimes necessary to override the default behavior the Cache#match
options can now be configured for assets and data groups.

closes angular#28443
  • Loading branch information
spike-rabbit committed Jan 7, 2020
1 parent 2dffe65 commit 8c3b8bc
Show file tree
Hide file tree
Showing 9 changed files with 80 additions and 10 deletions.
30 changes: 30 additions & 0 deletions packages/service-worker/config/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,21 @@
}
},
"additionalProperties": false
},
"cacheMatchOptions" : {
"type": "object",
"description": "Provide options that are passed to Cache#match",
"properties" : {
"ignoreMethod": {
"type": "boolean"
},
"ignoreSearch": {
"type": "boolean"
},
"ignoreVary": {
"type": "boolean"
}
}
}
},
"required": [
Expand Down Expand Up @@ -122,6 +137,21 @@
"maxAge"
],
"additionalProperties": false
},
"cacheMatchOptions" : {
"type": "object",
"description": "Provide options that are passed to Cache#match",
"properties" : {
"ignoreMethod": {
"type": "boolean"
},
"ignoreSearch": {
"type": "boolean"
},
"ignoreVary": {
"type": "boolean"
}
}
}
},
"required": [
Expand Down
2 changes: 2 additions & 0 deletions packages/service-worker/config/src/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export class Generator {
name: group.name,
installMode: group.installMode || 'prefetch',
updateMode: group.updateMode || group.installMode || 'prefetch',
cacheMatchOptions: group.cacheMatchOptions,
urls: matchedFiles.map(url => joinUrls(this.baseHref, url)),
patterns: (group.resources.urls || []).map(url => urlToRegex(url, this.baseHref, true)),
};
Expand All @@ -81,6 +82,7 @@ export class Generator {
patterns: group.urls.map(url => urlToRegex(url, this.baseHref, true)),
strategy: group.cacheConfig.strategy || 'performance',
maxSize: group.cacheConfig.maxSize,
cacheMatchOptions: group.cacheMatchOptions,
maxAge: parseDurationToMs(group.cacheConfig.maxAge),
timeoutMs: group.cacheConfig.timeout && parseDurationToMs(group.cacheConfig.timeout),
version: group.version !== undefined ? group.version : 1,
Expand Down
14 changes: 13 additions & 1 deletion packages/service-worker/config/src/in.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export interface AssetGroup {
installMode?: 'prefetch'|'lazy';
updateMode?: 'prefetch'|'lazy';
resources: {files?: Glob[]; urls?: Glob[];};
cacheMatchOptions: ClientMatchOptions;
}

/**
Expand All @@ -50,7 +51,18 @@ export interface DataGroup {
name: string;
urls: Glob[];
version?: number;
cacheConfig: {
cacheMatchOptions: ClientMatchOptions, cacheConfig: {
maxSize: number; maxAge: Duration; timeout?: Duration; strategy?: 'freshness' | 'performance';
};
}

/**
* Configuration for Cache#match. (copied lib DOM)
*
* @publicApi
*/
export interface ClientMatchOptions {
ignoreMethod?: boolean;
ignoreSearch?: boolean;
ignoreVary?: boolean;
}
13 changes: 7 additions & 6 deletions packages/service-worker/worker/src/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export abstract class AssetGroup {
async cacheStatus(url: string): Promise<UpdateCacheStatus> {
const cache = await this.cache;
const meta = await this.metadata;
const res = await cache.match(this.adapter.newRequest(url));
const res = await cache.match(this.adapter.newRequest(url), this.config.cacheMatchOptions);
if (res === undefined) {
return UpdateCacheStatus.NOT_CACHED;
}
Expand Down Expand Up @@ -120,7 +120,7 @@ export abstract class AssetGroup {

// Look for a cached response. If one exists, it can be used to resolve the fetch
// operation.
const cachedResponse = await cache.match(req);
const cachedResponse = await cache.match(req, this.config.cacheMatchOptions);
if (cachedResponse !== undefined) {
// A response has already been cached (which presumably matches the hash for this
// resource). Check whether it's safe to serve this resource from cache.
Expand Down Expand Up @@ -252,7 +252,7 @@ export abstract class AssetGroup {
const metaTable = await this.metadata;

// Lookup the response in the cache.
const response = await cache.match(this.adapter.newRequest(url));
const response = await cache.match(this.adapter.newRequest(url), this.config.cacheMatchOptions);
if (response === undefined) {
// It's not found, return null.
return null;
Expand Down Expand Up @@ -514,7 +514,7 @@ export class PrefetchAssetGroup extends AssetGroup {
const req = this.adapter.newRequest(url);

// First, check the cache to see if there is already a copy of this resource.
const alreadyCached = (await cache.match(req)) !== undefined;
const alreadyCached = (await cache.match(req, this.config.cacheMatchOptions)) !== undefined;

// If the resource is in the cache already, it can be skipped.
if (alreadyCached) {
Expand Down Expand Up @@ -550,7 +550,8 @@ export class PrefetchAssetGroup extends AssetGroup {

// It's possible that the resource in question is already cached. If so,
// continue to the next one.
const alreadyCached = (await cache.match(req) !== undefined);
const alreadyCached =
(await cache.match(req, this.config.cacheMatchOptions) !== undefined);
if (alreadyCached) {
return;
}
Expand Down Expand Up @@ -591,7 +592,7 @@ export class LazyAssetGroup extends AssetGroup {
const req = this.adapter.newRequest(url);

// First, check the cache to see if there is already a copy of this resource.
const alreadyCached = (await cache.match(req)) !== undefined;
const alreadyCached = (await cache.match(req, this.config.cacheMatchOptions)) !== undefined;

// If the resource is in the cache already, it can be skipped.
if (alreadyCached) {
Expand Down
2 changes: 1 addition & 1 deletion packages/service-worker/worker/src/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,7 @@ export class DataGroup {
Promise<{res: Response, age: number}|null> {
// Look for a response in the cache. If one exists, return it.
const cache = await this.cache;
let res = await cache.match(req);
let res = await cache.match(req, this.config.cacheMatchOptions);
if (res !== undefined) {
// A response was found in the cache, but its age is not yet known. Look it up.
try {
Expand Down
2 changes: 2 additions & 0 deletions packages/service-worker/worker/src/manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export interface AssetGroupConfig {
updateMode: 'prefetch'|'lazy';
urls: string[];
patterns: string[];
cacheMatchOptions?: CacheQueryOptions;
}

export interface DataGroupConfig {
Expand All @@ -38,6 +39,7 @@ export interface DataGroupConfig {
timeoutMs?: number;
refreshAheadMs?: number;
maxAge: number;
cacheMatchOptions?: CacheQueryOptions;
}

export function hashManifest(manifest: Manifest): ManifestHash {
Expand Down
11 changes: 10 additions & 1 deletion packages/service-worker/worker/test/prefetch_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {SwTestHarness, SwTestHarnessBuilder} from '../testing/scope';
}

const dist = new MockFileSystemBuilder()
.addFile('/foo.txt', 'this is foo')
.addFile('/foo.txt', 'this is foo', {Vary: 'Accept'})
.addFile('/bar.txt', 'this is bar')
.build();

Expand Down Expand Up @@ -83,6 +83,15 @@ import {SwTestHarness, SwTestHarnessBuilder} from '../testing/scope';
const err = await errorFrom(group.initializeFully());
expect(err.message).toContain('Hash mismatch');
});
it('respond with a resource even if a header listed in vary is present', async() => {
await group.initializeFully();
scope.updateServerState();
server.clearRequests();
const res1 = await group.handleFetch(
scope.newRequest('/foo.txt', {headers: {Accept: 'text/plain'}}), scope);
expect(await res1 !.text()).toEqual('this is foo');
server.assertNoOtherRequests();
});
});
})();

Expand Down
13 changes: 13 additions & 0 deletions packages/service-worker/worker/testing/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,19 @@ export class MockCache {
// TODO: cleanup typings. Typescript doesn't know this can resolve to undefined.
let res = this.cache.get(url);
if (res !== undefined) {
let varyHeaders = res.headers.get('Vary');
if (varyHeaders && (!options || !options.ignoreVary)) {
if (typeof request === 'string') {
return null !;
}
for (let varyHeader of varyHeaders.split(/, ?/)) {
let requestVary = request.headers.get(varyHeader);
let resVary = res.headers.get(varyHeader);
if (requestVary !== resVary) {
return null !;
}
}
}
res = res.clone();
}
return res !;
Expand Down
3 changes: 2 additions & 1 deletion packages/service-worker/worker/testing/mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {AssetGroupConfig, Manifest} from '../src/manifest';
import {Manifest} from '../src/manifest';
import {sha1} from '../src/sha1';

import {MockResponse} from './fetch';
Expand Down Expand Up @@ -203,6 +203,7 @@ export function tmpManifestSingleAssetGroup(fs: MockFileSystem): Manifest {
updateMode: 'prefetch',
urls: files,
patterns: [],
cacheMatchOptions: {ignoreVary: true}
},
],
navigationUrls: [], hashTable,
Expand Down

0 comments on commit 8c3b8bc

Please sign in to comment.