refactor(service-worker): remove duplicate `Context` type (in favor of `ExtendableEvent`) (#42736)
This commit removes the duplicate `Context` interface and uses the `ExtendableEvent` interface instead. This is in preparation of switching from our custom typings to the official TypeScript typings (`lib.webworker.d.ts`). PR Close #42736
This commit is contained in:
parent
ad00d0830f
commit
fe135e1198
|
@ -104,14 +104,3 @@ export class Adapter<T extends CacheStorage = CacheStorage> {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An event context in which an operation is taking place, which allows
|
||||
* the delaying of Service Worker shutdown until certain triggers occur.
|
||||
*/
|
||||
export interface Context {
|
||||
/**
|
||||
* Delay shutdown of the Service Worker until the given promise resolves.
|
||||
*/
|
||||
waitUntil(fn: Promise<any>): void;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Adapter, Context} from './adapter';
|
||||
import {Adapter} from './adapter';
|
||||
import {CacheState, NormalizedUrl, UpdateCacheStatus, UpdateSource} from './api';
|
||||
import {AssetGroup, LazyAssetGroup, PrefetchAssetGroup} from './assets';
|
||||
import {DataGroup} from './data';
|
||||
|
@ -135,7 +135,7 @@ export class AppVersion implements UpdateSource {
|
|||
}
|
||||
}
|
||||
|
||||
async handleFetch(req: Request, context: Context): Promise<Response|null> {
|
||||
async handleFetch(req: Request, event: ExtendableEvent): Promise<Response|null> {
|
||||
// Check the request against each `AssetGroup` in sequence. If an `AssetGroup` can't handle the
|
||||
// request,
|
||||
// it will return `null`. Thus, the first non-null response is the SW's answer to the request.
|
||||
|
@ -152,7 +152,7 @@ export class AppVersion implements UpdateSource {
|
|||
}
|
||||
|
||||
// No response has been found yet. Maybe this group will have one.
|
||||
return group.handleFetch(req, context);
|
||||
return group.handleFetch(req, event);
|
||||
}, Promise.resolve<Response|null>(null));
|
||||
|
||||
// The result of the above is the asset response, if there is any, or null otherwise. Return the
|
||||
|
@ -170,7 +170,7 @@ export class AppVersion implements UpdateSource {
|
|||
return resp;
|
||||
}
|
||||
|
||||
return group.handleFetch(req, context);
|
||||
return group.handleFetch(req, event);
|
||||
}, Promise.resolve<Response|null>(null));
|
||||
|
||||
// If the data caching group returned a response, go with it.
|
||||
|
@ -195,7 +195,7 @@ export class AppVersion implements UpdateSource {
|
|||
|
||||
// This was a navigation request. Re-enter `handleFetch` with a request for
|
||||
// the URL.
|
||||
return this.handleFetch(this.adapter.newRequest(this.indexUrl), context);
|
||||
return this.handleFetch(this.adapter.newRequest(this.indexUrl), event);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Adapter, Context} from './adapter';
|
||||
import {Adapter} from './adapter';
|
||||
import {CacheState, NormalizedUrl, UpdateCacheStatus, UpdateSource, UrlMetadata} from './api';
|
||||
import {Database, Table} from './database';
|
||||
import {CacheTable} from './db-cache';
|
||||
|
@ -114,7 +114,7 @@ export abstract class AssetGroup {
|
|||
/**
|
||||
* Process a request for a given resource and return it, or return null if it's not available.
|
||||
*/
|
||||
async handleFetch(req: Request, ctx: Context): Promise<Response|null> {
|
||||
async handleFetch(req: Request, _event: ExtendableEvent): Promise<Response|null> {
|
||||
const url = this.adapter.normalizeUrl(req.url);
|
||||
// Either the request matches one of the known resource URLs, one of the patterns for
|
||||
// dynamically matched URLs, or neither. Determine which is the case for this request in
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Adapter, Context} from './adapter';
|
||||
import {Adapter} from './adapter';
|
||||
import {Database, Table} from './database';
|
||||
import {CacheTable} from './db-cache';
|
||||
import {DebugHandler} from './debug';
|
||||
|
@ -294,7 +294,7 @@ export class DataGroup {
|
|||
* Process a fetch event and return a `Response` if the resource is covered by this group,
|
||||
* or `null` otherwise.
|
||||
*/
|
||||
async handleFetch(req: Request, ctx: Context): Promise<Response|null> {
|
||||
async handleFetch(req: Request, event: ExtendableEvent): Promise<Response|null> {
|
||||
// Do nothing
|
||||
if (!this.patterns.some(pattern => pattern.test(req.url))) {
|
||||
return null;
|
||||
|
@ -314,9 +314,9 @@ export class DataGroup {
|
|||
// Handle the request with whatever strategy was selected.
|
||||
switch (this.config.strategy) {
|
||||
case 'freshness':
|
||||
return this.handleFetchWithFreshness(req, ctx, lru);
|
||||
return this.handleFetchWithFreshness(req, event, lru);
|
||||
case 'performance':
|
||||
return this.handleFetchWithPerformance(req, ctx, lru);
|
||||
return this.handleFetchWithPerformance(req, event, lru);
|
||||
default:
|
||||
throw new Error(`Unknown strategy: ${this.config.strategy}`);
|
||||
}
|
||||
|
@ -337,7 +337,7 @@ export class DataGroup {
|
|||
}
|
||||
}
|
||||
|
||||
private async handleFetchWithPerformance(req: Request, ctx: Context, lru: LruList):
|
||||
private async handleFetchWithPerformance(req: Request, event: ExtendableEvent, lru: LruList):
|
||||
Promise<Response|null> {
|
||||
let res: Response|null|undefined = null;
|
||||
|
||||
|
@ -348,7 +348,7 @@ export class DataGroup {
|
|||
res = fromCache.res;
|
||||
// Check the age of the resource.
|
||||
if (this.config.refreshAheadMs !== undefined && fromCache.age >= this.config.refreshAheadMs) {
|
||||
ctx.waitUntil(this.safeCacheResponse(req, this.safeFetch(req), lru));
|
||||
event.waitUntil(this.safeCacheResponse(req, this.safeFetch(req), lru));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -367,7 +367,7 @@ export class DataGroup {
|
|||
res = this.adapter.newResponse(null, {status: 504, statusText: 'Gateway Timeout'});
|
||||
|
||||
// Cache the network response eventually.
|
||||
ctx.waitUntil(this.safeCacheResponse(req, networkFetch, lru));
|
||||
event.waitUntil(this.safeCacheResponse(req, networkFetch, lru));
|
||||
} else {
|
||||
// The request completed in time, so cache it inline with the response flow.
|
||||
await this.safeCacheResponse(req, res, lru);
|
||||
|
@ -376,7 +376,7 @@ export class DataGroup {
|
|||
return res;
|
||||
}
|
||||
|
||||
private async handleFetchWithFreshness(req: Request, ctx: Context, lru: LruList):
|
||||
private async handleFetchWithFreshness(req: Request, event: ExtendableEvent, lru: LruList):
|
||||
Promise<Response|null> {
|
||||
// Start with a network fetch.
|
||||
const [timeoutFetch, networkFetch] = this.networkFetchWithTimeout(req);
|
||||
|
@ -392,7 +392,7 @@ export class DataGroup {
|
|||
|
||||
// If the network fetch times out or errors, fall back on the cache.
|
||||
if (res === undefined) {
|
||||
ctx.waitUntil(this.safeCacheResponse(req, networkFetch, lru, true));
|
||||
event.waitUntil(this.safeCacheResponse(req, networkFetch, lru, true));
|
||||
|
||||
// Ignore the age, the network response will be cached anyway due to the
|
||||
// behavior of freshness.
|
||||
|
|
|
@ -12,7 +12,7 @@ import {IdleScheduler} from '../src/idle';
|
|||
import {MockCache} from '../testing/cache';
|
||||
import {MockRequest} from '../testing/fetch';
|
||||
import {MockFileSystemBuilder, MockServerStateBuilder, tmpHashTable, tmpManifestSingleAssetGroup} from '../testing/mock';
|
||||
import {SwTestHarness, SwTestHarnessBuilder} from '../testing/scope';
|
||||
import {MockExtendableEvent, SwTestHarness, SwTestHarnessBuilder} from '../testing/scope';
|
||||
|
||||
(function() {
|
||||
// Skip environments that don't support the minimum APIs needed to run the SW tests.
|
||||
|
@ -33,6 +33,8 @@ const scope = new SwTestHarnessBuilder().withServerState(server).build();
|
|||
|
||||
const db = new CacheDatabase(scope);
|
||||
|
||||
const testEvent = new MockExtendableEvent('test');
|
||||
|
||||
|
||||
describe('prefetch assets', () => {
|
||||
let group: PrefetchAssetGroup;
|
||||
|
@ -50,8 +52,8 @@ describe('prefetch assets', () => {
|
|||
it('fully caches the two files', async () => {
|
||||
await group.initializeFully();
|
||||
scope.updateServerState();
|
||||
const res1 = await group.handleFetch(scope.newRequest('/foo.txt'), scope);
|
||||
const res2 = await group.handleFetch(scope.newRequest('/bar.txt'), scope);
|
||||
const res1 = await group.handleFetch(scope.newRequest('/foo.txt'), testEvent);
|
||||
const res2 = await group.handleFetch(scope.newRequest('/bar.txt'), testEvent);
|
||||
expect(await res1!.text()).toEqual('this is foo');
|
||||
expect(await res2!.text()).toEqual('this is bar');
|
||||
});
|
||||
|
@ -63,14 +65,14 @@ describe('prefetch assets', () => {
|
|||
freshScope, freshScope, idle, manifest.assetGroups![0], tmpHashTable(manifest),
|
||||
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);
|
||||
const res1 = await group.handleFetch(scope.newRequest('/foo.txt'), testEvent);
|
||||
const res2 = await group.handleFetch(scope.newRequest('/bar.txt'), testEvent);
|
||||
expect(await res1!.text()).toEqual('this is foo');
|
||||
expect(await res2!.text()).toEqual('this is bar');
|
||||
});
|
||||
it('caches properly if resources are requested before initialization', async () => {
|
||||
const res1 = await group.handleFetch(scope.newRequest('/foo.txt'), scope);
|
||||
const res2 = await group.handleFetch(scope.newRequest('/bar.txt'), scope);
|
||||
const res1 = await group.handleFetch(scope.newRequest('/foo.txt'), testEvent);
|
||||
const res2 = await group.handleFetch(scope.newRequest('/bar.txt'), testEvent);
|
||||
expect(await res1!.text()).toEqual('this is foo');
|
||||
expect(await res2!.text()).toEqual('this is bar');
|
||||
scope.updateServerState();
|
||||
|
@ -90,7 +92,7 @@ describe('prefetch assets', () => {
|
|||
it('CacheQueryOptions are passed through', async () => {
|
||||
await group.initializeFully();
|
||||
const matchSpy = spyOn(MockCache.prototype, 'match').and.callThrough();
|
||||
await group.handleFetch(scope.newRequest('/foo.txt'), scope);
|
||||
await group.handleFetch(scope.newRequest('/foo.txt'), testEvent);
|
||||
expect(matchSpy).toHaveBeenCalledWith(new MockRequest('/foo.txt'), {ignoreVary: true});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import {Subject} from 'rxjs';
|
||||
|
||||
import {Adapter, Context} from '../src/adapter';
|
||||
import {Adapter} from '../src/adapter';
|
||||
import {AssetGroupConfig, Manifest} from '../src/manifest';
|
||||
import {sha1} from '../src/sha1';
|
||||
|
||||
|
@ -105,8 +105,7 @@ export class MockClients implements Clients {
|
|||
async claim(): Promise<any> {}
|
||||
}
|
||||
|
||||
export class SwTestHarness extends Adapter<MockCacheStorage> implements Context,
|
||||
ServiceWorkerGlobalScope {
|
||||
export class SwTestHarness extends Adapter<MockCacheStorage> implements ServiceWorkerGlobalScope {
|
||||
readonly clients = new MockClients();
|
||||
private eventHandlers = new Map<string, Function>();
|
||||
private skippedWaiting = false;
|
||||
|
@ -239,8 +238,6 @@ export class SwTestHarness extends Adapter<MockCacheStorage> implements Context,
|
|||
this.skippedWaiting = true;
|
||||
}
|
||||
|
||||
waitUntil(promise: Promise<void>): void {}
|
||||
|
||||
handleFetch(req: Request, clientId = ''): [Promise<Response|undefined>, Promise<void>] {
|
||||
if (!this.eventHandlers.has('fetch')) {
|
||||
throw new Error('No fetch handler registered');
|
||||
|
@ -376,7 +373,49 @@ export class ConfigBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
class OneTimeContext implements Context {
|
||||
class MockEvent implements Event {
|
||||
readonly AT_TARGET = -1;
|
||||
readonly BUBBLING_PHASE = -1;
|
||||
readonly CAPTURING_PHASE = -1;
|
||||
readonly NONE = -1;
|
||||
|
||||
readonly bubbles = false;
|
||||
cancelBubble = false;
|
||||
readonly cancelable = false;
|
||||
readonly composed = false;
|
||||
readonly currentTarget = null;
|
||||
readonly defaultPrevented = false;
|
||||
readonly eventPhase = -1;
|
||||
readonly isTrusted = false;
|
||||
returnValue = false;
|
||||
readonly srcElement = null;
|
||||
readonly target = null;
|
||||
readonly timeStamp = Date.now();
|
||||
|
||||
constructor(readonly type: string) {}
|
||||
|
||||
composedPath(): EventTarget[] {
|
||||
this.notImplemented();
|
||||
}
|
||||
initEvent(type: string, bubbles?: boolean, cancelable?: boolean): void {
|
||||
this.notImplemented();
|
||||
}
|
||||
preventDefault(): void {
|
||||
this.notImplemented();
|
||||
}
|
||||
stopImmediatePropagation(): void {
|
||||
this.notImplemented();
|
||||
}
|
||||
stopPropagation(): void {
|
||||
this.notImplemented();
|
||||
}
|
||||
|
||||
private notImplemented(): never {
|
||||
throw new Error('Method not implemented in `MockEvent`.');
|
||||
}
|
||||
}
|
||||
|
||||
export class MockExtendableEvent extends MockEvent implements ExtendableEvent {
|
||||
private queue: Promise<void>[] = [];
|
||||
|
||||
waitUntil(promise: Promise<void>): void {
|
||||
|
@ -392,14 +431,12 @@ class OneTimeContext implements Context {
|
|||
}
|
||||
}
|
||||
|
||||
class MockExtendableEvent extends OneTimeContext {}
|
||||
|
||||
class MockFetchEvent extends MockExtendableEvent {
|
||||
response: Promise<Response|undefined> = Promise.resolve(undefined);
|
||||
|
||||
constructor(
|
||||
readonly request: Request, readonly clientId: string, readonly resultingClientId: string) {
|
||||
super();
|
||||
super('fetch');
|
||||
}
|
||||
|
||||
respondWith(promise: Promise<Response>): Promise<Response> {
|
||||
|
@ -410,13 +447,13 @@ class MockFetchEvent extends MockExtendableEvent {
|
|||
|
||||
class MockMessageEvent extends MockExtendableEvent {
|
||||
constructor(readonly data: Object, readonly source: MockClient|null) {
|
||||
super();
|
||||
super('message');
|
||||
}
|
||||
}
|
||||
|
||||
class MockPushEvent extends MockExtendableEvent {
|
||||
constructor(private _data: Object) {
|
||||
super();
|
||||
super('push');
|
||||
}
|
||||
data = {
|
||||
json: () => this._data,
|
||||
|
@ -425,11 +462,19 @@ class MockPushEvent extends MockExtendableEvent {
|
|||
|
||||
class MockNotificationEvent extends MockExtendableEvent {
|
||||
constructor(private _notification: any, readonly action?: string) {
|
||||
super();
|
||||
super('notification');
|
||||
}
|
||||
readonly notification = {...this._notification, close: () => undefined};
|
||||
}
|
||||
|
||||
class MockInstallEvent extends MockExtendableEvent {}
|
||||
class MockInstallEvent extends MockExtendableEvent {
|
||||
constructor() {
|
||||
super('install');
|
||||
}
|
||||
}
|
||||
|
||||
class MockActivateEvent extends MockExtendableEvent {}
|
||||
class MockActivateEvent extends MockExtendableEvent {
|
||||
constructor() {
|
||||
super('activate');
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue