diff --git a/packages/common/http/src/transfer_cache.ts b/packages/common/http/src/transfer_cache.ts index 575ce9ffe73..b71d25e3b7c 100644 --- a/packages/common/http/src/transfer_cache.ts +++ b/packages/common/http/src/transfer_cache.ts @@ -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(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. * diff --git a/packages/common/http/src/util.ts b/packages/common/http/src/util.ts new file mode 100644 index 00000000000..2c0e739d280 --- /dev/null +++ b/packages/common/http/src/util.ts @@ -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; +} diff --git a/packages/core/test/bundling/hydration/bundle.golden_symbols.json b/packages/core/test/bundling/hydration/bundle.golden_symbols.json index fa9196f4153..6fbbcbf38cd 100644 --- a/packages/core/test/bundling/hydration/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hydration/bundle.golden_symbols.json @@ -531,6 +531,7 @@ "hasApplyArgsData", "hasAuthHeaders", "hasDeps", + "hasFromBase64", "hasInSkipHydrationBlockFlag", "hasLift", "hasMatchingDehydratedView",