refactor(service-worker): simplify accessing `CacheStorage` throughout the ServiceWorker (#42622)
This commit simplifies/systemizes accessing the `CacheStorage` through a wrapper, with the following benefits: - Ensuring a consistent cache name prefix is used for all caches (without having to repeat the prefix in different places). - Allowing referring to caches using their name without the common cache name prefix. - Exposing the cache name on cache instances, which for example makes it easier to delete caches without having to keep track of the name used to create them. PR Close #42622
This commit is contained in:
parent
73b0275dc2
commit
356dd2107b
|
@ -87,7 +87,7 @@ describe('ngsw + companion lib', () => {
|
|||
mock = new MockServiceWorkerContainer();
|
||||
comm = new NgswCommChannel(mock as any);
|
||||
scope = new SwTestHarnessBuilder().withServerState(server).build();
|
||||
driver = new Driver(scope, scope, new CacheDatabase(scope, scope));
|
||||
driver = new Driver(scope, scope, new CacheDatabase(scope));
|
||||
|
||||
scope.clients.add('default');
|
||||
scope.clients.getMock('default')!.queue.subscribe(msg => {
|
||||
|
|
|
@ -10,7 +10,7 @@ import {Adapter} from './src/adapter';
|
|||
import {CacheDatabase} from './src/db-cache';
|
||||
import {Driver} from './src/driver';
|
||||
|
||||
const scope = self as any as ServiceWorkerGlobalScope;
|
||||
const scope = self as unknown as ServiceWorkerGlobalScope;
|
||||
|
||||
const adapter = new Adapter(scope.registration.scope);
|
||||
const driver = new Driver(scope, adapter, new CacheDatabase(scope, adapter));
|
||||
const adapter = new Adapter(scope.registration.scope, self.caches);
|
||||
new Driver(scope, adapter, new CacheDatabase(adapter));
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
*/
|
||||
|
||||
import {NormalizedUrl} from './api';
|
||||
import {NamedCacheStorage} from './named-cache-storage';
|
||||
|
||||
|
||||
/**
|
||||
|
@ -15,20 +16,20 @@ import {NormalizedUrl} from './api';
|
|||
* Mostly, this is used to mock out identifiers which are otherwise read
|
||||
* from the global scope.
|
||||
*/
|
||||
export class Adapter {
|
||||
readonly cacheNamePrefix: string;
|
||||
export class Adapter<T extends CacheStorage = CacheStorage> {
|
||||
readonly caches: NamedCacheStorage<T>;
|
||||
private readonly origin: string;
|
||||
|
||||
constructor(protected readonly scopeUrl: string) {
|
||||
constructor(protected readonly scopeUrl: string, caches: T) {
|
||||
const parsedScopeUrl = this.parseUrl(this.scopeUrl);
|
||||
|
||||
// Determine the origin from the registration scope. This is used to differentiate between
|
||||
// relative and absolute URLs.
|
||||
this.origin = parsedScopeUrl.origin;
|
||||
|
||||
// Suffixing `ngsw` with the baseHref to avoid clash of cache names for SWs with different
|
||||
// scopes on the same domain.
|
||||
this.cacheNamePrefix = 'ngsw:' + parsedScopeUrl.path;
|
||||
// Use the baseHref in the cache name prefix to avoid clash of cache names for SWs with
|
||||
// different scopes on the same domain.
|
||||
this.caches = new NamedCacheStorage(caches, `ngsw:${parsedScopeUrl.path}`);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -12,6 +12,7 @@ import {Database, Table} from './database';
|
|||
import {errorToString, SwCriticalError, SwUnrecoverableStateError} from './error';
|
||||
import {IdleScheduler} from './idle';
|
||||
import {AssetGroupConfig} from './manifest';
|
||||
import {NamedCache} from './named-cache-storage';
|
||||
import {sha1Binary} from './sha1';
|
||||
|
||||
/**
|
||||
|
@ -40,7 +41,7 @@ export abstract class AssetGroup {
|
|||
* A Promise which resolves to the `Cache` used to back this asset group. This
|
||||
* is opened from the constructor.
|
||||
*/
|
||||
protected cache: Promise<Cache>;
|
||||
protected cache: Promise<NamedCache>;
|
||||
|
||||
/**
|
||||
* Group name from the configuration.
|
||||
|
@ -55,8 +56,7 @@ export abstract class AssetGroup {
|
|||
constructor(
|
||||
protected scope: ServiceWorkerGlobalScope, protected adapter: Adapter,
|
||||
protected idle: IdleScheduler, protected config: AssetGroupConfig,
|
||||
protected hashes: Map<string, string>, protected db: Database,
|
||||
protected cacheNamePrefix: string) {
|
||||
protected hashes: Map<string, string>, protected db: Database, cacheNamePrefix: string) {
|
||||
this.name = config.name;
|
||||
|
||||
// Normalize the config's URLs to take the ServiceWorker's scope into account.
|
||||
|
@ -67,8 +67,7 @@ export abstract class AssetGroup {
|
|||
|
||||
// This is the primary cache, which holds all of the cached requests for this group. If a
|
||||
// resource isn't in this cache, it hasn't been fetched yet.
|
||||
this.cache =
|
||||
scope.caches.open(`${adapter.cacheNamePrefix}:${cacheNamePrefix}:${config.name}:cache`);
|
||||
this.cache = adapter.caches.open(`${cacheNamePrefix}:${config.name}:cache`);
|
||||
|
||||
// This is the metadata table, which holds specific information for each cached URL, such as
|
||||
// the timestamp of when it was added to the cache.
|
||||
|
@ -104,9 +103,10 @@ export abstract class AssetGroup {
|
|||
* Clean up all the cached data for this group.
|
||||
*/
|
||||
async cleanup(): Promise<void> {
|
||||
await this.scope.caches.delete(
|
||||
`${this.adapter.cacheNamePrefix}:${this.cacheNamePrefix}:${this.config.name}:cache`);
|
||||
await this.db.delete(`${this.cacheNamePrefix}:${this.config.name}:meta`);
|
||||
await Promise.all([
|
||||
this.cache.then(cache => this.adapter.caches.delete(cache.name)),
|
||||
this.metadata.then(metadata => this.db.delete(metadata.name)),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -138,11 +138,9 @@ export abstract class AssetGroup {
|
|||
// This resource has no hash, and yet exists in the cache. Check how old this request is
|
||||
// to make sure it's still usable.
|
||||
if (await this.needToRevalidate(req, cachedResponse)) {
|
||||
this.idle.schedule(
|
||||
`revalidate(${this.cacheNamePrefix}, ${this.config.name}): ${req.url}`,
|
||||
async () => {
|
||||
await this.fetchAndCacheOnce(req);
|
||||
});
|
||||
this.idle.schedule(`revalidate(${cache.name}): ${req.url}`, async () => {
|
||||
await this.fetchAndCacheOnce(req);
|
||||
});
|
||||
}
|
||||
|
||||
// In either case (revalidation or not), the cached response must be good.
|
||||
|
|
|
@ -10,6 +10,7 @@ import {Adapter, Context} from './adapter';
|
|||
import {Database, Table} from './database';
|
||||
import {DebugHandler} from './debug';
|
||||
import {DataGroupConfig} from './manifest';
|
||||
import {NamedCache} from './named-cache-storage';
|
||||
|
||||
/**
|
||||
* A metadata record of how old a particular cached resource is.
|
||||
|
@ -227,7 +228,7 @@ export class DataGroup {
|
|||
/**
|
||||
* The `Cache` instance in which resources belonging to this group are cached.
|
||||
*/
|
||||
private readonly cache: Promise<Cache>;
|
||||
private readonly cache: Promise<NamedCache>;
|
||||
|
||||
/**
|
||||
* Tracks the LRU state of resources in this cache.
|
||||
|
@ -247,10 +248,9 @@ export class DataGroup {
|
|||
constructor(
|
||||
private scope: ServiceWorkerGlobalScope, private adapter: Adapter,
|
||||
private config: DataGroupConfig, private db: Database, private debugHandler: DebugHandler,
|
||||
private cacheNamePrefix: string) {
|
||||
cacheNamePrefix: string) {
|
||||
this.patterns = config.patterns.map(pattern => new RegExp(pattern));
|
||||
this.cache =
|
||||
scope.caches.open(`${adapter.cacheNamePrefix}:${cacheNamePrefix}:${config.name}:cache`);
|
||||
this.cache = adapter.caches.open(`${cacheNamePrefix}:${config.name}:cache`);
|
||||
this.lruTable = this.db.open(`${cacheNamePrefix}:${config.name}:lru`, config.cacheQueryOptions);
|
||||
this.ageTable = this.db.open(`${cacheNamePrefix}:${config.name}:age`, config.cacheQueryOptions);
|
||||
}
|
||||
|
@ -549,10 +549,9 @@ export class DataGroup {
|
|||
async cleanup(): Promise<void> {
|
||||
// Remove both the cache and the database entries which track LRU stats.
|
||||
await Promise.all([
|
||||
this.scope.caches.delete(
|
||||
`${this.adapter.cacheNamePrefix}:${this.cacheNamePrefix}:${this.config.name}:cache`),
|
||||
this.db.delete(`${this.cacheNamePrefix}:${this.config.name}:age`),
|
||||
this.db.delete(`${this.cacheNamePrefix}:${this.config.name}:lru`),
|
||||
this.cache.then(cache => this.adapter.caches.delete(cache.name)),
|
||||
this.ageTable.then(table => this.db.delete(table.name)),
|
||||
this.lruTable.then(table => this.db.delete(table.name)),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,11 @@
|
|||
* An abstract table, with the ability to read/write objects stored under keys.
|
||||
*/
|
||||
export interface Table {
|
||||
/**
|
||||
* The name of this table in the database.
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* Delete a key from the table.
|
||||
*/
|
||||
|
|
|
@ -15,21 +15,21 @@ import {Database, NotFound, Table} from './database';
|
|||
* state within mock `Response` objects.
|
||||
*/
|
||||
export class CacheDatabase implements Database {
|
||||
private cacheNamePrefix = `${this.adapter.cacheNamePrefix}:db`;
|
||||
private cacheNamePrefix = 'db';
|
||||
private tables = new Map<string, CacheTable>();
|
||||
|
||||
constructor(private scope: ServiceWorkerGlobalScope, private adapter: Adapter) {}
|
||||
constructor(private adapter: Adapter) {}
|
||||
|
||||
'delete'(name: string): Promise<boolean> {
|
||||
if (this.tables.has(name)) {
|
||||
this.tables.delete(name);
|
||||
}
|
||||
return this.scope.caches.delete(`${this.cacheNamePrefix}:${name}`);
|
||||
return this.adapter.caches.delete(`${this.cacheNamePrefix}:${name}`);
|
||||
}
|
||||
|
||||
async list(): Promise<string[]> {
|
||||
const prefix = `${this.cacheNamePrefix}:`;
|
||||
const allCacheNames = await this.scope.caches.keys();
|
||||
const allCacheNames = await this.adapter.caches.keys();
|
||||
const dbCacheNames = allCacheNames.filter(name => name.startsWith(prefix));
|
||||
|
||||
// Return the un-prefixed table names, so they can be used with other `CacheDatabase` methods
|
||||
|
@ -39,7 +39,7 @@ export class CacheDatabase implements Database {
|
|||
|
||||
async open(name: string, cacheQueryOptions?: CacheQueryOptions): Promise<Table> {
|
||||
if (!this.tables.has(name)) {
|
||||
const cache = await this.scope.caches.open(`${this.cacheNamePrefix}:${name}`);
|
||||
const cache = await this.adapter.caches.open(`${this.cacheNamePrefix}:${name}`);
|
||||
const table = new CacheTable(name, cache, this.adapter, cacheQueryOptions);
|
||||
this.tables.set(name, table);
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ export class CacheDatabase implements Database {
|
|||
*/
|
||||
export class CacheTable implements Table {
|
||||
constructor(
|
||||
readonly table: string, private cache: Cache, private adapter: Adapter,
|
||||
readonly name: string, private cache: Cache, private adapter: Adapter,
|
||||
private cacheQueryOptions?: CacheQueryOptions) {}
|
||||
|
||||
private request(key: string): Request {
|
||||
|
@ -70,7 +70,7 @@ export class CacheTable implements Table {
|
|||
read(key: string): Promise<any> {
|
||||
return this.cache.match(this.request(key), this.cacheQueryOptions).then(res => {
|
||||
if (res === undefined) {
|
||||
return Promise.reject(new NotFound(this.table, key));
|
||||
return Promise.reject(new NotFound(this.name, key));
|
||||
}
|
||||
return res.json();
|
||||
});
|
||||
|
|
|
@ -760,11 +760,8 @@ export class Driver implements Debuggable, UpdateSource {
|
|||
}
|
||||
|
||||
private async deleteAllCaches(): Promise<void> {
|
||||
const cacheNames = await this.scope.caches.keys();
|
||||
const ownCacheNames =
|
||||
cacheNames.filter(name => name.startsWith(`${this.adapter.cacheNamePrefix}:`));
|
||||
|
||||
await Promise.all(ownCacheNames.map(name => this.scope.caches.delete(name)));
|
||||
const cacheNames = await this.adapter.caches.keys();
|
||||
await Promise.all(cacheNames.map(name => this.adapter.caches.delete(name)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -988,9 +985,13 @@ export class Driver implements Debuggable, UpdateSource {
|
|||
* (Since at this point the SW has claimed all clients, it is safe to remove those caches.)
|
||||
*/
|
||||
async cleanupOldSwCaches(): Promise<void> {
|
||||
const cacheNames = await this.scope.caches.keys();
|
||||
// This is an exceptional case, where we need to interact with caches that would not be
|
||||
// generated by this ServiceWorker (but by old versions of it). Use the native `CacheStorage`
|
||||
// directly.
|
||||
const caches = this.adapter.caches.original;
|
||||
const cacheNames = await caches.keys();
|
||||
const oldSwCacheNames = cacheNames.filter(name => /^ngsw:(?!\/)/.test(name));
|
||||
await Promise.all(oldSwCacheNames.map(name => this.scope.caches.delete(name)));
|
||||
await Promise.all(oldSwCacheNames.map(name => caches.delete(name)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
export interface NamedCache extends Cache {
|
||||
readonly name: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper around `CacheStorage` to allow interacting with caches more easily and consistently by:
|
||||
* - Adding a `name` property to all opened caches, which can be used to easily perform other
|
||||
* operations that require the cache name.
|
||||
* - Name-spacing cache names to avoid conflicts with other caches on the same domain.
|
||||
*/
|
||||
export class NamedCacheStorage<T extends CacheStorage> implements CacheStorage {
|
||||
constructor(readonly original: T, private cacheNamePrefix: string) {}
|
||||
|
||||
delete(cacheName: string): Promise<boolean> {
|
||||
return this.original.delete(`${this.cacheNamePrefix}:${cacheName}`);
|
||||
}
|
||||
|
||||
has(cacheName: string): Promise<boolean> {
|
||||
return this.original.has(`${this.cacheNamePrefix}:${cacheName}`);
|
||||
}
|
||||
|
||||
async keys(): Promise<string[]> {
|
||||
const prefix = `${this.cacheNamePrefix}:`;
|
||||
const allCacheNames = await this.original.keys();
|
||||
const ownCacheNames = allCacheNames.filter(name => name.startsWith(prefix));
|
||||
return ownCacheNames.map(name => name.slice(prefix.length));
|
||||
}
|
||||
|
||||
match(request: RequestInfo, options?: MultiCacheQueryOptions): Promise<Response|undefined> {
|
||||
return this.original.match(request, options);
|
||||
}
|
||||
|
||||
async open(cacheName: string): Promise<NamedCache> {
|
||||
const cache = await this.original.open(`${this.cacheNamePrefix}:${cacheName}`);
|
||||
return Object.assign(cache, {name: cacheName});
|
||||
}
|
||||
}
|
|
@ -111,7 +111,9 @@ interface ExtendableMessageEvent extends ExtendableEvent {
|
|||
// ServiceWorkerGlobalScope
|
||||
|
||||
interface ServiceWorkerGlobalScope {
|
||||
caches: CacheStorage;
|
||||
// Intentionally does not include a `caches` property to disallow accessing `CacheStorage` APIs
|
||||
// directly. All interactions with `CacheStorage` should go through a `NamedCacheStorage` instance
|
||||
// (exposed by the `Adapter`).
|
||||
clients: Clients;
|
||||
registration: ServiceWorkerRegistration;
|
||||
|
||||
|
|
|
@ -126,7 +126,7 @@ describe('data cache', () => {
|
|||
let driver: Driver;
|
||||
beforeEach(async () => {
|
||||
scope = new SwTestHarnessBuilder().withServerState(server).build();
|
||||
driver = new Driver(scope, scope, new CacheDatabase(scope, scope));
|
||||
driver = new Driver(scope, scope, new CacheDatabase(scope));
|
||||
|
||||
// Initialize.
|
||||
expect(await makeRequest(scope, '/foo.txt')).toEqual('this is foo');
|
||||
|
@ -144,7 +144,7 @@ describe('data cache', () => {
|
|||
describe('in performance mode', () => {
|
||||
it('names the caches correctly', async () => {
|
||||
expect(await makeRequest(scope, '/api/test')).toEqual('version 1');
|
||||
const keys = await scope.caches.keys();
|
||||
const keys = await scope.caches.original.keys();
|
||||
expect(keys.every(key => key.startsWith('ngsw:/:'))).toEqual(true);
|
||||
});
|
||||
|
||||
|
|
|
@ -304,7 +304,7 @@ describe('Driver', () => {
|
|||
brokenServer.reset();
|
||||
|
||||
scope = new SwTestHarnessBuilder().withServerState(server).build();
|
||||
driver = new Driver(scope, scope, new CacheDatabase(scope, scope));
|
||||
driver = new Driver(scope, scope, new CacheDatabase(scope));
|
||||
});
|
||||
|
||||
it('activates without waiting', async () => {
|
||||
|
@ -553,10 +553,10 @@ describe('Driver', () => {
|
|||
await driver.initialized;
|
||||
|
||||
scope = new SwTestHarnessBuilder()
|
||||
.withCacheState(scope.caches.dehydrate())
|
||||
.withCacheState(scope.caches.original.dehydrate())
|
||||
.withServerState(serverUpdate)
|
||||
.build();
|
||||
driver = new Driver(scope, scope, new CacheDatabase(scope, scope));
|
||||
driver = new Driver(scope, scope, new CacheDatabase(scope));
|
||||
expect(await makeRequest(scope, '/foo.txt')).toEqual('this is foo');
|
||||
await driver.initialized;
|
||||
serverUpdate.assertNoOtherRequests();
|
||||
|
@ -609,10 +609,10 @@ describe('Driver', () => {
|
|||
serverUpdate.clearRequests();
|
||||
|
||||
scope = new SwTestHarnessBuilder()
|
||||
.withCacheState(scope.caches.dehydrate())
|
||||
.withCacheState(scope.caches.original.dehydrate())
|
||||
.withServerState(serverUpdate)
|
||||
.build();
|
||||
driver = new Driver(scope, scope, new CacheDatabase(scope, scope));
|
||||
driver = new Driver(scope, scope, new CacheDatabase(scope));
|
||||
|
||||
expect(await makeRequest(scope, '/foo.txt')).toEqual('this is foo');
|
||||
expect(await makeRequest(scope, '/foo.txt', 'new')).toEqual('this is foo v2');
|
||||
|
@ -671,16 +671,16 @@ describe('Driver', () => {
|
|||
await driver.initialized;
|
||||
|
||||
scope = new SwTestHarnessBuilder()
|
||||
.withCacheState(scope.caches.dehydrate())
|
||||
.withCacheState(scope.caches.original.dehydrate())
|
||||
.withServerState(serverUpdate)
|
||||
.build();
|
||||
driver = new Driver(scope, scope, new CacheDatabase(scope, scope));
|
||||
driver = new Driver(scope, scope, new CacheDatabase(scope));
|
||||
expect(await makeRequest(scope, '/foo.txt')).toEqual('this is foo');
|
||||
await driver.initialized;
|
||||
serverUpdate.assertNoOtherRequests();
|
||||
|
||||
let keys = await scope.caches.keys();
|
||||
let hasOriginalCaches = keys.some(name => name.startsWith(`ngsw:/:${manifestHash}:`));
|
||||
let hasOriginalCaches = keys.some(name => name.startsWith(`${manifestHash}:`));
|
||||
expect(hasOriginalCaches).toEqual(true);
|
||||
|
||||
scope.clients.remove('default');
|
||||
|
@ -689,11 +689,11 @@ describe('Driver', () => {
|
|||
await driver.idle.empty;
|
||||
serverUpdate.clearRequests();
|
||||
|
||||
driver = new Driver(scope, scope, new CacheDatabase(scope, scope));
|
||||
driver = new Driver(scope, scope, new CacheDatabase(scope));
|
||||
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(`${manifestHash}:`));
|
||||
expect(hasOriginalCaches).toEqual(false);
|
||||
});
|
||||
|
||||
|
@ -1255,7 +1255,7 @@ describe('Driver', () => {
|
|||
it('should show debug info when the scope is not root', async () => {
|
||||
const newScope =
|
||||
new SwTestHarnessBuilder('http://localhost/foo/bar/').withServerState(server).build();
|
||||
new Driver(newScope, newScope, new CacheDatabase(newScope, newScope));
|
||||
new Driver(newScope, newScope, new CacheDatabase(newScope));
|
||||
|
||||
expect(await makeRequest(newScope, '/foo/bar/ngsw/state'))
|
||||
.toMatch(/^NGSW Debug Info:\n\nDriver version: .+\nDriver state: NORMAL/);
|
||||
|
@ -1324,7 +1324,8 @@ describe('Driver', () => {
|
|||
});
|
||||
|
||||
const getClientAssignments = async (sw: SwTestHarness, baseHref: string) => {
|
||||
const cache = await sw.caches.open(`ngsw:${baseHref}:db:control`) as unknown as MockCache;
|
||||
const cache =
|
||||
await sw.caches.original.open(`ngsw:${baseHref}:db:control`) as unknown as MockCache;
|
||||
const dehydrated = cache.dehydrate();
|
||||
return JSON.parse(dehydrated['/assignments'].body!) as any;
|
||||
};
|
||||
|
@ -1344,7 +1345,7 @@ describe('Driver', () => {
|
|||
.withCacheState(initialCacheState)
|
||||
.withServerState(serverState)
|
||||
.build();
|
||||
const newDriver = new Driver(newScope, newScope, new CacheDatabase(newScope, newScope));
|
||||
const newDriver = new Driver(newScope, newScope, new CacheDatabase(newScope));
|
||||
|
||||
await makeRequest(newScope, newManifest.index, baseHref.replace(/\//g, '_'));
|
||||
await newDriver.initialized;
|
||||
|
@ -1359,14 +1360,14 @@ describe('Driver', () => {
|
|||
it('includes the SW scope in all cache names', async () => {
|
||||
// SW with scope `/`.
|
||||
const [rootScope, rootManifestHash] = await initializeSwFor('/');
|
||||
const cacheNames = await rootScope.caches.keys();
|
||||
const cacheNames = await rootScope.caches.original.keys();
|
||||
|
||||
expect(cacheNames).toEqual(cacheKeysFor('/', rootManifestHash));
|
||||
expect(cacheNames.every(name => name.includes('/'))).toBe(true);
|
||||
|
||||
// SW with scope `/foo/`.
|
||||
const [fooScope, fooManifestHash] = await initializeSwFor('/foo/');
|
||||
const fooCacheNames = await fooScope.caches.keys();
|
||||
const fooCacheNames = await fooScope.caches.original.keys();
|
||||
|
||||
expect(fooCacheNames).toEqual(cacheKeysFor('/foo/', fooManifestHash));
|
||||
expect(fooCacheNames.every(name => name.includes('/foo/'))).toBe(true);
|
||||
|
@ -1381,8 +1382,8 @@ describe('Driver', () => {
|
|||
|
||||
// Add new SW with different scope.
|
||||
const [barScope, barManifestHash] =
|
||||
await initializeSwFor('/bar/', await fooScope.caches.dehydrate());
|
||||
const barCacheNames = await barScope.caches.keys();
|
||||
await initializeSwFor('/bar/', await fooScope.caches.original.dehydrate());
|
||||
const barCacheNames = await barScope.caches.original.keys();
|
||||
const barAssignments = await getClientAssignments(barScope, '/bar/');
|
||||
|
||||
expect(barAssignments).toEqual({_bar_: barManifestHash});
|
||||
|
@ -1412,7 +1413,7 @@ describe('Driver', () => {
|
|||
|
||||
// Add new SW with same scope.
|
||||
const [fooScope2, fooManifestHash2] =
|
||||
await initializeSwFor('/foo/', await fooScope.caches.dehydrate());
|
||||
await initializeSwFor('/foo/', await fooScope.caches.original.dehydrate());
|
||||
|
||||
// Update client `_foo_` but not client `_bar_`.
|
||||
await fooScope2.handleMessage({action: 'CHECK_FOR_UPDATES'}, '_foo_');
|
||||
|
@ -1490,9 +1491,9 @@ describe('Driver', () => {
|
|||
expect(await makeRequest(scope, '/unhashed/a.txt')).toEqual('this is unhashed');
|
||||
server.clearRequests();
|
||||
|
||||
const state = scope.caches.dehydrate();
|
||||
const state = scope.caches.original.dehydrate();
|
||||
scope = new SwTestHarnessBuilder().withCacheState(state).withServerState(server).build();
|
||||
driver = new Driver(scope, scope, new CacheDatabase(scope, scope));
|
||||
driver = new Driver(scope, scope, new CacheDatabase(scope));
|
||||
expect(await makeRequest(scope, '/foo.txt')).toEqual('this is foo');
|
||||
await driver.initialized;
|
||||
server.assertNoRequestFor('/unhashed/a.txt');
|
||||
|
@ -1514,10 +1515,10 @@ describe('Driver', () => {
|
|||
server.clearRequests();
|
||||
|
||||
scope = new SwTestHarnessBuilder()
|
||||
.withCacheState(scope.caches.dehydrate())
|
||||
.withCacheState(scope.caches.original.dehydrate())
|
||||
.withServerState(serverUpdate)
|
||||
.build();
|
||||
driver = new Driver(scope, scope, new CacheDatabase(scope, scope));
|
||||
driver = new Driver(scope, scope, new CacheDatabase(scope));
|
||||
expect(await makeRequest(scope, '/foo.txt')).toEqual('this is foo');
|
||||
await driver.initialized;
|
||||
|
||||
|
@ -1709,7 +1710,7 @@ describe('Driver', () => {
|
|||
scope = new SwTestHarnessBuilder('http://localhost/base/href/')
|
||||
.withServerState(serverWithBaseHref)
|
||||
.build();
|
||||
driver = new Driver(scope, scope, new CacheDatabase(scope, scope));
|
||||
driver = new Driver(scope, scope, new CacheDatabase(scope));
|
||||
});
|
||||
|
||||
it('initializes prefetched content correctly, after a request kicks it off', async () => {
|
||||
|
@ -1820,21 +1821,21 @@ describe('Driver', () => {
|
|||
];
|
||||
const allCacheNames = oldSwCacheNames.concat(otherCacheNames);
|
||||
|
||||
await Promise.all(allCacheNames.map(name => scope.caches.open(name)));
|
||||
expect(await scope.caches.keys()).toEqual(allCacheNames);
|
||||
await Promise.all(allCacheNames.map(name => scope.caches.original.open(name)));
|
||||
expect(await scope.caches.original.keys()).toEqual(allCacheNames);
|
||||
|
||||
await driver.cleanupOldSwCaches();
|
||||
expect(await scope.caches.keys()).toEqual(otherCacheNames);
|
||||
expect(await scope.caches.original.keys()).toEqual(otherCacheNames);
|
||||
});
|
||||
|
||||
it('should delete other caches even if deleting one of them fails', async () => {
|
||||
const oldSwCacheNames = ['ngsw:active', 'ngsw:staged', 'ngsw:manifest:a1b2c3:super:duper'];
|
||||
const deleteSpy =
|
||||
spyOn(scope.caches, 'delete')
|
||||
spyOn(scope.caches.original, 'delete')
|
||||
.and.callFake(
|
||||
(cacheName: string) => Promise.reject(`Failed to delete cache '${cacheName}'.`));
|
||||
|
||||
await Promise.all(oldSwCacheNames.map(name => scope.caches.open(name)));
|
||||
await Promise.all(oldSwCacheNames.map(name => scope.caches.original.open(name)));
|
||||
const error = await driver.cleanupOldSwCaches().catch(err => err);
|
||||
|
||||
expect(error).toBe('Failed to delete cache \'ngsw:active\'.');
|
||||
|
@ -1847,7 +1848,7 @@ describe('Driver', () => {
|
|||
it('does not crash with bad index hash', async () => {
|
||||
scope = new SwTestHarnessBuilder().withServerState(brokenServer).build();
|
||||
(scope.registration as any).scope = 'http://site.com';
|
||||
driver = new Driver(scope, scope, new CacheDatabase(scope, scope));
|
||||
driver = new Driver(scope, scope, new CacheDatabase(scope));
|
||||
|
||||
expect(await makeRequest(scope, '/foo.txt')).toEqual('this is foo (broken)');
|
||||
});
|
||||
|
@ -1858,10 +1859,10 @@ describe('Driver', () => {
|
|||
server.clearRequests();
|
||||
|
||||
scope = new SwTestHarnessBuilder()
|
||||
.withCacheState(scope.caches.dehydrate())
|
||||
.withCacheState(scope.caches.original.dehydrate())
|
||||
.withServerState(brokenServer)
|
||||
.build();
|
||||
driver = new Driver(scope, scope, new CacheDatabase(scope, scope));
|
||||
driver = new Driver(scope, scope, new CacheDatabase(scope));
|
||||
await driver.checkForUpdate();
|
||||
|
||||
scope.advance(12000);
|
||||
|
@ -2018,7 +2019,7 @@ describe('Driver', () => {
|
|||
expect(driver.state).toBe(DriverReadyState.NORMAL);
|
||||
|
||||
// Ensure the data has been stored in the DB.
|
||||
const db: MockCache = await scope.caches.open('ngsw:/:db:control') as any;
|
||||
const db: MockCache = await scope.caches.open('db:control') as any;
|
||||
const getLatestHashFromDb = async () => (await (await db.match('/latest')).json()).latest;
|
||||
expect(await getLatestHashFromDb()).toBe(manifestHash);
|
||||
|
||||
|
@ -2145,7 +2146,7 @@ describe('Driver', () => {
|
|||
|
||||
// Create initial server state and initialize the SW.
|
||||
scope = new SwTestHarnessBuilder().withServerState(serverState1).build();
|
||||
driver = new Driver(scope, scope, new CacheDatabase(scope, scope));
|
||||
driver = new Driver(scope, scope, new CacheDatabase(scope));
|
||||
|
||||
// Verify that all three clients are able to make the request.
|
||||
expect(await makeRequest(scope, '/foo.hash.js', 'client1')).toBe('console.log("FOO");');
|
||||
|
@ -2223,7 +2224,7 @@ describe('Driver', () => {
|
|||
|
||||
// Create initial server state and initialize the SW.
|
||||
scope = new SwTestHarnessBuilder().withServerState(originalServer).build();
|
||||
driver = new Driver(scope, scope, new CacheDatabase(scope, scope));
|
||||
driver = new Driver(scope, scope, new CacheDatabase(scope));
|
||||
|
||||
expect(await makeRequest(scope, '/foo.hash.js')).toBe('console.log("FOO");');
|
||||
await driver.initialized;
|
||||
|
@ -2236,10 +2237,10 @@ describe('Driver', () => {
|
|||
// Update the server state to emulate deploying a new version (where `foo.hash.js` does not
|
||||
// exist any more). Keep the cache though.
|
||||
scope = new SwTestHarnessBuilder()
|
||||
.withCacheState(scope.caches.dehydrate())
|
||||
.withCacheState(scope.caches.original.dehydrate())
|
||||
.withServerState(updatedServer)
|
||||
.build();
|
||||
driver = new Driver(scope, scope, new CacheDatabase(scope, scope));
|
||||
driver = new Driver(scope, scope, new CacheDatabase(scope));
|
||||
|
||||
// The SW is still able to serve `foo.hash.js` from the cache.
|
||||
expect(await makeRequest(scope, '/foo.hash.js')).toBe('console.log("FOO");');
|
||||
|
@ -2267,7 +2268,7 @@ describe('Driver', () => {
|
|||
.build();
|
||||
|
||||
scope = new SwTestHarnessBuilder().withServerState(serverV5).build();
|
||||
driver = new Driver(scope, scope, new CacheDatabase(scope, scope));
|
||||
driver = new Driver(scope, scope, new CacheDatabase(scope));
|
||||
});
|
||||
|
||||
// Test this bug: https://github.com/angular/angular/issues/27209
|
||||
|
@ -2321,7 +2322,7 @@ describe('Driver', () => {
|
|||
const freshnessManifest: Manifest = {...manifest, navigationRequestStrategy: 'freshness'};
|
||||
const server = serverBuilderBase.withManifest(freshnessManifest).build();
|
||||
const scope = new SwTestHarnessBuilder().withServerState(server).build();
|
||||
const driver = new Driver(scope, scope, new CacheDatabase(scope, scope));
|
||||
const driver = new Driver(scope, scope, new CacheDatabase(scope));
|
||||
|
||||
return {server, scope, driver};
|
||||
}
|
||||
|
@ -2333,8 +2334,7 @@ async function removeAssetFromCache(
|
|||
scope: SwTestHarness, appVersionManifest: Manifest, assetPath: string) {
|
||||
const assetGroupName =
|
||||
appVersionManifest.assetGroups?.find(group => group.urls.includes(assetPath))?.name;
|
||||
const cacheName = `${scope.cacheNamePrefix}:${sha1(JSON.stringify(appVersionManifest))}:assets:${
|
||||
assetGroupName}:cache`;
|
||||
const cacheName = `${sha1(JSON.stringify(appVersionManifest))}:assets:${assetGroupName}:cache`;
|
||||
const cache = await scope.caches.open(cacheName);
|
||||
return cache.delete(assetPath);
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ const server = new MockServerStateBuilder().withStaticFiles(dist).withManifest(m
|
|||
|
||||
const scope = new SwTestHarnessBuilder().withServerState(server).build();
|
||||
|
||||
const db = new CacheDatabase(scope, scope);
|
||||
const db = new CacheDatabase(scope);
|
||||
|
||||
|
||||
describe('prefetch assets', () => {
|
||||
|
@ -57,10 +57,11 @@ describe('prefetch assets', () => {
|
|||
});
|
||||
it('persists the cache across restarts', async () => {
|
||||
await group.initializeFully();
|
||||
const freshScope = new SwTestHarnessBuilder().withCacheState(scope.caches.dehydrate()).build();
|
||||
const freshScope =
|
||||
new SwTestHarnessBuilder().withCacheState(scope.caches.original.dehydrate()).build();
|
||||
group = new PrefetchAssetGroup(
|
||||
freshScope, freshScope, idle, manifest.assetGroups![0], tmpHashTable(manifest),
|
||||
new CacheDatabase(freshScope, freshScope), 'test');
|
||||
new CacheDatabase(freshScope), 'test');
|
||||
await group.initializeFully();
|
||||
const res1 = await group.handleFetch(scope.newRequest('/foo.txt'), scope);
|
||||
const res2 = await group.handleFetch(scope.newRequest('/bar.txt'), scope);
|
||||
|
@ -82,7 +83,7 @@ describe('prefetch assets', () => {
|
|||
const badScope = new SwTestHarnessBuilder().withServerState(badServer).build();
|
||||
group = new PrefetchAssetGroup(
|
||||
badScope, badScope, idle, manifest.assetGroups![0], tmpHashTable(manifest),
|
||||
new CacheDatabase(badScope, badScope), 'test');
|
||||
new CacheDatabase(badScope), 'test');
|
||||
const err = await errorFrom(group.initializeFully());
|
||||
expect(err.message).toContain('Hash mismatch');
|
||||
});
|
||||
|
|
|
@ -105,7 +105,8 @@ export class MockClients implements Clients {
|
|||
async claim(): Promise<any> {}
|
||||
}
|
||||
|
||||
export class SwTestHarness extends Adapter implements ServiceWorkerGlobalScope, Context {
|
||||
export class SwTestHarness extends Adapter<MockCacheStorage> implements Context,
|
||||
ServiceWorkerGlobalScope {
|
||||
readonly clients = new MockClients();
|
||||
private eventHandlers = new Map<string, Function>();
|
||||
private skippedWaiting = false;
|
||||
|
@ -163,9 +164,8 @@ export class SwTestHarness extends Adapter implements ServiceWorkerGlobalScope,
|
|||
|
||||
parseUrl = parseUrl;
|
||||
|
||||
constructor(
|
||||
private server: MockServerState, readonly caches: MockCacheStorage, scopeUrl: string) {
|
||||
super(scopeUrl);
|
||||
constructor(private server: MockServerState, caches: MockCacheStorage, scopeUrl: string) {
|
||||
super(scopeUrl, caches);
|
||||
}
|
||||
|
||||
async resolveSelfMessages(): Promise<void> {
|
||||
|
|
Loading…
Reference in New Issue