@akanass/ng-universal-transfer-http
Version:
TransferHttpCacheModule installs a Http interceptor that avoids duplicate HttpClient requests on the client
791 lines (785 loc) • 23.4 kB
JavaScript
import { InjectionToken, Injectable, Optional, Inject, ApplicationRef, PLATFORM_ID, NgModule } from '@angular/core';
import { HttpResponse, HttpHeaders, HttpErrorResponse, HTTP_INTERCEPTORS } from '@angular/common/http';
import { makeStateKey, TransferState, BrowserTransferStateModule } from '@angular/platform-browser';
import { isPlatformServer } from '@angular/common';
import createHash from 'create-hash';
import { stringify } from 'flatted/esm';
import { of, merge, from, throwError } from 'rxjs';
import { flatMap, filter, tap, first, map, defaultIfEmpty, toArray } from 'rxjs/operators';
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/** @type {?} */
const NG_UNIVERSAL_TRANSFER_HTTP_CONFIG = new InjectionToken('ng_universal_transfer_http_config');
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
class TransferHttpCacheConfigService {
/**
* Class constructor
* @param {?} _transferHttpCacheConfig
*/
constructor(_transferHttpCacheConfig) {
this._transferHttpCacheConfig = _transferHttpCacheConfig;
this._config = { prodMode: true };
if (this._transferHttpCacheConfig !== null) {
Object.assign(this._config, this._transferHttpCacheConfig);
}
}
/**
* Returns private property _config
* @return {?}
*/
get config() {
return this._config;
}
}
TransferHttpCacheConfigService.decorators = [
{ type: Injectable }
];
/** @nocollapse */
TransferHttpCacheConfigService.ctorParameters = () => [
{ type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [NG_UNIVERSAL_TRANSFER_HTTP_CONFIG,] }] }
];
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
class TransferHttpCacheInterceptor {
/**
* Class constructor
* @param {?} _appRef
* @param {?} _transferState
* @param {?} _platformId
* @param {?} _configService
*/
constructor(_appRef, _transferState, _platformId, _configService) {
this._appRef = _appRef;
this._transferState = _transferState;
this._platformId = _platformId;
this._configService = _configService;
this._id = 0;
this._serverStateDataStoreKey = makeStateKey('server_state_data');
this._lastIdStoreKey = makeStateKey('server_state_last_id');
this._isCacheActivatedStoreKey = makeStateKey('is_cache_activated');
this._initCacheProcess();
}
/**
* Initialize cache process
* @private
* @return {?}
*/
_initCacheProcess() {
// initialize cache flag for the current platform
of(of(isPlatformServer(this._platformId)))
.pipe(flatMap((/**
* @param {?} isServer
* @return {?}
*/
(isServer) => merge(isServer
.pipe(filter((/**
* @param {?} _
* @return {?}
*/
_ => !!_)), tap((/**
* @param {?} _
* @return {?}
*/
_ => this._isCacheActivated = _)), tap((/**
* @param {?} _
* @return {?}
*/
_ => this._transferState.set(this._isCacheActivatedStoreKey, _)))), isServer
.pipe(filter((/**
* @param {?} _
* @return {?}
*/
_ => !_)), tap((/**
* @param {?} _
* @return {?}
*/
_ => this._transferState.hasKey(this._isCacheActivatedStoreKey) ?
this._isCacheActivated = this._transferState.get(this._isCacheActivatedStoreKey, true) :
this._isCacheActivated = _)))))), flatMap((/**
* @return {?}
*/
() =>
// Stop using the cache if the application has stabilized, indicating initial rendering is complete
// or if we are in development mode.
merge(of(this._configService.config.prodMode)
.pipe(filter((/**
* @param {?} _
* @return {?}
*/
_ => !_)), tap((/**
* @return {?}
*/
() => console.log('TransferHttpCacheModule is in the development mode. ' +
'Enable the production mode with Server Side Rendering.')))), this._appRef.isStable
.pipe(filter((/**
* @param {?} _
* @return {?}
*/
_ => !!_))))
.pipe(first())))).subscribe((/**
* @return {?}
*/
() => this._isCacheActivated = false));
}
/**
* Interceptor process
* @param {?} req
* @param {?} next
* @return {?}
*/
intercept(req, next) {
return of(of(this._isCacheActivated))
.pipe(flatMap((/**
* @param {?} isCacheActivated
* @return {?}
*/
(isCacheActivated) => merge(isCacheActivated
.pipe(filter((/**
* @param {?} _
* @return {?}
*/
_ => !_)), flatMap((/**
* @return {?}
*/
() => next.handle(req)
.pipe(tap((/**
* @return {?}
*/
() => this._cleanServerState())))))), isCacheActivated
.pipe(filter((/**
* @param {?} _
* @return {?}
*/
_ => !!_)), flatMap((/**
* @return {?}
*/
() => this._transferStateProcess(req, next))))))));
}
/**
* Function to clean all data in server state
* @private
* @return {?}
*/
_cleanServerState() {
merge(this._getLastId(false)
.pipe(tap((/**
* @return {?}
*/
() => this._transferState.remove(this._lastIdStoreKey)))), this._getServerStateData(false)
.pipe(tap((/**
* @param {?} _
* @return {?}
*/
_ => _.forEach((/**
* @param {?} __
* @return {?}
*/
__ => this._transferState.remove(makeStateKey(this._createHash(`${__.reqKey}_${__.id}`))))))), tap((/**
* @return {?}
*/
() => this._transferState.remove(this._serverStateDataStoreKey)))), of(of(this._transferState.hasKey(this._isCacheActivatedStoreKey)))
.pipe(flatMap((/**
* @param {?} hasKey
* @return {?}
*/
(hasKey) => hasKey
.pipe(filter((/**
* @param {?} _
* @return {?}
*/
_ => !!_)), tap((/**
* @return {?}
*/
() => this._transferState.remove(this._isCacheActivatedStoreKey))))))))
.subscribe((/**
* @return {?}
*/
() => undefined), (/**
* @param {?} e
* @return {?}
*/
e => {
throw (e);
}));
}
/**
* Transfer state process
* @private
* @param {?} req
* @param {?} next
* @return {?}
*/
_transferStateProcess(req, next) {
return this._createKey(req)
.pipe(flatMap((/**
* @param {?} storeKey
* @return {?}
*/
storeKey => of(of(this._transferState.hasKey(storeKey)))
.pipe(flatMap((/**
* @param {?} hasKey
* @return {?}
*/
hasKey => merge(this._hasKeyProcess(hasKey, storeKey), this._hasNotKeyProcess(req, next, hasKey, storeKey))))))));
}
/**
* Creates transfer state key's store
* @private
* @param {?} req
* @return {?}
*/
_createKey(req) {
this._id++;
return of(of(isPlatformServer(this._platformId)))
.pipe(flatMap((/**
* @param {?} isServer
* @return {?}
*/
(isServer) => merge(isServer
.pipe(filter((/**
* @param {?} _
* @return {?}
*/
_ => !!_)), flatMap((/**
* @return {?}
*/
() => this._serverKey(req)))), isServer
.pipe(filter((/**
* @param {?} _
* @return {?}
*/
_ => !_)), flatMap((/**
* @return {?}
*/
() => this._clientKey(req))))))));
}
/**
* Function to get state data and create client key for current request
* @private
* @param {?} req
* @return {?}
*/
_clientKey(req) {
return this._requestFormatted(req)
.pipe(map((/**
* @param {?} _
* @return {?}
*/
(_) => this._createHash(stringify(_)))), flatMap((/**
* @param {?} reqKey
* @return {?}
*/
reqKey => this._getServerStateData()
.pipe(flatMap((/**
* @param {?} _
* @return {?}
*/
_ => from(_))), filter((/**
* @param {?} _
* @return {?}
*/
_ => _.reqKey === reqKey)), defaultIfEmpty(undefined), flatMap((/**
* @param {?} _
* @return {?}
*/
_ => !!_ ?
of(_) :
throwError(new Error('Request missing in server state data')))), flatMap((/**
* @param {?} serverState
* @return {?}
*/
serverState => this._getLastId()
.pipe(flatMap((/**
* @param {?} _
* @return {?}
*/
_ => !_ || this._id > _ ?
throwError(new Error('Wrong id for server state data')) :
of(this._id))), map((/**
* @param {?} _
* @return {?}
*/
_ => _ === serverState.id ? _ : serverState.id)), map((/**
* @param {?} id
* @return {?}
*/
id => this._createHash(`${reqKey}_${id}`))), map((/**
* @param {?} key
* @return {?}
*/
key => makeStateKey(key))))))))));
}
/**
* Function to get last id from server
* @private
* @param {?=} _throwError
* @return {?}
*/
_getLastId(_throwError = true) {
return of(this._lastIdStoreKey)
.pipe(flatMap((/**
* @param {?} storeKey
* @return {?}
*/
storeKey => of(of(this._transferState.hasKey(storeKey)))
.pipe(flatMap((/**
* @param {?} hasKey
* @return {?}
*/
hasKey => merge(hasKey
.pipe(filter((/**
* @param {?} _
* @return {?}
*/
_ => !_)), flatMap((/**
* @return {?}
*/
() => of(_throwError)
.pipe(filter((/**
* @param {?} __
* @return {?}
*/
__ => !!__)), flatMap((/**
* @return {?}
*/
() => throwError(new Error('Missing server state last id')))))))), hasKey
.pipe(filter((/**
* @param {?} _
* @return {?}
*/
_ => !!_)), map((/**
* @return {?}
*/
() => this._transferState.get(storeKey, 0)))))))))));
}
/**
* Function to get server state data
* @private
* @param {?=} _throwError
* @return {?}
*/
_getServerStateData(_throwError = true) {
return of(this._serverStateDataStoreKey)
.pipe(flatMap((/**
* @param {?} storeKey
* @return {?}
*/
storeKey => of(of(this._transferState.hasKey(storeKey)))
.pipe(flatMap((/**
* @param {?} hasKey
* @return {?}
*/
hasKey => merge(hasKey
.pipe(filter((/**
* @param {?} _
* @return {?}
*/
_ => !_)), flatMap((/**
* @return {?}
*/
() => of(_throwError)
.pipe(filter((/**
* @param {?} __
* @return {?}
*/
__ => !!__)), flatMap((/**
* @return {?}
*/
() => throwError(new Error('Missing server state data')))))))), hasKey
.pipe(filter((/**
* @param {?} _
* @return {?}
*/
_ => !!_)), map((/**
* @return {?}
*/
() => this._transferState.get(storeKey, (/** @type {?} */ ([])))))))))))));
}
/**
* Function to create server key and store state data for current request
* @private
* @param {?} req
* @return {?}
*/
_serverKey(req) {
return this._requestFormatted(req)
.pipe(map((/**
* @param {?} _
* @return {?}
*/
(_) => this._createHash(stringify(_)))), tap((/**
* @param {?} reqKey
* @return {?}
*/
reqKey => this._storeServerStateData(reqKey))), map((/**
* @param {?} reqKey
* @return {?}
*/
reqKey => this._createHash(`${reqKey}_${this._id}`))), map((/**
* @param {?} key
* @return {?}
*/
key => makeStateKey(key))));
}
/**
* Function to store server state data
* @private
* @param {?} reqKey
* @return {?}
*/
_storeServerStateData(reqKey) {
of(this._serverStateDataStoreKey)
.pipe(flatMap((/**
* @param {?} storeKey
* @return {?}
*/
storeKey => of(of(this._transferState.hasKey(storeKey)))
.pipe(flatMap((/**
* @param {?} hasKey
* @return {?}
*/
hasKey => merge(hasKey
.pipe(filter((/**
* @param {?} _
* @return {?}
*/
_ => !_)), map((/**
* @return {?}
*/
() => (/** @type {?} */ ([]))))), hasKey
.pipe(filter((/**
* @param {?} _
* @return {?}
*/
_ => !!_)), map((/**
* @return {?}
*/
() => this._transferState.get(storeKey, (/** @type {?} */ ([]))))), flatMap((/**
* @param {?} serverStateData
* @return {?}
*/
serverStateData => !!serverStateData.find((/**
* @param {?} _
* @return {?}
*/
_ => _.reqKey === reqKey)) ?
throwError(new Error('Request already stored in server state data')) :
of(serverStateData))))))), tap((/**
* @return {?}
*/
() => this._transferState.set(this._lastIdStoreKey, this._id)))))))
.subscribe((/**
* @param {?} serverStateData
* @return {?}
*/
serverStateData => this._transferState.set(this._serverStateDataStoreKey, serverStateData.concat({
id: this._id,
reqKey
}))), (/**
* @param {?} e
* @return {?}
*/
e => {
throw (e);
}));
}
/**
* Process when key exists in transfer state
* @private
* @param {?} hasKey
* @param {?} storeKey
* @return {?}
*/
_hasKeyProcess(hasKey, storeKey) {
return hasKey
.pipe(filter((/**
* @param {?} _
* @return {?}
*/
_ => !!_)), map((/**
* @return {?}
*/
() => of(this._transferState.get(storeKey, (/** @type {?} */ ({})))))), flatMap((/**
* @param {?} obs
* @return {?}
*/
(obs) => merge(obs
.pipe(filter((/**
* @param {?} _
* @return {?}
*/
_ => _.status < 400)), map((/**
* @param {?} response
* @return {?}
*/
(response) => new HttpResponse({
body: response.body,
headers: new HttpHeaders(response.headers),
status: response.status,
statusText: response.statusText,
url: response.url,
})))), obs
.pipe(filter((/**
* @param {?} _
* @return {?}
*/
_ => _.status >= 400)), flatMap((/**
* @param {?} response
* @return {?}
*/
(response) => throwError(new HttpErrorResponse({
error: response.error,
headers: new HttpHeaders(response.headers),
status: response.status,
statusText: response.statusText,
url: response.url,
})))))))));
}
/**
* Process when key doesn't exist in transfer state
* @private
* @param {?} req
* @param {?} next
* @param {?} hasKey
* @param {?} storeKey
* @return {?}
*/
_hasNotKeyProcess(req, next, hasKey, storeKey) {
return hasKey
.pipe(filter((/**
* @param {?} _
* @return {?}
*/
_ => !_)), flatMap((/**
* @return {?}
*/
() => next.handle(req)
.pipe(tap((/**
* @param {?} event
* @return {?}
*/
(event) => of(event)
.pipe(filter((/**
* @param {?} evt
* @return {?}
*/
evt => evt instanceof HttpResponse)))
.subscribe((/**
* @param {?} evt
* @return {?}
*/
(evt) => this._transferState.set(storeKey, {
body: evt.body,
headers: this._getHeadersMap(evt.headers),
status: evt.status,
statusText: evt.statusText,
// tslint:disable-next-line:no-non-null-assertion
url: (/** @type {?} */ (evt.url)),
})))), (/**
* @param {?} error
* @return {?}
*/
(error) => of(error)
.pipe(filter((/**
* @param {?} err
* @return {?}
*/
err => err instanceof HttpErrorResponse)))
.subscribe((/**
* @param {?} err
* @return {?}
*/
(err) => this._transferState.set(storeKey, {
error: err.error,
headers: this._getHeadersMap(err.headers),
status: err.status,
statusText: err.statusText,
// tslint:disable-next-line:no-non-null-assertion
url: (/** @type {?} */ (err.url)),
})))))))));
}
/**
* Creates Headers Map
* @private
* @param {?} headers
* @return {?}
*/
_getHeadersMap(headers) {
// tslint:disable-next-line:no-non-null-assertion
return headers.keys().reduce((/**
* @param {?} acc
* @param {?} curr
* @return {?}
*/
(acc, curr) => Object.assign(acc, { [curr]: (/** @type {?} */ (headers.getAll(curr))) })), {});
}
/**
* Function to create sha256 hash
* @private
* @param {?} data
* @return {?}
*/
_createHash(data) {
return createHash('sha256').update(data).digest('hex');
}
/**
* Returns HttpRequest with value of header inside url & urlWithParams
* @private
* @param {?} req
* @param {?} headerName
* @return {?}
*/
_replaceWithHeader(req, headerName) {
return of(of(this._getHeadersMap(req.headers)[headerName]))
.pipe(flatMap((/**
* @param {?} obs
* @return {?}
*/
(obs) => merge(obs.pipe(filter((/**
* @param {?} _
* @return {?}
*/
(_) => !!_ && !!_.length)), map((/**
* @param {?} _
* @return {?}
*/
(_) => of(_[_.length - 1]))), flatMap((/**
* @param {?} o
* @return {?}
*/
(o) => merge(o.pipe(filter((/**
* @param {?} _
* @return {?}
*/
_ => !!_)), flatMap((/**
* @param {?} headerValue
* @return {?}
*/
(headerValue) => merge(this._formatUrlWithHeaderValue(req.url, headerValue), this._formatUrlWithHeaderValue(req.urlWithParams, headerValue)).pipe(toArray(), map((/**
* @param {?} _
* @return {?}
*/
_ => (/** @type {?} */ (Object.assign({}, req, {
url: _[0],
urlWithParams: _[1]
}))))))))), o.pipe(filter((/**
* @param {?} _
* @return {?}
*/
_ => !_)), flatMap((/**
* @return {?}
*/
() => throwError(new Error(`Missing header '${headerName}' value inside request to generate state key`))))))))), obs.pipe(filter((/**
* @param {?} _
* @return {?}
*/
_ => !_ || !_.length)), flatMap((/**
* @return {?}
*/
() => throwError(new Error(`Missing header '${headerName}' value inside request to generate state key`)))))))));
}
/**
* Replace url with header value
* @private
* @param {?} url
* @param {?} headerValue
* @return {?}
*/
_formatUrlWithHeaderValue(url, headerValue) {
return of(url)
.pipe(map((/**
* @param {?} _
* @return {?}
*/
(_) => _.split('://')[1].split('/'))), map((/**
* @param {?} _
* @return {?}
*/
(_) => _.map((/**
* @param {?} s
* @param {?} i
* @return {?}
*/
(s, i) => i === 0 ? headerValue : s)))), map((/**
* @param {?} _
* @return {?}
*/
(_) => _.join('/'))));
}
/**
* Returns the good request object to create hash
* @private
* @param {?} req
* @return {?}
*/
_requestFormatted(req) {
return of(of(this._configService.config.headerNameToOverrideUrlInKeyCachingGeneration))
.pipe(flatMap((/**
* @param {?} obs
* @return {?}
*/
(obs) => merge(obs.pipe(filter((/**
* @param {?} _
* @return {?}
*/
_ => !!_)), flatMap((/**
* @param {?} _
* @return {?}
*/
_ => this._replaceWithHeader(req, _)))), obs.pipe(filter((/**
* @param {?} _
* @return {?}
*/
_ => !_)), map((/**
* @return {?}
*/
() => req)))))));
}
}
TransferHttpCacheInterceptor.decorators = [
{ type: Injectable }
];
/** @nocollapse */
TransferHttpCacheInterceptor.ctorParameters = () => [
{ type: ApplicationRef },
{ type: TransferState },
{ type: undefined, decorators: [{ type: Inject, args: [PLATFORM_ID,] }] },
{ type: TransferHttpCacheConfigService }
];
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
class TransferHttpCacheModule {
/**
* @param {?} config
* @return {?}
*/
static withConfig(config) {
return {
ngModule: TransferHttpCacheModule,
providers: [{
provide: NG_UNIVERSAL_TRANSFER_HTTP_CONFIG,
useValue: config
}]
};
}
}
TransferHttpCacheModule.decorators = [
{ type: NgModule, args: [{
imports: [BrowserTransferStateModule],
providers: [
TransferHttpCacheConfigService,
{ provide: HTTP_INTERCEPTORS, useClass: TransferHttpCacheInterceptor, multi: true },
],
},] }
];
export { TransferHttpCacheModule, TransferHttpCacheConfigService as ɵa, NG_UNIVERSAL_TRANSFER_HTTP_CONFIG as ɵb, TransferHttpCacheInterceptor as ɵc };
//# sourceMappingURL=akanass-ng-universal-transfer-http.js.map