refactor(http): Improves base64 encoding/decoding with feature detection (#67002)

Use feature detection for `Uint8Array.prototype.toBase64` and
`Uint8Array.fromBase64`, falling back to the existing implementation
when native support is not available

PR Close #67002
This commit is contained in:
SkyZeroZx 2026-02-11 11:59:16 -05:00 committed by Jessica Janiuk
parent d9e40cb456
commit aafeb1d2bd
3 changed files with 50 additions and 23 deletions

View file

@ -29,6 +29,7 @@ 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.
@ -272,7 +273,7 @@ export function transferCacheInterceptorFn(
transferState.set<TransferHttpResponse>(storeKey, {
[BODY]:
req.responseType === 'arraybuffer' || req.responseType === 'blob'
? toBase64(event.body)
? toBase64(event.body as ArrayBufferLike)
: event.body,
[HEADERS]: getFilteredHeaders(event.headers, headersToInclude),
[STATUS]: event.status,
@ -360,28 +361,6 @@ 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.
*

View file

@ -0,0 +1,47 @@
/**
* @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
*/
// TODO: Replace this fallback once widely available.
// See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array/toBase64
type Uint8ArrayWithToBase64 = Uint8Array & {toBase64(): string};
type Uint8ArrayCtorWithFromBase64 = typeof Uint8Array & {fromBase64(base64: string): Uint8Array};
function hasToBase64(u8: Uint8Array): u8 is Uint8ArrayWithToBase64 {
return typeof (u8 as Uint8ArrayWithToBase64).toBase64 === 'function';
}
function hasFromBase64(ctor: typeof Uint8Array): ctor is Uint8ArrayCtorWithFromBase64 {
return typeof (ctor as Uint8ArrayCtorWithFromBase64).fromBase64 === 'function';
}
export function toBase64(buffer: ArrayBufferLike): string {
const bytes = new Uint8Array(buffer);
if (hasToBase64(bytes)) {
return bytes.toBase64();
}
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);
}
export function fromBase64(base64: string): ArrayBuffer {
if (hasFromBase64(Uint8Array)) {
return Uint8Array.fromBase64(base64).buffer as ArrayBuffer;
}
const binary = atob(base64);
const bytes = Uint8Array.from(binary, (c) => c.charCodeAt(0));
return bytes.buffer as ArrayBuffer;
}

View file

@ -531,6 +531,7 @@
"hasApplyArgsData",
"hasAuthHeaders",
"hasDeps",
"hasFromBase64",
"hasInSkipHydrationBlockFlag",
"hasLift",
"hasMatchingDehydratedView",