The v1.69.0 caching refactor (f19033e0d) replaced the old Redis-backed
cached request handler with a simpler in-memory handler but dropped
all per-handler cacheDuration values, defaulting everything to 10s.
This caused widgets that previously had short cache durations (e.g.
health-monitoring at 5s, dns-hole at 5s) to refresh much less
frequently since the client polls every 30s.
Restores cacheTtlMs for all affected request handlers to match their
pre-v1.69.0 values. Also adds cacheTtlMs support to
createWidgetRequestHandler which was missing it.
Beszel handlers are intentionally left untouched as they have their
own cache handling.
Fixes #6268
74 lines
2.1 KiB
TypeScript
74 lines
2.1 KiB
TypeScript
import { z } from "zod";
|
|
|
|
import { env } from "@homarr/common/env";
|
|
import { ResponseError } from "@homarr/common/server";
|
|
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
|
import { withTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
|
|
import { createRequestHandler } from "@homarr/request-handler/lib/request-handler";
|
|
|
|
export type DuckDuckGoBang = z.infer<typeof duckDuckGoBangSchema>;
|
|
|
|
const DUCKDUCKGO_BANGS_URL = "https://duckduckgo.com/bang.js";
|
|
|
|
/**
|
|
* DuckDuckGo `bang.js` uses short keys (to reduce the payload size):
|
|
* - `t`: bang token (e.g. `"yt"`)
|
|
* - `s`: display name
|
|
* - `d`: domain (optional)
|
|
* - `u`: URL template (contains `{{{s}}}`)
|
|
* - `c`: category (optional)
|
|
* - `sc`: subcategory (optional)
|
|
* - `r`: rank (optional)
|
|
*/
|
|
export const duckDuckGoBangSchema = z.object({
|
|
t: z.string(),
|
|
s: z.string(),
|
|
d: z.string().optional(),
|
|
u: z.string(),
|
|
c: z.string().optional(),
|
|
sc: z.string().optional(),
|
|
r: z.number().optional(),
|
|
});
|
|
|
|
const duckDuckGoBangsResponseSchema = z.array(duckDuckGoBangSchema);
|
|
|
|
const normalizeBangToken = (token: string) => token.toLowerCase().trim();
|
|
|
|
const compareBangTokenAsc = (left: DuckDuckGoBang, right: DuckDuckGoBang) => left.t.localeCompare(right.t);
|
|
|
|
export const duckDuckGoBangsRequestHandler = createRequestHandler({
|
|
async requestAsync(_) {
|
|
if (env.NO_EXTERNAL_CONNECTION) {
|
|
return [];
|
|
}
|
|
|
|
const res = await withTimeoutAsync(async (signal) => {
|
|
return await fetchWithTrustedCertificatesAsync(DUCKDUCKGO_BANGS_URL, {
|
|
signal,
|
|
headers: {
|
|
accept: "application/json,text/plain,*/*",
|
|
},
|
|
});
|
|
});
|
|
|
|
if (!res.ok) {
|
|
throw new ResponseError(res);
|
|
}
|
|
|
|
const json: unknown = await res.json();
|
|
const bangs = await duckDuckGoBangsResponseSchema.parseAsync(json);
|
|
|
|
const normalized = bangs
|
|
.map((bang) => ({
|
|
...bang,
|
|
t: normalizeBangToken(bang.t),
|
|
}))
|
|
.filter((bang) => bang.t.length > 0);
|
|
|
|
normalized.sort(compareBangTokenAsc);
|
|
|
|
return normalized;
|
|
},
|
|
cacheTtlMs: 86_400_000,
|
|
});
|