Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 28 additions & 18 deletions packages/common/http/src/transfer_cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,14 @@ import {
ɵRuntimeError as RuntimeError,
} from '@angular/core';
import {Observable, of} from 'rxjs';
import {concatMap} from 'rxjs/operators';
import {tap} from 'rxjs/operators';

import {RuntimeErrorCode} from './errors';
import {HttpHeaders} from './headers';
import {HTTP_ROOT_INTERCEPTOR_FNS, HttpHandlerFn} from './interceptor';
import {HttpRequest} from './request';
import {HttpEvent, HttpResponse} from './response';
import {HttpParams} from './params';
import {fromBase64, toBase64} from './util';

/**
* Options to configure how TransferCache should be used to cache requests made via HttpClient.
Expand Down Expand Up @@ -267,32 +266,21 @@ export function transferCacheInterceptorFn(
if (typeof ngServerMode !== 'undefined' && ngServerMode) {
// Request not found in cache. Make the request and cache it if on the server.
return event$.pipe(
concatMap(async (event: HttpEvent<unknown>) => {
tap((event: HttpEvent<unknown>) => {
// Only cache successful HTTP responses.
if (event instanceof HttpResponse) {
let body = event.body;
if (req.responseType === 'blob') {
// Note: Blob is converted to ArrayBuffer because Uint8Array constructor
// doesn't accept Blob directly, which would result in an empty array.
// Type assertion is safe here: when responseType is 'blob', the body is guaranteed to be a Blob
const arrayBuffer = await (event.body as Blob).arrayBuffer();
body = toBase64(arrayBuffer);
} else if (req.responseType === 'arraybuffer') {
// For arraybuffer, convert to base64; for other types (json, text), store as-is.
// Type assertion is safe here: when responseType is 'arraybuffer', the body is
// guaranteed to be an ArrayBuffer
body = toBase64(event.body as ArrayBufferLike);
}
transferState.set<TransferHttpResponse>(storeKey, {
[BODY]: body,
[BODY]:
req.responseType === 'arraybuffer' || req.responseType === 'blob'
? toBase64(event.body)
: event.body,
[HEADERS]: getFilteredHeaders(event.headers, headersToInclude),
[STATUS]: event.status,
[STATUS_TEXT]: event.statusText,
[REQ_URL]: requestUrl,
[RESPONSE_TYPE]: req.responseType,
});
}
return event;
}),
);
}
Expand Down Expand Up @@ -372,6 +360,28 @@ function generateHash(value: string): string {
return hash.toString();
}

function toBase64(buffer: unknown): string {
//TODO: replace with when is Baseline widely available
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array/toBase64
const bytes = new Uint8Array(buffer as ArrayBufferLike);

const CHUNK_SIZE = 0x8000; // 32,768 bytes (~32 KB) per chunk, to avoid stack overflow

let binaryString = '';

for (let i = 0; i < bytes.length; i += CHUNK_SIZE) {
const chunk = bytes.subarray(i, i + CHUNK_SIZE);
binaryString += String.fromCharCode.apply(null, chunk as unknown as number[]);
}
return btoa(binaryString);
}

function fromBase64(base64: string): ArrayBuffer {
const binary = atob(base64);
const bytes = Uint8Array.from(binary, (c) => c.charCodeAt(0));
return bytes.buffer;
}

/**
* Returns the DI providers needed to enable HTTP transfer cache.
*
Expand Down
47 changes: 0 additions & 47 deletions packages/common/http/src/util.ts

This file was deleted.

Loading