Skip to content

Commit bd809c2

Browse files
committed
refactor(http): Improves base64 encoding/decoding with feature detection
Use feature detection for `Uint8Array.prototype.toBase64` and `Uint8Array.fromBase64`, falling back to the existing implementation when native support is not available
1 parent 0f47fda commit bd809c2

File tree

3 files changed

+49
-22
lines changed

3 files changed

+49
-22
lines changed

packages/common/http/src/transfer_cache.ts

Lines changed: 1 addition & 22 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.
@@ -316,28 +317,6 @@ function generateHash(value: string): string {
316317
return hash.toString();
317318
}
318319

319-
function toBase64(buffer: unknown): string {
320-
//TODO: replace with when is Baseline widely available
321-
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array/toBase64
322-
const bytes = new Uint8Array(buffer as ArrayBufferLike);
323-
324-
const CHUNK_SIZE = 0x8000; // 32,768 bytes (~32 KB) per chunk, to avoid stack overflow
325-
326-
let binaryString = '';
327-
328-
for (let i = 0; i < bytes.length; i += CHUNK_SIZE) {
329-
const chunk = bytes.subarray(i, i + CHUNK_SIZE);
330-
binaryString += String.fromCharCode.apply(null, chunk as unknown as number[]);
331-
}
332-
return btoa(binaryString);
333-
}
334-
335-
function fromBase64(base64: string): ArrayBuffer {
336-
const binary = atob(base64);
337-
const bytes = Uint8Array.from(binary, (c) => c.charCodeAt(0));
338-
return bytes.buffer;
339-
}
340-
341320
/**
342321
* Returns the DI providers needed to enable HTTP transfer cache.
343322
*

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: unknown): string {
24+
const bytes = new Uint8Array(buffer as ArrayBufferLike);
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
@@ -529,6 +529,7 @@
529529
"hasApplyArgsData",
530530
"hasAuthHeaders",
531531
"hasDeps",
532+
"hasFromBase64",
532533
"hasInSkipHydrationBlockFlag",
533534
"hasLift",
534535
"hasMatchingDehydratedView",

0 commit comments

Comments
 (0)