76 lines
2.3 KiB
TypeScript
76 lines
2.3 KiB
TypeScript
import dayjs from "dayjs";
|
|
import { z } from "zod/v4";
|
|
|
|
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
|
import { withTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
|
|
|
|
import { createCachedWidgetRequestHandler } from "./lib/cached-widget-request-handler";
|
|
|
|
export const fetchStockPriceHandler = createCachedWidgetRequestHandler({
|
|
queryKey: "fetchStockPriceResult",
|
|
widgetKind: "stockPrice",
|
|
async requestAsync(input: { stock: string; timeRange: string; timeInterval: string }) {
|
|
const response = await withTimeoutAsync(async (signal) => {
|
|
return await fetchWithTrustedCertificatesAsync(
|
|
`https://query1.finance.yahoo.com/v8/finance/chart/${input.stock}?range=${input.timeRange}&interval=${input.timeInterval}`,
|
|
{ signal },
|
|
);
|
|
});
|
|
const data = dataSchema.parse(await response.json());
|
|
|
|
if ("error" in data) {
|
|
throw new Error(data.error.description);
|
|
}
|
|
if (data.chart.result.length !== 1) {
|
|
throw new Error("Received multiple results");
|
|
}
|
|
const firstResult = data.chart.result[0];
|
|
if (!firstResult) {
|
|
throw new Error("Received invalid data");
|
|
}
|
|
|
|
const priceHistory =
|
|
firstResult.indicators.quote[0]?.close.filter(
|
|
// Filter out null values from price arrays (Yahoo Finance returns null for missing data points)
|
|
(value) => value !== null && value !== undefined,
|
|
) ?? [];
|
|
|
|
return {
|
|
priceHistory,
|
|
previousClose: firstResult.meta.previousClose ?? priceHistory[0] ?? 1,
|
|
symbol: firstResult.meta.symbol,
|
|
shortName: firstResult.meta.shortName,
|
|
};
|
|
},
|
|
cacheDuration: dayjs.duration(5, "minutes"),
|
|
});
|
|
|
|
const dataSchema = z
|
|
.object({
|
|
error: z.object({
|
|
description: z.string(),
|
|
}),
|
|
})
|
|
.or(
|
|
z.object({
|
|
chart: z.object({
|
|
result: z.array(
|
|
z.object({
|
|
indicators: z.object({
|
|
quote: z.array(
|
|
z.object({
|
|
close: z.array(z.number().nullish()),
|
|
}),
|
|
),
|
|
}),
|
|
meta: z.object({
|
|
symbol: z.string(),
|
|
shortName: z.string(),
|
|
previousClose: z.number().optional(),
|
|
}),
|
|
}),
|
|
),
|
|
}),
|
|
}),
|
|
);
|