diff --git a/modules/common/clover/src/transfer-http-cache/transfer-http-cache.interceptor.ts b/modules/common/clover/src/transfer-http-cache/transfer-http-cache.interceptor.ts index 94e838fdf..803762614 100644 --- a/modules/common/clover/src/transfer-http-cache/transfer-http-cache.interceptor.ts +++ b/modules/common/clover/src/transfer-http-cache/transfer-http-cache.interceptor.ts @@ -20,12 +20,15 @@ import { StateKey, TransferState, makeStateKey } from '@angular/platform-browser import { Observable, of } from 'rxjs'; import { filter, take, tap } from 'rxjs/operators'; +type ResponseType = HttpRequest['responseType']; + interface TransferHttpResponse { body?: any | null; headers?: Record; status?: number; statusText?: string; url?: string; + responseType?: ResponseType; } @Injectable() @@ -36,6 +39,7 @@ export class TransferHttpCacheInterceptor implements HttpInterceptor { method: string, url: string, params: HttpParams, + responseType?: ResponseType, ): StateKey { // make the params encoded same as a url so it's easy to identify const encodedParams = params @@ -43,7 +47,8 @@ export class TransferHttpCacheInterceptor implements HttpInterceptor { .sort() .map((k) => `${k}=${params.getAll(k)}`) .join('&'); - const key = (method === 'GET' ? 'G.' : 'H.') + url + '?' + encodedParams; + + const key = (method === 'GET' ? 'G.' : 'H.') + responseType + '.' + url + '?' + encodedParams; return makeStateKey(key); } @@ -66,15 +71,38 @@ export class TransferHttpCacheInterceptor implements HttpInterceptor { return next.handle(req); } - const storeKey = this.makeCacheKey(req.method, req.url, req.params); + const storeKey = this.makeCacheKey(req.method, req.url, req.params, req.responseType); if (this.transferState.hasKey(storeKey)) { // Request found in cache. Respond using it. const response = this.transferState.get(storeKey, {}); + let body: ArrayBuffer | Blob | string | undefined = response.body; + + switch (response.responseType) { + case 'arraybuffer': + { + // If we're in Node... + if (typeof Buffer !== 'undefined') { + const buf = Buffer.from(response.body); + body = new ArrayBuffer(buf.length); + const view = new Uint8Array(body); + for (let i = 0; i < buf.length; ++i) { + view[i] = buf[i]; + } + } else if (typeof TextEncoder !== 'undefined') { + // Modern browsers implement TextEncode. + body = new TextEncoder().encode(response.body).buffer; + } + } + break; + case 'blob': + body = new Blob([response.body]); + break; + } return of( new HttpResponse({ - body: response.body, + body, headers: new HttpHeaders(response.headers), status: response.status, statusText: response.statusText, @@ -95,6 +123,7 @@ export class TransferHttpCacheInterceptor implements HttpInterceptor { status: event.status, statusText: event.statusText, url: event.url ?? '', + responseType: req.responseType, }); } }), diff --git a/modules/common/spec/transfer_http.spec.ts b/modules/common/spec/transfer_http.spec.ts index 4882c7574..0a7f15eff 100644 --- a/modules/common/spec/transfer_http.spec.ts +++ b/modules/common/spec/transfer_http.spec.ts @@ -32,8 +32,9 @@ describe('TransferHttp', () => { 'GET', 'https://google.com/api', new HttpParams().append('foo', 'bar'), + 'text', ); - expect(key).toEqual('G.https://google.com/api?foo=bar'); + expect(key).toEqual('G.text.https://google.com/api?foo=bar'); }); it('should sort the keys by unicode points', () => { const interceptor = new TransferHttpCacheInterceptor(mockAppRef(), mockTransferState()); @@ -41,8 +42,9 @@ describe('TransferHttp', () => { 'GET', 'https://google.com/api', new HttpParams().append('b', 'foo').append('a', 'bar'), + 'text', ); - expect(key).toEqual('G.https://google.com/api?a=bar&b=foo'); + expect(key).toEqual('G.text.https://google.com/api?a=bar&b=foo'); }); it('should make equal keys if order of params changes', () => { const interceptor = new TransferHttpCacheInterceptor(mockAppRef(), mockTransferState()); @@ -50,11 +52,13 @@ describe('TransferHttp', () => { 'GET', 'https://google.com/api', new HttpParams().append('a', 'bar').append('b', 'foo'), + 'text', ); const key2 = interceptor['makeCacheKey']( 'GET', 'https://google.com/api', new HttpParams().append('b', 'foo').append('a', 'bar'), + 'text', ); expect(key1).toEqual(key2); }); @@ -64,8 +68,9 @@ describe('TransferHttp', () => { 'GET', 'https://google.com/api', new HttpParams().append('b', 'xyz').append('a', 'foo').append('a', 'bar'), + 'text', ); - expect(key).toEqual('G.https://google.com/api?a=foo,bar&b=xyz'); + expect(key).toEqual('G.text.https://google.com/api?a=foo,bar&b=xyz'); }); }); }); diff --git a/modules/common/src/transfer_http.ts b/modules/common/src/transfer_http.ts index e119f3bf1..d29197101 100644 --- a/modules/common/src/transfer_http.ts +++ b/modules/common/src/transfer_http.ts @@ -26,12 +26,15 @@ import { import { Observable, of as observableOf } from 'rxjs'; import { filter, take, tap } from 'rxjs/operators'; +type ResponseType = HttpRequest['responseType']; + export interface TransferHttpResponse { body?: any | null; headers?: Record; status?: number; statusText?: string; url?: string; + responseType?: ResponseType; } function getHeadersMap(headers: HttpHeaders): Record { @@ -60,6 +63,7 @@ export class TransferHttpCacheInterceptor implements HttpInterceptor { method: string, url: string, params: HttpParams, + responseType: ResponseType, ): StateKey { // make the params encoded same as a url so it's easy to identify const encodedParams = params @@ -67,7 +71,8 @@ export class TransferHttpCacheInterceptor implements HttpInterceptor { .sort() .map((k) => `${k}=${params.getAll(k)}`) .join('&'); - const key = (method === 'GET' ? 'G.' : 'H.') + url + '?' + encodedParams; + + const key = (method === 'GET' ? 'G.' : 'H.') + responseType + '.' + url + '?' + encodedParams; return makeStateKey(key); } @@ -97,15 +102,38 @@ export class TransferHttpCacheInterceptor implements HttpInterceptor { return next.handle(req); } - const storeKey = this.makeCacheKey(req.method, req.url, req.params); + const storeKey = this.makeCacheKey(req.method, req.url, req.params, req.responseType); if (this.transferState.hasKey(storeKey)) { // Request found in cache. Respond using it. const response = this.transferState.get(storeKey, {}); + let body: ArrayBuffer | Blob | string | undefined = response.body; + + switch (response.responseType) { + case 'arraybuffer': + { + // If we're in Node... + if (typeof Buffer !== 'undefined') { + const buf = Buffer.from(response.body); + body = new ArrayBuffer(buf.length); + const view = new Uint8Array(body); + for (let i = 0; i < buf.length; ++i) { + view[i] = buf[i]; + } + } else if (typeof TextEncoder !== 'undefined') { + // Modern browsers implement TextEncode. + body = new TextEncoder().encode(response.body).buffer; + } + } + break; + case 'blob': + body = new Blob([response.body]); + break; + } return observableOf( new HttpResponse({ - body: response.body, + body, headers: new HttpHeaders(response.headers), status: response.status, statusText: response.statusText, @@ -125,6 +153,7 @@ export class TransferHttpCacheInterceptor implements HttpInterceptor { status: event.status, statusText: event.statusText, url: event.url || '', + responseType: req.responseType, }); } }),