Files
homarr/packages/request-handler/src/duckduckgo-bangs.ts
T
ajnart b650bffaa8 fix(request-handler): restore cacheTtlMs values lost in v1.69.0 refactor
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
2026-07-15 10:53:36 +02:00

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,
});