feat: add cache, fix ui
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
export interface SWRCacheOptions {
|
||||
/**
|
||||
* Data becomes stale after this duration.
|
||||
* Stale data is returned immediately while a refresh happens.
|
||||
*
|
||||
* Default: 60 seconds
|
||||
*/
|
||||
minTimeToStale?: number;
|
||||
|
||||
/**
|
||||
* Data is completely discarded after this duration.
|
||||
* Requests wait for a fresh fetch.
|
||||
*
|
||||
* Default: Infinity
|
||||
*/
|
||||
maxTimeToLive?: number;
|
||||
}
|
||||
|
||||
export interface SWRCacheResult<T> {
|
||||
value: T;
|
||||
stale: boolean;
|
||||
updatedAt: number;
|
||||
}
|
||||
|
||||
interface CacheEntry<T> {
|
||||
value?: T;
|
||||
updatedAt: number;
|
||||
|
||||
promise?: Promise<T>;
|
||||
revalidating: boolean;
|
||||
}
|
||||
|
||||
export class SWRCache {
|
||||
private cache = new Map<string, CacheEntry<any>>();
|
||||
|
||||
async get<T>(
|
||||
key: string,
|
||||
fetcher: () => Promise<T>,
|
||||
options: SWRCacheOptions = {},
|
||||
): Promise<SWRCacheResult<T>> {
|
||||
const {
|
||||
minTimeToStale = 60_000,
|
||||
maxTimeToLive = Number.POSITIVE_INFINITY,
|
||||
} = options;
|
||||
|
||||
const now = Date.now();
|
||||
|
||||
let entry = this.cache.get(key) as CacheEntry<T> | undefined;
|
||||
|
||||
// No cache
|
||||
if (!entry) {
|
||||
entry = {
|
||||
updatedAt: now,
|
||||
revalidating: false,
|
||||
};
|
||||
|
||||
entry.promise = fetcher().then((value) => {
|
||||
entry!.value = value;
|
||||
entry!.updatedAt = Date.now();
|
||||
entry!.promise = undefined;
|
||||
return value;
|
||||
});
|
||||
|
||||
this.cache.set(key, entry);
|
||||
|
||||
return {
|
||||
value: await entry.promise,
|
||||
stale: false,
|
||||
updatedAt: entry.updatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
// Initial fetch still running
|
||||
if (entry.promise) {
|
||||
return {
|
||||
value: await entry.promise,
|
||||
stale: false,
|
||||
updatedAt: entry.updatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
const age = now - entry.updatedAt;
|
||||
|
||||
// Fresh
|
||||
if (age < minTimeToStale) {
|
||||
return {
|
||||
value: entry.value!,
|
||||
stale: false,
|
||||
updatedAt: entry.updatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
// Expired
|
||||
if (age >= maxTimeToLive) {
|
||||
entry.promise = fetcher().then((value) => {
|
||||
entry!.value = value;
|
||||
entry!.updatedAt = Date.now();
|
||||
entry!.promise = undefined;
|
||||
return value;
|
||||
});
|
||||
|
||||
return {
|
||||
value: await entry.promise,
|
||||
stale: false,
|
||||
updatedAt: entry.updatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
// Stale -> return immediately and refresh in background
|
||||
if (!entry.revalidating) {
|
||||
entry.revalidating = true;
|
||||
|
||||
void fetcher()
|
||||
.then((value) => {
|
||||
entry!.value = value;
|
||||
entry!.updatedAt = Date.now();
|
||||
})
|
||||
.catch(() => {
|
||||
// ignore
|
||||
})
|
||||
.finally(() => {
|
||||
entry!.revalidating = false;
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
value: entry.value!,
|
||||
stale: true,
|
||||
updatedAt: entry.updatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
invalidate(key: string) {
|
||||
this.cache.delete(key);
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.cache.clear();
|
||||
}
|
||||
|
||||
has(key: string) {
|
||||
return this.cache.has(key);
|
||||
}
|
||||
|
||||
peek<T>(key: string): T | undefined {
|
||||
return this.cache.get(key)?.value;
|
||||
}
|
||||
}
|
||||
|
||||
const cacheMgr = new SWRCache();
|
||||
|
||||
export default cacheMgr;
|
||||
+18
-5
@@ -3,6 +3,7 @@ import { WebSocketServer, WebSocket } from "ws";
|
||||
import type { BridgeRequest, RpcResponse } from "../shared/protocol";
|
||||
import { initDb, insertMessage } from "./db";
|
||||
import type { MessageDTO } from "../shared/dto";
|
||||
import cacheMgr from "./lib/cache";
|
||||
|
||||
const PORT = 8787;
|
||||
|
||||
@@ -198,8 +199,10 @@ wss.on("connection", (ws) => {
|
||||
|
||||
let result: unknown;
|
||||
const wa = getWhatsApp(request.deviceId);
|
||||
const { method, params } = request;
|
||||
const deviceId = request.deviceId || "";
|
||||
|
||||
switch (request.method) {
|
||||
switch (method) {
|
||||
case "listDevices":
|
||||
result = [...bridgeSockets].map((ws) => ({
|
||||
id: ws.deviceId,
|
||||
@@ -208,16 +211,26 @@ wss.on("connection", (ws) => {
|
||||
}));
|
||||
break;
|
||||
|
||||
case "status":
|
||||
case "listChats":
|
||||
// cached
|
||||
case "listContacts":
|
||||
case "listGroups":
|
||||
case "listChats":
|
||||
const res = await cacheMgr.get(
|
||||
method + ":" + deviceId,
|
||||
async () => {
|
||||
return call(wa, method, params);
|
||||
},
|
||||
{ minTimeToStale: method === "listContacts" ? 60 * 60_000 : 5_000 },
|
||||
);
|
||||
result = res.value;
|
||||
break;
|
||||
|
||||
case "status":
|
||||
case "getMessages":
|
||||
case "sendText":
|
||||
case "sendFile":
|
||||
case "getMedia":
|
||||
case "exec":
|
||||
result = await call(wa, request.method, request.params);
|
||||
result = await call(wa, method, params);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
Reference in New Issue
Block a user