refactor(service-worker): use nominal type for normalized URLs (#37922)
Some ServiceWorker operations and methods require normalized URLs. Previously, the generic `string` type was used. This commit introduces a new `NormalizedUrl` type, a special kind of `string`, to make this requirement explicit and use the type system to enforce it. PR Close #37922
This commit is contained in:
parent
d19ef6534f
commit
fb735d625b
|
@ -6,6 +6,9 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {NormalizedUrl} from './api';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adapts the service worker to its runtime environment.
|
* Adapts the service worker to its runtime environment.
|
||||||
*
|
*
|
||||||
|
@ -75,16 +78,10 @@ export class Adapter {
|
||||||
* @param url The raw request URL.
|
* @param url The raw request URL.
|
||||||
* @return A normalized representation of the URL.
|
* @return A normalized representation of the URL.
|
||||||
*/
|
*/
|
||||||
normalizeUrl(url: string): string {
|
normalizeUrl(url: string): NormalizedUrl {
|
||||||
// Check the URL's origin against the ServiceWorker's.
|
// Check the URL's origin against the ServiceWorker's.
|
||||||
const parsed = this.parseUrl(url, this.scopeUrl);
|
const parsed = this.parseUrl(url, this.scopeUrl);
|
||||||
if (parsed.origin === this.origin) {
|
return (parsed.origin === this.origin ? parsed.path : url) as NormalizedUrl;
|
||||||
// The URL is relative to the SW's origin: Return the path only.
|
|
||||||
return parsed.path;
|
|
||||||
} else {
|
|
||||||
// The URL is not relative to the SW's origin: Return the full URL.
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -12,6 +12,18 @@ export enum UpdateCacheStatus {
|
||||||
CACHED,
|
CACHED,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A `string` representing a URL that has been normalized relative to an origin (usually that of the
|
||||||
|
* ServiceWorker).
|
||||||
|
*
|
||||||
|
* If the URL is relative to the origin, then it is representated by the path part only. Otherwise,
|
||||||
|
* the full URL is used.
|
||||||
|
*
|
||||||
|
* NOTE: A `string` is not assignable to a `NormalizedUrl`, but a `NormalizedUrl` is assignable to a
|
||||||
|
* `string`.
|
||||||
|
*/
|
||||||
|
export type NormalizedUrl = string&{_brand: 'normalizedUrl'};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A source for old versions of URL contents and other resources.
|
* A source for old versions of URL contents and other resources.
|
||||||
*
|
*
|
||||||
|
@ -27,7 +39,7 @@ export interface UpdateSource {
|
||||||
* If an old version of the resource doesn't exist, or exists but does
|
* If an old version of the resource doesn't exist, or exists but does
|
||||||
* not match the hash given, this returns null.
|
* not match the hash given, this returns null.
|
||||||
*/
|
*/
|
||||||
lookupResourceWithHash(url: string, hash: string): Promise<Response|null>;
|
lookupResourceWithHash(url: NormalizedUrl, hash: string): Promise<Response|null>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lookup an older version of a resource for which the hash is not known.
|
* Lookup an older version of a resource for which the hash is not known.
|
||||||
|
@ -37,7 +49,7 @@ export interface UpdateSource {
|
||||||
* `Response`, but the cache metadata needed to re-cache the resource in
|
* `Response`, but the cache metadata needed to re-cache the resource in
|
||||||
* a newer `AppVersion`.
|
* a newer `AppVersion`.
|
||||||
*/
|
*/
|
||||||
lookupResourceWithoutHash(url: string): Promise<CacheState|null>;
|
lookupResourceWithoutHash(url: NormalizedUrl): Promise<CacheState|null>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List the URLs of all of the resources which were previously cached.
|
* List the URLs of all of the resources which were previously cached.
|
||||||
|
@ -45,7 +57,7 @@ export interface UpdateSource {
|
||||||
* This allows for the discovery of resources which are not listed in the
|
* This allows for the discovery of resources which are not listed in the
|
||||||
* manifest but which were picked up because they matched URL patterns.
|
* manifest but which were picked up because they matched URL patterns.
|
||||||
*/
|
*/
|
||||||
previouslyCachedResources(): Promise<string[]>;
|
previouslyCachedResources(): Promise<NormalizedUrl[]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether a particular resource exists in the most recent cache.
|
* Check whether a particular resource exists in the most recent cache.
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Adapter, Context} from './adapter';
|
import {Adapter, Context} from './adapter';
|
||||||
import {CacheState, UpdateCacheStatus, UpdateSource} from './api';
|
import {CacheState, NormalizedUrl, UpdateCacheStatus, UpdateSource} from './api';
|
||||||
import {AssetGroup, LazyAssetGroup, PrefetchAssetGroup} from './assets';
|
import {AssetGroup, LazyAssetGroup, PrefetchAssetGroup} from './assets';
|
||||||
import {DataGroup} from './data';
|
import {DataGroup} from './data';
|
||||||
import {Database} from './database';
|
import {Database} from './database';
|
||||||
|
@ -31,10 +31,9 @@ const BACKWARDS_COMPATIBILITY_NAVIGATION_URLS = [
|
||||||
*/
|
*/
|
||||||
export class AppVersion implements UpdateSource {
|
export class AppVersion implements UpdateSource {
|
||||||
/**
|
/**
|
||||||
* A Map of absolute URL paths (/foo.txt) to the known hash of their
|
* A Map of absolute URL paths (`/foo.txt`) to the known hash of their contents (if available).
|
||||||
* contents (if available).
|
|
||||||
*/
|
*/
|
||||||
private hashTable = new Map<string, string>();
|
private hashTable = new Map<NormalizedUrl, string>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All of the asset groups active in this version of the app.
|
* All of the asset groups active in this version of the app.
|
||||||
|
@ -218,7 +217,7 @@ export class AppVersion implements UpdateSource {
|
||||||
/**
|
/**
|
||||||
* Check this version for a given resource with a particular hash.
|
* Check this version for a given resource with a particular hash.
|
||||||
*/
|
*/
|
||||||
async lookupResourceWithHash(url: string, hash: string): Promise<Response|null> {
|
async lookupResourceWithHash(url: NormalizedUrl, hash: string): Promise<Response|null> {
|
||||||
// Verify that this version has the requested resource cached. If not,
|
// Verify that this version has the requested resource cached. If not,
|
||||||
// there's no point in trying.
|
// there's no point in trying.
|
||||||
if (!this.hashTable.has(url)) {
|
if (!this.hashTable.has(url)) {
|
||||||
|
@ -238,7 +237,7 @@ export class AppVersion implements UpdateSource {
|
||||||
/**
|
/**
|
||||||
* Check this version for a given resource regardless of its hash.
|
* Check this version for a given resource regardless of its hash.
|
||||||
*/
|
*/
|
||||||
lookupResourceWithoutHash(url: string): Promise<CacheState|null> {
|
lookupResourceWithoutHash(url: NormalizedUrl): Promise<CacheState|null> {
|
||||||
// Limit the search to asset groups, and only scan the cache, don't
|
// Limit the search to asset groups, and only scan the cache, don't
|
||||||
// load resources from the network.
|
// load resources from the network.
|
||||||
return this.assetGroups.reduce(async (potentialResponse, group) => {
|
return this.assetGroups.reduce(async (potentialResponse, group) => {
|
||||||
|
@ -256,10 +255,10 @@ export class AppVersion implements UpdateSource {
|
||||||
/**
|
/**
|
||||||
* List all unhashed resources from all asset groups.
|
* List all unhashed resources from all asset groups.
|
||||||
*/
|
*/
|
||||||
previouslyCachedResources(): Promise<string[]> {
|
previouslyCachedResources(): Promise<NormalizedUrl[]> {
|
||||||
return this.assetGroups.reduce(async (resources, group) => {
|
return this.assetGroups.reduce(
|
||||||
return (await resources).concat(await group.unhashedResources());
|
async (resources, group) => (await resources).concat(await group.unhashedResources()),
|
||||||
}, Promise.resolve<string[]>([]));
|
Promise.resolve<NormalizedUrl[]>([]));
|
||||||
}
|
}
|
||||||
|
|
||||||
async recentCacheStatus(url: string): Promise<UpdateCacheStatus> {
|
async recentCacheStatus(url: string): Promise<UpdateCacheStatus> {
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Adapter, Context} from './adapter';
|
import {Adapter, Context} from './adapter';
|
||||||
import {CacheState, UpdateCacheStatus, UpdateSource, UrlMetadata} from './api';
|
import {CacheState, NormalizedUrl, UpdateCacheStatus, UpdateSource, UrlMetadata} from './api';
|
||||||
import {Database, Table} from './database';
|
import {Database, Table} from './database';
|
||||||
import {errorToString, SwCriticalError} from './error';
|
import {errorToString, SwCriticalError} from './error';
|
||||||
import {IdleScheduler} from './idle';
|
import {IdleScheduler} from './idle';
|
||||||
|
@ -29,7 +29,7 @@ export abstract class AssetGroup {
|
||||||
/**
|
/**
|
||||||
* Normalized resource URLs.
|
* Normalized resource URLs.
|
||||||
*/
|
*/
|
||||||
protected urls: string[] = [];
|
protected urls: NormalizedUrl[] = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Regular expression patterns.
|
* Regular expression patterns.
|
||||||
|
@ -266,7 +266,7 @@ export abstract class AssetGroup {
|
||||||
/**
|
/**
|
||||||
* Lookup all resources currently stored in the cache which have no associated hash.
|
* Lookup all resources currently stored in the cache which have no associated hash.
|
||||||
*/
|
*/
|
||||||
async unhashedResources(): Promise<string[]> {
|
async unhashedResources(): Promise<NormalizedUrl[]> {
|
||||||
const cache = await this.cache;
|
const cache = await this.cache;
|
||||||
// Start with the set of all cached requests.
|
// Start with the set of all cached requests.
|
||||||
return (await cache.keys())
|
return (await cache.keys())
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Adapter} from './adapter';
|
import {Adapter} from './adapter';
|
||||||
import {CacheState, Debuggable, DebugIdleState, DebugState, DebugVersion, UpdateCacheStatus, UpdateSource} from './api';
|
import {CacheState, Debuggable, DebugIdleState, DebugState, DebugVersion, NormalizedUrl, UpdateCacheStatus, UpdateSource} from './api';
|
||||||
import {AppVersion} from './app-version';
|
import {AppVersion} from './app-version';
|
||||||
import {Database} from './database';
|
import {Database} from './database';
|
||||||
import {DebugHandler} from './debug';
|
import {DebugHandler} from './debug';
|
||||||
|
@ -959,7 +959,7 @@ export class Driver implements Debuggable, UpdateSource {
|
||||||
* Determine if a specific version of the given resource is cached anywhere within the SW,
|
* Determine if a specific version of the given resource is cached anywhere within the SW,
|
||||||
* and fetch it if so.
|
* and fetch it if so.
|
||||||
*/
|
*/
|
||||||
lookupResourceWithHash(url: string, hash: string): Promise<Response|null> {
|
lookupResourceWithHash(url: NormalizedUrl, hash: string): Promise<Response|null> {
|
||||||
return Array
|
return Array
|
||||||
// Scan through the set of all cached versions, valid or otherwise. It's safe to do such
|
// Scan through the set of all cached versions, valid or otherwise. It's safe to do such
|
||||||
// lookups even for invalid versions as the cached version of a resource will have the
|
// lookups even for invalid versions as the cached version of a resource will have the
|
||||||
|
@ -981,13 +981,13 @@ export class Driver implements Debuggable, UpdateSource {
|
||||||
}, Promise.resolve<Response|null>(null));
|
}, Promise.resolve<Response|null>(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
async lookupResourceWithoutHash(url: string): Promise<CacheState|null> {
|
async lookupResourceWithoutHash(url: NormalizedUrl): Promise<CacheState|null> {
|
||||||
await this.initialized;
|
await this.initialized;
|
||||||
const version = this.versions.get(this.latestHash!);
|
const version = this.versions.get(this.latestHash!);
|
||||||
return version ? version.lookupResourceWithoutHash(url) : null;
|
return version ? version.lookupResourceWithoutHash(url) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async previouslyCachedResources(): Promise<string[]> {
|
async previouslyCachedResources(): Promise<NormalizedUrl[]> {
|
||||||
await this.initialized;
|
await this.initialized;
|
||||||
const version = this.versions.get(this.latestHash!);
|
const version = this.versions.get(this.latestHash!);
|
||||||
return version ? version.previouslyCachedResources() : [];
|
return version ? version.previouslyCachedResources() : [];
|
||||||
|
|
|
@ -6,6 +6,9 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {NormalizedUrl} from '../src/api';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a normalized representation of a URL relative to a provided base URL.
|
* Get a normalized representation of a URL relative to a provided base URL.
|
||||||
*
|
*
|
||||||
|
@ -19,11 +22,11 @@
|
||||||
* (This is usually the ServiceWorker's origin or registration scope).
|
* (This is usually the ServiceWorker's origin or registration scope).
|
||||||
* @return A normalized representation of the URL.
|
* @return A normalized representation of the URL.
|
||||||
*/
|
*/
|
||||||
export function normalizeUrl(url: string, relativeTo: string): string {
|
export function normalizeUrl(url: string, relativeTo: string): NormalizedUrl {
|
||||||
const {origin, path, search} = parseUrl(url, relativeTo);
|
const {origin, path, search} = parseUrl(url, relativeTo);
|
||||||
const {origin: relativeToOrigin} = parseUrl(relativeTo);
|
const {origin: relativeToOrigin} = parseUrl(relativeTo);
|
||||||
|
|
||||||
return (origin === relativeToOrigin) ? path + search : url;
|
return ((origin === relativeToOrigin) ? path + search : url) as NormalizedUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue