fix(telegram): preserve HTTP proxy env in global dispatcher workaround (#29940)
* fix(telegram): preserve HTTP proxy env in global dispatcher workaround * telegram: document request-scoped proxy dispatcher constraint * telegram: assert proxy path never mutates global dispatcher * changelog: credit telegram proxy env regression fix --------- Co-authored-by: Rylen Anil <rylen.anil@gmail.com>
This commit is contained in:
@@ -88,6 +88,7 @@ Docs: https://docs.openclaw.ai
|
||||
### Fixes
|
||||
|
||||
- CLI/Startup (Raspberry Pi + small hosts): speed up startup by avoiding unnecessary plugin preload on fast routes, adding root `--version` fast-path bootstrap bypass, parallelizing status JSON/non-JSON scans where safe, and enabling Node compile cache at startup with env override compatibility (`NODE_COMPILE_CACHE`, `NODE_DISABLE_COMPILE_CACHE`). (#5871) Thanks @BookCatKid and @vincentkoc for raising startup reports, and @lupuletic for related startup work in #27973.
|
||||
- Telegram/Outbound API proxy env: keep the Node 22 `autoSelectFamily` global-dispatcher workaround while restoring env-proxy support by using `EnvHttpProxyAgent` so `HTTP_PROXY`/`HTTPS_PROXY` continue to apply to outbound requests. (#26207) Thanks @qsysbio-cjw for reporting and @rylena and @vincentkoc for work.
|
||||
- Docs/Slack manifest scopes: add missing DM/group-DM bot scopes (`im:read`, `im:write`, `mpim:read`, `mpim:write`) to the Slack app manifest example so DM setup guidance is complete. (#29999) Thanks @JcMinarro.
|
||||
- Slack/Onboarding token help: update setup text to include the “From manifest” app-creation path and current install wording for obtaining the `xoxb-` bot token. (#30846) Thanks @yzhong52.
|
||||
- Slack/Bot attachment-only messages: when `allowBots: true`, bot messages with empty `text` now include non-forwarded attachment `text`/`fallback` content so webhook alerts are not silently dropped. (#27616)
|
||||
|
||||
@@ -5,8 +5,8 @@ import { resetTelegramFetchStateForTests, resolveTelegramFetch } from "./fetch.j
|
||||
const setDefaultAutoSelectFamily = vi.hoisted(() => vi.fn());
|
||||
const setDefaultResultOrder = vi.hoisted(() => vi.fn());
|
||||
const setGlobalDispatcher = vi.hoisted(() => vi.fn());
|
||||
const AgentCtor = vi.hoisted(() =>
|
||||
vi.fn(function MockAgent(this: { options: unknown }, options: unknown) {
|
||||
const EnvHttpProxyAgentCtor = vi.hoisted(() =>
|
||||
vi.fn(function MockEnvHttpProxyAgent(this: { options: unknown }, options: unknown) {
|
||||
this.options = options;
|
||||
}),
|
||||
);
|
||||
@@ -28,7 +28,7 @@ vi.mock("node:dns", async () => {
|
||||
});
|
||||
|
||||
vi.mock("undici", () => ({
|
||||
Agent: AgentCtor,
|
||||
EnvHttpProxyAgent: EnvHttpProxyAgentCtor,
|
||||
setGlobalDispatcher,
|
||||
}));
|
||||
|
||||
@@ -39,7 +39,7 @@ afterEach(() => {
|
||||
setDefaultAutoSelectFamily.mockReset();
|
||||
setDefaultResultOrder.mockReset();
|
||||
setGlobalDispatcher.mockReset();
|
||||
AgentCtor.mockClear();
|
||||
EnvHttpProxyAgentCtor.mockClear();
|
||||
vi.unstubAllEnvs();
|
||||
vi.clearAllMocks();
|
||||
if (originalFetch) {
|
||||
@@ -147,12 +147,12 @@ describe("resolveTelegramFetch", () => {
|
||||
expect(setDefaultResultOrder).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("replaces global undici dispatcher with autoSelectFamily-enabled agent", async () => {
|
||||
it("replaces global undici dispatcher with proxy-aware EnvHttpProxyAgent", async () => {
|
||||
globalThis.fetch = vi.fn(async () => ({})) as unknown as typeof fetch;
|
||||
resolveTelegramFetch(undefined, { network: { autoSelectFamily: true } });
|
||||
|
||||
expect(setGlobalDispatcher).toHaveBeenCalledTimes(1);
|
||||
expect(AgentCtor).toHaveBeenCalledWith({
|
||||
expect(EnvHttpProxyAgentCtor).toHaveBeenCalledWith({
|
||||
connect: {
|
||||
autoSelectFamily: true,
|
||||
autoSelectFamilyAttemptTimeout: 300,
|
||||
@@ -174,13 +174,13 @@ describe("resolveTelegramFetch", () => {
|
||||
resolveTelegramFetch(undefined, { network: { autoSelectFamily: false } });
|
||||
|
||||
expect(setGlobalDispatcher).toHaveBeenCalledTimes(2);
|
||||
expect(AgentCtor).toHaveBeenNthCalledWith(1, {
|
||||
expect(EnvHttpProxyAgentCtor).toHaveBeenNthCalledWith(1, {
|
||||
connect: {
|
||||
autoSelectFamily: true,
|
||||
autoSelectFamilyAttemptTimeout: 300,
|
||||
},
|
||||
});
|
||||
expect(AgentCtor).toHaveBeenNthCalledWith(2, {
|
||||
expect(EnvHttpProxyAgentCtor).toHaveBeenNthCalledWith(2, {
|
||||
connect: {
|
||||
autoSelectFamily: false,
|
||||
autoSelectFamilyAttemptTimeout: 300,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as dns from "node:dns";
|
||||
import * as net from "node:net";
|
||||
import { Agent, setGlobalDispatcher } from "undici";
|
||||
import { EnvHttpProxyAgent, setGlobalDispatcher } from "undici";
|
||||
import type { TelegramNetworkConfig } from "../config/types.telegram.js";
|
||||
import { resolveFetch } from "../infra/fetch.js";
|
||||
import { createSubsystemLogger } from "../logging/subsystem.js";
|
||||
@@ -46,7 +46,7 @@ function applyTelegramNetworkWorkarounds(network?: TelegramNetworkConfig): void
|
||||
) {
|
||||
try {
|
||||
setGlobalDispatcher(
|
||||
new Agent({
|
||||
new EnvHttpProxyAgent({
|
||||
connect: {
|
||||
autoSelectFamily: autoSelectDecision.value,
|
||||
autoSelectFamilyAttemptTimeout: 300,
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
const { ProxyAgent, undiciFetch, proxyAgentSpy, getLastAgent } = vi.hoisted(() => {
|
||||
const mocks = vi.hoisted(() => {
|
||||
const undiciFetch = vi.fn();
|
||||
const proxyAgentSpy = vi.fn();
|
||||
const setGlobalDispatcher = vi.fn();
|
||||
class ProxyAgent {
|
||||
static lastCreated: ProxyAgent | undefined;
|
||||
proxyUrl: string;
|
||||
@@ -17,13 +18,15 @@ const { ProxyAgent, undiciFetch, proxyAgentSpy, getLastAgent } = vi.hoisted(() =
|
||||
ProxyAgent,
|
||||
undiciFetch,
|
||||
proxyAgentSpy,
|
||||
setGlobalDispatcher,
|
||||
getLastAgent: () => ProxyAgent.lastCreated,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("undici", () => ({
|
||||
ProxyAgent,
|
||||
fetch: undiciFetch,
|
||||
ProxyAgent: mocks.ProxyAgent,
|
||||
fetch: mocks.undiciFetch,
|
||||
setGlobalDispatcher: mocks.setGlobalDispatcher,
|
||||
}));
|
||||
|
||||
import { makeProxyFetch } from "./proxy.js";
|
||||
@@ -31,15 +34,16 @@ import { makeProxyFetch } from "./proxy.js";
|
||||
describe("makeProxyFetch", () => {
|
||||
it("uses undici fetch with ProxyAgent dispatcher", async () => {
|
||||
const proxyUrl = "http://proxy.test:8080";
|
||||
undiciFetch.mockResolvedValue({ ok: true });
|
||||
mocks.undiciFetch.mockResolvedValue({ ok: true });
|
||||
|
||||
const proxyFetch = makeProxyFetch(proxyUrl);
|
||||
await proxyFetch("https://api.telegram.org/bot123/getMe");
|
||||
|
||||
expect(proxyAgentSpy).toHaveBeenCalledWith(proxyUrl);
|
||||
expect(undiciFetch).toHaveBeenCalledWith(
|
||||
expect(mocks.proxyAgentSpy).toHaveBeenCalledWith(proxyUrl);
|
||||
expect(mocks.undiciFetch).toHaveBeenCalledWith(
|
||||
"https://api.telegram.org/bot123/getMe",
|
||||
expect.objectContaining({ dispatcher: getLastAgent() }),
|
||||
expect.objectContaining({ dispatcher: mocks.getLastAgent() }),
|
||||
);
|
||||
expect(mocks.setGlobalDispatcher).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,6 +4,8 @@ export function makeProxyFetch(proxyUrl: string): typeof fetch {
|
||||
const agent = new ProxyAgent(proxyUrl);
|
||||
// undici's fetch is runtime-compatible with global fetch but the types diverge
|
||||
// on stream/body internals. Single cast at the boundary keeps the rest type-safe.
|
||||
// Keep proxy dispatching request-scoped. Replacing the global dispatcher breaks
|
||||
// env-driven HTTP(S)_PROXY behavior for unrelated outbound requests.
|
||||
const fetcher = ((input: RequestInfo | URL, init?: RequestInit) =>
|
||||
undiciFetch(input as string | URL, {
|
||||
...(init as Record<string, unknown>),
|
||||
|
||||
Reference in New Issue
Block a user