feat: Add caching mechanism for API requests

This commit is contained in:
nick-delirium 2024-08-30 15:48:19 +02:00
parent 3dbd7f5575
commit 60940d133a
No known key found for this signature in database
GPG key ID: 93ABD695DF5FDBA0
2 changed files with 115 additions and 2 deletions

View file

@ -56,8 +56,10 @@ export default class APIClient {
private init: RequestInit;
private readonly siteId: string | undefined;
private refreshingTokenPromise: Promise<string> | null = null;
private cacheManager = new CacheManager();
constructor() {
this.cacheManager.init();
const jwt = store.getState().getIn(['user', 'jwt']);
const siteId = store.getState().getIn(['site', 'siteId']);
this.init = {
@ -234,4 +236,115 @@ export default class APIClient {
this.init.method = 'PATCH';
return this.fetch(path, params, 'PATCH');
}
async cachedPost(path: string, params?: any, options?: any, headers?: Record<string, any>): Promise<Response> {
if (params.startTimestamp) {
params.startTimestamp = roundToFiveMinutes(params.startTimestamp);
}
if (params.endTimestamp) {
params.endTimestamp = roundToFiveMinutes(params.endTimestamp);
}
const bodyHash = params ? simpleHash(JSON.stringify(params)) : 0;
const cacheKey = `${path}-${bodyHash}`;
const cachedResponse = await this.cacheManager.get(cacheKey);
if (cachedResponse) {
return cachedResponse;
}
const response = await this.post(path, params, options, headers);
if (response.ok) {
this.cacheManager.set(cacheKey, response.clone());
}
return response;
}
async cachedGet(path: string, params?: any, options?: any, headers?: Record<string, any>): Promise<Response> {
if (params.startTimestamp) {
params.startTimestamp = roundToFiveMinutes(params.startTimestamp);
}
if (params.endTimestamp) {
params.endTimestamp = roundToFiveMinutes(params.endTimestamp);
}
const bodyHash = params ? simpleHash(JSON.stringify(params)) : 0;
const cacheKey = `${path}-${bodyHash}`;
const cachedResponse = await this.cacheManager.get(cacheKey);
if (cachedResponse) {
return cachedResponse;
}
const response = await this.post(path, params, options, headers);
if (response.ok) {
this.cacheManager.set(cacheKey, response.clone());
}
return response;
}
}
function simpleHash(str: string): number {
let hash = 5381;
let i = str.length;
while (i) {
hash = (hash * 33) ^ str.charCodeAt(--i);
}
return hash >>> 0;
}
function roundToFiveMinutes(timestamp: number): number {
const minutes = 5 * 60 * 1000;
return Math.floor(timestamp / minutes) * minutes;
}
class CacheManager {
private cache: Cache | null = null;
private invalidationTracker = new Map<string, number>();
async init() {
this.cache = await caches.open('or-req-cache');
this.restoreInvalidationTracker();
}
saveInvalidationTracker() {
localStorage.setItem('invalidationTracker', JSON.stringify(Array.from(this.invalidationTracker.entries())));
}
restoreInvalidationTracker() {
const tracker = localStorage.getItem('invalidationTracker');
if (tracker) {
this.invalidationTracker = new Map(JSON.parse(tracker));
}
}
async get(key: string): Promise<Response | null> {
if (!this.cache) {
return null;
}
if (this.invalidationTracker.has(key)) {
const ts = this.invalidationTracker.get(key);
if (!ts) {
return null;
}
if (Date.now() - ts > 5 * 60 * 1000) {
this.cache.delete(key);
this.invalidationTracker.delete(key);
this.saveInvalidationTracker();
return null;
}
}
const result = await this.cache.match(key)
return result ?? null;
}
async set(key: string, response: Response) {
if (!this.cache) {
return;
}
this.invalidationTracker.set(key, Date.now());
this.saveInvalidationTracker();
this.cache.put(key, response.clone());
}
}

View file

@ -86,7 +86,7 @@ export default class MetricService {
data.metricOf = 'sessionCount';
}
try {
const r = await this.client.post(path, data);
const r = await this.client.cachedPost(path, data);
const response = await r.json();
return response.data || {};
} catch (e) {
@ -114,7 +114,7 @@ export default class MetricService {
filter.filters = drillDownFilter;
}
let resp: Response = await this.client.post(`/cards/try/issues`, filter);
let resp: Response = await this.client.cachedPost(`/cards/try/issues`, filter);
const json: any = await resp.json();
return await json.data || {};
}