Skip to content

Commit aafeb1d

Browse files
SkyZeroZxthePunderWoman
authored andcommitted
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
1 parent d9e40cb commit aafeb1d

File tree

3 files changed

+50
-23
lines changed

3 files changed

+50
-23
lines changed

packages/common/http/src/transfer_cache.ts

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {HTTP_ROOT_INTERCEPTOR_FNS, HttpHandlerFn} from './interceptor';
2929
import {HttpRequest} from './request';
3030
import {HttpEvent, HttpResponse} from './response';
3131
import {HttpParams} from './params';
32+
import {fromBase64, toBase64} from './util';
3233

3334
/**
3435
* Options to configure how TransferCache should be used to cache requests made via HttpClient.
@@ -272,7 +273,7 @@ export function transferCacheInterceptorFn(
272273
transferState.set<TransferHttpResponse>(storeKey, {
273274
[BODY]:
274275
req.responseType === 'arraybuffer' || req.responseType === 'blob'
275-
? toBase64(event.body)
276+
? toBase64(event.body as ArrayBufferLike)
276277
: event.body,
277278
[HEADERS]: getFilteredHeaders(event.headers, headersToInclude),
278279
[STATUS]: event.status,
@@ -360,28 +361,6 @@ function generateHash(value: string): string {
360361
return hash.toString();
361362
}
362363

363-
function toBase64(buffer: unknown): string {
364-
//TODO: replace with when is Baseline widely available
365-
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array/toBase64
366-
const bytes = new Uint8Array(buffer as ArrayBufferLike);
367-
368-
const CHUNK_SIZE = 0x8000; // 32,768 bytes (~32 KB) per chunk, to avoid stack overflow
369-
370-
let binaryString = '';
371-
372-
for (let i = 0; i < bytes.length; i += CHUNK_SIZE) {
373-
const chunk = bytes.subarray(i, i + CHUNK_SIZE);
374-
binaryString += String.fromCharCode.apply(null, chunk as unknown as number[]);
375-
}
376-
return btoa(binaryString);
377-
}
378-
379-
function fromBase64(base64: string): ArrayBuffer {
380-
const binary = atob(base64);
381-
const bytes = Uint8Array.from(binary, (c) => c.charCodeAt(0));
382-
return bytes.buffer;
383-
}
384-
385364
/**
386365
* Returns the DI providers needed to enable HTTP transfer cache.
387366
*

packages/common/http/src/util.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.dev/license
7+
*/
8+
9+
// TODO: Replace this fallback once widely available.
10+
// See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array/toBase64
11+
12+
type Uint8ArrayWithToBase64 = Uint8Array & {toBase64(): string};
13+
type Uint8ArrayCtorWithFromBase64 = typeof Uint8Array & {fromBase64(base64: string): Uint8Array};
14+
15+
function hasToBase64(u8: Uint8Array): u8 is Uint8ArrayWithToBase64 {
16+
return typeof (u8 as Uint8ArrayWithToBase64).toBase64 === 'function';
17+
}
18+
19+
function hasFromBase64(ctor: typeof Uint8Array): ctor is Uint8ArrayCtorWithFromBase64 {
20+
return typeof (ctor as Uint8ArrayCtorWithFromBase64).fromBase64 === 'function';
21+
}
22+
23+
export function toBase64(buffer: ArrayBufferLike): string {
24+
const bytes = new Uint8Array(buffer);
25+
26+
if (hasToBase64(bytes)) {
27+
return bytes.toBase64();
28+
}
29+
30+
const CHUNK_SIZE = 0x8000; // 32,768 bytes (~32 KB) per chunk, to avoid stack overflow
31+
let binaryString = '';
32+
for (let i = 0; i < bytes.length; i += CHUNK_SIZE) {
33+
const chunk = bytes.subarray(i, i + CHUNK_SIZE);
34+
binaryString += String.fromCharCode.apply(null, chunk as unknown as number[]);
35+
}
36+
return btoa(binaryString);
37+
}
38+
39+
export function fromBase64(base64: string): ArrayBuffer {
40+
if (hasFromBase64(Uint8Array)) {
41+
return Uint8Array.fromBase64(base64).buffer as ArrayBuffer;
42+
}
43+
44+
const binary = atob(base64);
45+
const bytes = Uint8Array.from(binary, (c) => c.charCodeAt(0));
46+
return bytes.buffer as ArrayBuffer;
47+
}

packages/core/test/bundling/hydration/bundle.golden_symbols.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,7 @@
531531
"hasApplyArgsData",
532532
"hasAuthHeaders",
533533
"hasDeps",
534+
"hasFromBase64",
534535
"hasInSkipHydrationBlockFlag",
535536
"hasLift",
536537
"hasMatchingDehydratedView",

0 commit comments

Comments
 (0)