mirror of
https://github.com/mgeeky/decode-spam-headers.git
synced 2026-02-22 05:23:31 +01:00
MAESTRO: add analysis cache hook
This commit is contained in:
171
frontend/src/hooks/useAnalysisCache.ts
Normal file
171
frontend/src/hooks/useAnalysisCache.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
import type { AnalysisConfig, AnalysisReport } from "../types/analysis";
|
||||
|
||||
export interface CachedAnalysisPayload {
|
||||
headers: string;
|
||||
config: AnalysisConfig;
|
||||
result: AnalysisReport;
|
||||
}
|
||||
|
||||
export interface CachedAnalysisData extends CachedAnalysisPayload {
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
export interface UseAnalysisCacheState {
|
||||
save: (payload: CachedAnalysisPayload) => void;
|
||||
load: () => CachedAnalysisData | null;
|
||||
clear: () => void;
|
||||
hasCachedData: () => boolean;
|
||||
isNearLimit: boolean;
|
||||
}
|
||||
|
||||
const STORAGE_LIMIT_BYTES = 5 * 1024 * 1024;
|
||||
const WARNING_THRESHOLD_RATIO = 0.9;
|
||||
|
||||
const HEADER_KEY = "wha:headers";
|
||||
const CONFIG_KEY = "wha:config";
|
||||
const RESULT_KEY = "wha:result";
|
||||
const TIMESTAMP_KEY = "wha:timestamp";
|
||||
|
||||
const canUseStorage = (): boolean => {
|
||||
if (typeof window === "undefined") {
|
||||
return false;
|
||||
}
|
||||
if (!window.localStorage) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const probeKey = "wha:probe";
|
||||
window.localStorage.setItem(probeKey, probeKey);
|
||||
window.localStorage.removeItem(probeKey);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const estimateBytes = (value: string): number => {
|
||||
if (typeof TextEncoder !== "undefined") {
|
||||
return new TextEncoder().encode(value).length;
|
||||
}
|
||||
|
||||
return value.length;
|
||||
};
|
||||
|
||||
const estimateStorageSize = (): number => {
|
||||
if (!canUseStorage()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let total = 0;
|
||||
for (let index = 0; index < localStorage.length; index += 1) {
|
||||
const key = localStorage.key(index);
|
||||
if (!key) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const value = localStorage.getItem(key) ?? "";
|
||||
total += estimateBytes(key);
|
||||
total += estimateBytes(value);
|
||||
}
|
||||
|
||||
return total;
|
||||
};
|
||||
|
||||
const parseJson = <T>(value: string | null): T | null => {
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(value) as T;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const useAnalysisCache = (): UseAnalysisCacheState => {
|
||||
const [isNearLimit, setIsNearLimit] = useState(false);
|
||||
|
||||
const save = useCallback((payload: CachedAnalysisPayload) => {
|
||||
if (!canUseStorage()) {
|
||||
setIsNearLimit(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const timestamp = Date.now();
|
||||
localStorage.setItem(HEADER_KEY, payload.headers);
|
||||
localStorage.setItem(CONFIG_KEY, JSON.stringify(payload.config));
|
||||
localStorage.setItem(RESULT_KEY, JSON.stringify(payload.result));
|
||||
localStorage.setItem(TIMESTAMP_KEY, String(timestamp));
|
||||
|
||||
const usedBytes = estimateStorageSize();
|
||||
const warningThreshold = STORAGE_LIMIT_BYTES * WARNING_THRESHOLD_RATIO;
|
||||
setIsNearLimit(usedBytes >= warningThreshold);
|
||||
}, []);
|
||||
|
||||
const load = useCallback((): CachedAnalysisData | null => {
|
||||
if (!canUseStorage()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const headers = localStorage.getItem(HEADER_KEY);
|
||||
const config = parseJson<AnalysisConfig>(localStorage.getItem(CONFIG_KEY));
|
||||
const result = parseJson<AnalysisReport>(localStorage.getItem(RESULT_KEY));
|
||||
const timestampValue = localStorage.getItem(TIMESTAMP_KEY);
|
||||
|
||||
if (!headers || !config || !result || !timestampValue) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const timestamp = Number(timestampValue);
|
||||
if (Number.isNaN(timestamp)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
headers,
|
||||
config,
|
||||
result,
|
||||
timestamp,
|
||||
};
|
||||
}, []);
|
||||
|
||||
const clear = useCallback(() => {
|
||||
if (!canUseStorage()) {
|
||||
setIsNearLimit(false);
|
||||
return;
|
||||
}
|
||||
|
||||
localStorage.removeItem(HEADER_KEY);
|
||||
localStorage.removeItem(CONFIG_KEY);
|
||||
localStorage.removeItem(RESULT_KEY);
|
||||
localStorage.removeItem(TIMESTAMP_KEY);
|
||||
setIsNearLimit(false);
|
||||
}, []);
|
||||
|
||||
const hasCachedData = useCallback((): boolean => {
|
||||
if (!canUseStorage()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Boolean(
|
||||
localStorage.getItem(HEADER_KEY) &&
|
||||
localStorage.getItem(CONFIG_KEY) &&
|
||||
localStorage.getItem(RESULT_KEY) &&
|
||||
localStorage.getItem(TIMESTAMP_KEY),
|
||||
);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
save,
|
||||
load,
|
||||
clear,
|
||||
hasCachedData,
|
||||
isNearLimit,
|
||||
};
|
||||
};
|
||||
|
||||
export default useAnalysisCache;
|
||||
Reference in New Issue
Block a user