Files
SkyZeroZx 5619120931 fix(service-worker): Preserves HTTP cache mode in asset group requests
Ensures explicit HTTP cache mode from incoming requests is forwarded and maintained when creating fetch requests for assets, aligning with expected fetch behavior and preventing unintended cache handling.
2026-05-27 11:02:13 -07:00

233 lines
5.8 KiB
TypeScript

/**
* @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.dev/license
*/
export class MockBody implements Body {
readonly body!: ReadableStream;
bodyUsed: boolean = false;
constructor(public _body: string | null) {}
async arrayBuffer(): Promise<ArrayBuffer> {
const body = this.getBody();
const buffer = new ArrayBuffer(body.length);
const view = new Uint8Array(buffer);
for (let i = 0; i < body.length; i++) {
view[i] = body.charCodeAt(i);
}
return buffer;
}
async blob(): Promise<Blob> {
throw 'Not implemented';
}
async json(): Promise<any> {
return JSON.parse(this.getBody()) as any;
}
async text(): Promise<string> {
return this.getBody();
}
async formData(): Promise<FormData> {
throw 'Not implemented';
}
async bytes(): Promise<Uint8Array<ArrayBufferLike>> {
throw 'Not implemented';
}
private getBody(): string {
if (this.bodyUsed === true) {
throw new Error('Cannot reuse body without cloning.');
}
this.bodyUsed = true;
// According to the spec, a `null` body results in an empty `ReadableStream` (which for our
// needs is equivalent to `''`). See https://fetch.spec.whatwg.org/#concept-body-consume-body.
return this._body || '';
}
}
export class MockHeaders implements Headers {
map = new Map<string, string>();
[Symbol.iterator]() {
return this.map[Symbol.iterator]();
}
append(name: string, value: string): void {
this.map.set(name.toLowerCase(), value);
}
delete(name: string): void {
this.map.delete(name.toLowerCase());
}
entries() {
return this.map.entries();
}
forEach(callback: Function): void {
this.map.forEach(callback as any);
}
get(name: string): string | null {
return this.map.get(name.toLowerCase()) || null;
}
has(name: string): boolean {
return this.map.has(name.toLowerCase());
}
keys() {
return this.map.keys();
}
set(name: string, value: string): void {
this.map.set(name.toLowerCase(), value);
}
values() {
return this.map.values();
}
getSetCookie(): string[] {
return [];
}
}
export class MockRequest extends MockBody implements Request {
readonly isHistoryNavigation: boolean = false;
readonly isReloadNavigation: boolean = false;
readonly cache: RequestCache = 'default';
readonly credentials: RequestCredentials = 'same-origin';
readonly destination: RequestDestination = 'document';
readonly headers: Headers = new MockHeaders();
readonly integrity: string = '';
readonly keepalive: boolean = true;
readonly method: string = 'GET';
readonly mode: RequestMode = 'cors';
readonly redirect: RequestRedirect = 'follow';
readonly referrer: string = '';
readonly referrerPolicy: ReferrerPolicy = 'no-referrer';
readonly signal: AbortSignal = null as any;
url: string;
constructor(
input: string | Request,
init: RequestInit & {destination?: RequestDestination} = {},
) {
super((init.body as string | null) ?? null);
if (typeof input !== 'string') {
throw 'Not implemented';
}
this.url = input;
const headers = init.headers as {[key: string]: string};
if (headers !== undefined) {
if (headers instanceof MockHeaders) {
this.headers = headers;
} else {
Object.keys(headers).forEach((header) => {
this.headers.set(header, headers[header]);
});
}
}
if (init.cache !== undefined) {
this.cache = init.cache;
}
if (init.mode !== undefined) {
this.mode = init.mode;
}
if (init.credentials !== undefined) {
this.credentials = init.credentials;
}
if (init.method !== undefined) {
this.method = init.method;
}
if (init.redirect !== undefined) {
this.redirect = init.redirect;
}
if (init.destination !== undefined) {
this.destination = init.destination;
}
}
clone(): Request {
if (this.bodyUsed) {
throw 'Body already consumed';
}
return new MockRequest(this.url, {
body: this._body,
cache: this.cache,
mode: this.mode,
credentials: this.credentials,
headers: this.headers,
redirect: this.redirect,
});
}
}
export class MockResponse extends MockBody implements Response {
readonly trailer: Promise<Headers> = Promise.resolve(new MockHeaders());
readonly headers: Headers = new MockHeaders();
get ok(): boolean {
return this.status >= 200 && this.status < 300;
}
readonly status: number;
readonly statusText: string;
readonly type: ResponseType = 'basic';
readonly url: string = '';
readonly redirected: boolean = false;
constructor(
body?: any,
init: ResponseInit & {type?: ResponseType; redirected?: boolean; url?: string} = {},
) {
super(typeof body === 'string' ? body : null);
this.status = init.status !== undefined ? init.status : 200;
this.statusText = init.statusText !== undefined ? init.statusText : 'OK';
const headers = init.headers as {[key: string]: string};
if (headers !== undefined) {
if (headers instanceof MockHeaders) {
this.headers = headers;
} else {
Object.keys(headers).forEach((header) => {
this.headers.set(header, headers[header]);
});
}
}
if (init.type !== undefined) {
this.type = init.type;
}
if (init.redirected !== undefined) {
this.redirected = init.redirected;
}
if (init.url !== undefined) {
this.url = init.url;
}
}
clone(): Response {
if (this.bodyUsed) {
throw 'Body already consumed';
}
return new MockResponse(this._body, {
status: this.status,
statusText: this.statusText,
headers: this.headers,
type: this.type,
redirected: this.redirected,
url: this.url,
});
}
}