fix(image-proxy): use ArrayBuffer instead of Blob to prevent OOM on Node v24 (#5926)

This commit is contained in:
Thomas authored and GitHub committed 2026-06-11 11:50:49 +02:00
1 parent c54a7c9a7e
commit 3e464a76f2
2 files changed
+19 -10

No files matched your search

+14 -9
View File
@@ -10,6 +10,8 @@ import { createGetSetChannel } from "@homarr/redis";
const logger = createLogger({ module: "imageProxy" });
export const IMAGE_PROXY_REDIS_TTL_SECONDS = 86400;
const createHmacChannel = (hmac: `${string}.${string}`) => createGetSetChannel<string>(`image-proxy:hmac:${hmac}`);
const createUrlByIdChannel = (id: string) =>
createGetSetChannel<{
@@ -45,7 +47,7 @@ export class ImageProxy {
return this.createImageUrl(id);
}
public async forwardImageAsync(id: string): Promise<Blob | null> {
public async forwardImageAsync(id: string): Promise<ArrayBuffer | null> {
const urlAndHeaders = await this.getImageUrlAndHeadersAsync(id);
if (!urlAndHeaders) {
return null;
@@ -69,16 +71,16 @@ export class ImageProxy {
return null;
}
const blob = (await response.blob()) as Blob;
const arrayBuffer = await response.arrayBuffer();
logger.debug("Forwarding image succeeded", {
id,
url: this.redactUrl(urlAndHeaders.url),
headers: this.redactHeaders(urlAndHeaders.headers),
proxyUrl,
size: `${(blob.size / 1024).toFixed(1)}KB`,
size: `${(arrayBuffer.byteLength / 1024).toFixed(1)}KB`,
});
return blob;
return arrayBuffer;
}
private createImageUrl(id: string): string {
@@ -113,11 +115,14 @@ export class ImageProxy {
const hashChannel = createHmacChannel(`${urlHash}.${headerHash}`);
const urlHeaderChannel = createUrlByIdChannel(id);
await urlHeaderChannel.setAsync({
url: encryptSecret(url),
headers: encryptSecret(JSON.stringify(headers ?? null)),
});
await hashChannel.setAsync(id);
await urlHeaderChannel.setAsync(
{
url: encryptSecret(url),
headers: encryptSecret(JSON.stringify(headers ?? null)),
},
{ ttlSeconds: IMAGE_PROXY_REDIS_TTL_SECONDS },
);
await hashChannel.setAsync(id, { ttlSeconds: IMAGE_PROXY_REDIS_TTL_SECONDS });
logger.debug("Stored image in the proxy", {
id,
+5 -1
View File
@@ -112,9 +112,13 @@ export const createGetSetChannel = <TData>(name: string) => {
/**
* Set data in the channel
* @param data data to be stored in the channel
* @param options optional TTL in seconds
*/
setAsync: async (data: TData) => {
setAsync: async (data: TData, options?: { ttlSeconds?: number }) => {
await getSetClient.set(name, superjson.stringify(data));
if (options?.ttlSeconds) {
await getSetClient.expire(name, options.ttlSeconds);
}
},
/**
* Remove data from the channel