From be7c92e5e1ffffdfa21aaffc73ac8b88df7d71ed Mon Sep 17 00:00:00 2001 From: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Date: Tue, 17 Mar 2026 00:42:54 -0700 Subject: [PATCH 1/2] fix(manual-scraper): respect HTTP_PROXY env vars Use undici ProxyAgent as the fetch dispatcher when HTTP_PROXY, HTTPS_PROXY, or their lowercase variants are set. This ensures the manual scraper routes requests through the configured proxy, matching the behavior of other data providers. Closes #6206 Co-Authored-By: Claude Opus 4.6 --- .../data-provider/manual/manual.service.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/apps/api/src/services/data-provider/manual/manual.service.ts b/apps/api/src/services/data-provider/manual/manual.service.ts index 51e65e631..6e825d950 100644 --- a/apps/api/src/services/data-provider/manual/manual.service.ts +++ b/apps/api/src/services/data-provider/manual/manual.service.ts @@ -27,6 +27,7 @@ import { Injectable, Logger } from '@nestjs/common'; import { DataSource, SymbolProfile } from '@prisma/client'; import * as cheerio from 'cheerio'; import { addDays, format, isBefore } from 'date-fns'; +import { ProxyAgent } from 'undici'; @Injectable() export class ManualService implements DataProviderInterface { @@ -292,12 +293,24 @@ export class ManualService implements DataProviderInterface { }): Promise { let locale = scraperConfiguration.locale; - const response = await fetch(scraperConfiguration.url, { + const fetchOptions: RequestInit & { dispatcher?: ProxyAgent } = { headers: scraperConfiguration.headers as HeadersInit, signal: AbortSignal.timeout( this.configurationService.get('REQUEST_TIMEOUT') ) - }); + }; + + const proxyUrl = + process.env.HTTPS_PROXY ?? + process.env.https_proxy ?? + process.env.HTTP_PROXY ?? + process.env.http_proxy; + + if (proxyUrl) { + fetchOptions.dispatcher = new ProxyAgent(proxyUrl); + } + + const response = await fetch(scraperConfiguration.url, fetchOptions); if (!response.ok) { throw new Error( From f961ab35022b56387cabdb0d47591e07cd065b82 Mon Sep 17 00:00:00 2001 From: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Date: Sat, 2 May 2026 11:29:36 -0700 Subject: [PATCH 2/2] fix(api): use global undici dispatcher for HTTP_PROXY across all data providers Switch to setGlobalDispatcher(new EnvHttpProxyAgent()) at bootstrap so every service that uses native fetch picks up HTTP_PROXY / HTTPS_PROXY / NO_PROXY without per-call wiring. Reverts the inline ProxyAgent in manual.service.ts because the global dispatcher covers it (and the other 7 services that hit native fetch: coingecko, openfigi, trackinsight, eod-historical-data, financial-modeling-prep, ghostfolio, rapid-api). Also gets NO_PROXY handling for free, which the inline ProxyAgent path did not have. Matches the approach @dtslvr suggested in #6206. --- apps/api/src/main.ts | 6 ++++++ .../data-provider/manual/manual.service.ts | 17 ++--------------- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts index f08a09a83..1e4aaa4e3 100644 --- a/apps/api/src/main.ts +++ b/apps/api/src/main.ts @@ -18,10 +18,16 @@ import type { NestExpressApplication } from '@nestjs/platform-express'; import cookieParser from 'cookie-parser'; import { NextFunction, Request, Response } from 'express'; import helmet from 'helmet'; +import { EnvHttpProxyAgent, setGlobalDispatcher } from 'undici'; import { AppModule } from './app/app.module'; import { environment } from './environments/environment'; +// Route Node's global `fetch` (used by all data-provider services) through +// HTTP_PROXY / HTTPS_PROXY when those env vars are set. Native fetch +// otherwise ignores them. EnvHttpProxyAgent reads NO_PROXY natively. +setGlobalDispatcher(new EnvHttpProxyAgent()); + async function bootstrap() { const configApp = await NestFactory.create(AppModule); const configService = configApp.get(ConfigService); diff --git a/apps/api/src/services/data-provider/manual/manual.service.ts b/apps/api/src/services/data-provider/manual/manual.service.ts index 6e825d950..51e65e631 100644 --- a/apps/api/src/services/data-provider/manual/manual.service.ts +++ b/apps/api/src/services/data-provider/manual/manual.service.ts @@ -27,7 +27,6 @@ import { Injectable, Logger } from '@nestjs/common'; import { DataSource, SymbolProfile } from '@prisma/client'; import * as cheerio from 'cheerio'; import { addDays, format, isBefore } from 'date-fns'; -import { ProxyAgent } from 'undici'; @Injectable() export class ManualService implements DataProviderInterface { @@ -293,24 +292,12 @@ export class ManualService implements DataProviderInterface { }): Promise { let locale = scraperConfiguration.locale; - const fetchOptions: RequestInit & { dispatcher?: ProxyAgent } = { + const response = await fetch(scraperConfiguration.url, { headers: scraperConfiguration.headers as HeadersInit, signal: AbortSignal.timeout( this.configurationService.get('REQUEST_TIMEOUT') ) - }; - - const proxyUrl = - process.env.HTTPS_PROXY ?? - process.env.https_proxy ?? - process.env.HTTP_PROXY ?? - process.env.http_proxy; - - if (proxyUrl) { - fetchOptions.dispatcher = new ProxyAgent(proxyUrl); - } - - const response = await fetch(scraperConfiguration.url, fetchOptions); + }); if (!response.ok) { throw new Error(