import {Inject, Injectable, Optional} from '@angular/core';
import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse} from '@angular/common/http';
import {Observable, of} from 'rxjs';
import * as hash from 'object-hash'
import {tap} from "rxjs/operators";
import {CachingHeaders} from "./caching-headers.enum";
import {CachingStore} from "./caching-store.enum";
import {P3X_HTTP_CACHE_CONFIG} from "./http-cache-config.token";
import {HttpCacheConfig} from "./http-cache-config";
const hashOptions = {
algorithm: 'md5',
encoding: 'hex'
}
const globalCache = new Map<string, any>()
@Injectable()
export class HttpCacheInterceptorInterceptor implements HttpInterceptor {
private cachedData = new Map<string, any>()
httpCacheConfig : HttpCacheConfig = {
behavior: CachingHeaders.Cache,
store: CachingStore.Global,
}
getCache(key: string) {
if (this.httpCacheConfig.store === CachingStore.Global) {
return globalCache.get(key)
} else {
return this.cachedData.get(key)
}
}
setCache(key: string, value: any) {
if (this.httpCacheConfig.store === CachingStore.Global) {
globalCache.set(key, value)
} else {
this.cachedData.set(key, value)
}
}
constructor(@Inject(P3X_HTTP_CACHE_CONFIG) @Optional() httpCacheConfigToken: HttpCacheConfig) {
if (httpCacheConfigToken) {
this.httpCacheConfig = httpCacheConfigToken
}
}
httpToKey(httpRequest: HttpRequest<any>) {
const key = httpRequest.method + '@' + httpRequest.urlWithParams + '@' + hash(httpRequest.params, hashOptions) + '@' + hash(httpRequest.body, hashOptions)
return key
}
intercept(httpRequest: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
//console.log(httpRequest)
//console.log('has', httpRequest.headers.has(CachingHeaders.NoCache))
//console.log('value', httpRequest.headers.get(CachingHeaders.NoCache))
const forcedCache = httpRequest.headers.get(CachingHeaders.Cache) !== null
const forcedNoneCache = httpRequest.headers.get(CachingHeaders.NoCache) !== null
//console.log('forcedCache', forcedCache, 'forcedNoneCache', forcedNoneCache)
let headers = httpRequest.headers.delete(CachingHeaders.NoCache)
headers = headers.delete(CachingHeaders.Cache)
httpRequest = httpRequest.clone({
headers: headers
})
if (forcedCache && forcedNoneCache) {
throw new Error('You cannot use cache and non-cache header at once!')
} else if (forcedNoneCache || (this.httpCacheConfig.behavior === CachingHeaders.NoCache && !forcedCache)) {
return next.handle(httpRequest);
} else if (forcedCache || (this.httpCacheConfig.behavior === CachingHeaders.Cache && !forcedNoneCache)) {
// Checked if there is cached data for this URI
const key = this.httpToKey(httpRequest)
const lastResponse = this.getCache(key);
if (lastResponse) {
// In case of parallel requests to same URI,
// return the request already in progress
// otherwise return the last cached data
//console.info('http cache interceptor hit cache', key)
return (lastResponse instanceof Observable)
? lastResponse : of(lastResponse.clone());
}
//console.info('http cache interceptor', key)
// If the request of going through for first time
// then let the request proceed and cache the response
const requestHandle = next.handle(httpRequest).pipe(
tap((stateEvent: any) => {
if (stateEvent instanceof HttpResponse) {
this.setCache(
key,
stateEvent.clone()
);
}
})
)
// Meanwhile cache the request Observable to handle parallel request
//this.cachedData.set(key, requestHandle);
return requestHandle;
} else {
console.error(this.httpCacheConfig)
console.error(httpRequest.headers)
throw new Error('There is a configuration in your setup')
}
/*
// Also leave scope of resetting already cached data for a URI
if (httpRequest.headers.get("reset-cache")) {
this.cachedData.delete(httpRequest.urlWithParams);
}
*/
}
}