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
|
||||
*/
|
||||
|
||||
import {NormalizedUrl} from './api';
|
||||
|
||||
|
||||
/**
|
||||
* Adapts the service worker to its runtime environment.
|
||||
*
|
||||
|
@ -75,16 +78,10 @@ export class Adapter {
|
|||
* @param url The raw request 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.
|
||||
const parsed = this.parseUrl(url, this.scopeUrl);
|
||||
if (parsed.origin === this.origin) {
|
||||
// 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;
|
||||
}
|
||||
return (parsed.origin === this.origin ? parsed.path : url) as NormalizedUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -12,6 +12,18 @@ export enum UpdateCacheStatus {
|
|||
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.
|
||||
*
|
||||
|
@ -27,7 +39,7 @@ export interface UpdateSource {
|
|||
* If an old version of the resource doesn't exist, or exists but does
|
||||
* 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.
|
||||
|
@ -37,7 +49,7 @@ export interface UpdateSource {
|
|||
* `Response`, but the cache metadata needed to re-cache the resource in
|
||||
* 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.
|
||||
|
@ -45,7 +57,7 @@ export interface UpdateSource {
|
|||
* This allows for the discovery of resources which are not listed in the
|
||||
* 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.
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
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 {DataGroup} from './data';
|
||||
import {Database} from './database';
|
||||
|
@ -31,10 +31,9 @@ const BACKWARDS_COMPATIBILITY_NAVIGATION_URLS = [
|
|||
*/
|
||||
export class AppVersion implements UpdateSource {
|
||||
/**
|
||||
* A Map of absolute URL paths (/foo.txt) to the known hash of their
|
||||
* contents (if available).
|
||||
* A Map of absolute URL paths (`/foo.txt`) to the known hash of their 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.
|
||||
|
@ -218,7 +217,7 @@ export class AppVersion implements UpdateSource {
|
|||
/**
|
||||
* 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,
|
||||
// there's no point in trying.
|
||||
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.
|
||||
*/
|
||||
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
|
||||
// load resources from the network.
|
||||
return this.assetGroups.reduce(async (potentialResponse, group) => {
|
||||
|
@ -256,10 +255,10 @@ export class AppVersion implements UpdateSource {
|
|||
/**
|
||||
* List all unhashed resources from all asset groups.
|
||||
*/
|
||||
previouslyCachedResources(): Promise<string[]> {
|
||||
return this.assetGroups.reduce(async (resources, group) => {
|
||||
return (await resources).concat(await group.unhashedResources());
|
||||
}, Promise.resolve<string[]>([]));
|
||||
previouslyCachedResources(): Promise<NormalizedUrl[]> {
|
||||
return this.assetGroups.reduce(
|
||||
async (resources, group) => (await resources).concat(await group.unhashedResources()),
|
||||
Promise.resolve<NormalizedUrl[]>([]));
|
||||
}
|
||||
|
||||
async recentCacheStatus(url: string): Promise<UpdateCacheStatus> {
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
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 {errorToString, SwCriticalError} from './error';
|
||||
import {IdleScheduler} from './idle';
|
||||
|
@ -29,7 +29,7 @@ export abstract class AssetGroup {
|
|||
/**
|
||||
* Normalized resource URLs.
|
||||
*/
|
||||
protected urls: string[] = [];
|
||||
protected urls: NormalizedUrl[] = [];
|
||||
|
||||
/**
|
||||
* Regular expression patterns.
|
||||
|
@ -266,7 +266,7 @@ export abstract class AssetGroup {
|
|||
/**
|
||||
* 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;
|
||||
// Start with the set of all cached requests.
|
||||
return (await cache.keys())
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
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 {Database} from './database';
|
||||
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,
|
||||
* and fetch it if so.
|
||||
*/
|
||||
lookupResourceWithHash(url: string, hash: string): Promise<Response|null> {
|
||||
lookupResourceWithHash(url: NormalizedUrl, hash: string): Promise<Response|null> {
|
||||
return Array
|
||||
// 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
|
||||
|
@ -981,13 +981,13 @@ export class Driver implements Debuggable, UpdateSource {
|
|||
}, Promise.resolve<Response|null>(null));
|
||||
}
|
||||
|
||||
async lookupResourceWithoutHash(url: string): Promise<CacheState|null> {
|
||||
async lookupResourceWithoutHash(url: NormalizedUrl): Promise<CacheState|null> {
|
||||
await this.initialized;
|
||||
const version = this.versions.get(this.latestHash!);
|
||||
return version ? version.lookupResourceWithoutHash(url) : null;
|
||||
}
|
||||
|
||||
async previouslyCachedResources(): Promise<string[]> {
|
||||
async previouslyCachedResources(): Promise<NormalizedUrl[]> {
|
||||
await this.initialized;
|
||||
const version = this.versions.get(this.latestHash!);
|
||||
return version ? version.previouslyCachedResources() : [];
|
||||
|
|
|
@ -6,6 +6,9 @@
|
|||
* 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.
|
||||
*
|
||||
|
@ -19,11 +22,11 @@
|
|||
* (This is usually the ServiceWorker's origin or registration scope).
|
||||
* @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: relativeToOrigin} = parseUrl(relativeTo);
|
||||
|
||||
return (origin === relativeToOrigin) ? path + search : url;
|
||||
return ((origin === relativeToOrigin) ? path + search : url) as NormalizedUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue