mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-04 11:30:29 +00:00
fix(slack): honor HTTPS_PROXY for Socket Mode WebSocket connections
When HTTPS_PROXY or HTTP_PROXY env vars are set, create an HttpsProxyAgent and pass it as the `agent` option through @slack/bolt → @slack/socket-mode → ws, so the WebSocket upgrade request is tunneled through the proxy. This fixes Slack Socket Mode in environments where all outbound traffic must go through an HTTP CONNECT proxy (e.g. sandboxed containers, corporate networks). Previously the ws library opened a direct connection to wss-primary.slack.com, ignoring proxy env vars entirely. The approach mirrors the existing Discord gateway proxy support (extensions/discord/src/monitor/gateway-plugin.ts) which uses the same https-proxy-agent library. Fixes #57405
This commit is contained in:
committed by
Peter Steinberger
parent
b73d8ef7d7
commit
d4e5f250a0
@@ -1,4 +1,4 @@
|
||||
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("@slack/web-api", () => {
|
||||
const WebClient = vi.fn(function WebClientMock(
|
||||
@@ -81,3 +81,72 @@ describe("slack web client config", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("slack proxy agent", () => {
|
||||
const originalEnv = { ...process.env };
|
||||
|
||||
beforeEach(() => {
|
||||
// Clear all proxy env vars before each test
|
||||
for (const key of ["HTTPS_PROXY", "HTTP_PROXY", "https_proxy", "http_proxy"]) {
|
||||
delete process.env[key];
|
||||
}
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Restore original env
|
||||
for (const key of ["HTTPS_PROXY", "HTTP_PROXY", "https_proxy", "http_proxy"]) {
|
||||
if (originalEnv[key] !== undefined) {
|
||||
process.env[key] = originalEnv[key];
|
||||
} else {
|
||||
delete process.env[key];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("sets agent from HTTPS_PROXY env var", () => {
|
||||
process.env.HTTPS_PROXY = "http://proxy.example.com:3128";
|
||||
const options = resolveSlackWebClientOptions();
|
||||
|
||||
expect(options.agent).toBeDefined();
|
||||
expect(options.agent!.constructor.name).toBe("HttpsProxyAgent");
|
||||
});
|
||||
|
||||
it("falls back to HTTP_PROXY when HTTPS_PROXY is not set", () => {
|
||||
process.env.HTTP_PROXY = "http://proxy.example.com:3128";
|
||||
const options = resolveSlackWebClientOptions();
|
||||
|
||||
expect(options.agent).toBeDefined();
|
||||
});
|
||||
|
||||
it("does not set agent when no proxy env var is configured", () => {
|
||||
const options = resolveSlackWebClientOptions();
|
||||
|
||||
expect(options.agent).toBeUndefined();
|
||||
});
|
||||
|
||||
it("does not override an explicitly provided agent", () => {
|
||||
process.env.HTTPS_PROXY = "http://proxy.example.com:3128";
|
||||
const customAgent = {} as never;
|
||||
const options = resolveSlackWebClientOptions({ agent: customAgent });
|
||||
|
||||
expect(options.agent).toBe(customAgent);
|
||||
});
|
||||
|
||||
it("prefers lowercase https_proxy over uppercase", () => {
|
||||
process.env.https_proxy = "http://lower.example.com:3128";
|
||||
process.env.HTTPS_PROXY = "http://upper.example.com:3128";
|
||||
const options = resolveSlackWebClientOptions();
|
||||
|
||||
expect(options.agent).toBeDefined();
|
||||
// HttpsProxyAgent stores the proxy URL — verify it picked the lower-case one
|
||||
expect((options.agent as { proxy: { href: string } }).proxy.href).toContain("lower.example.com");
|
||||
});
|
||||
|
||||
it("also applies proxy agent to write client options", () => {
|
||||
process.env.HTTPS_PROXY = "http://proxy.example.com:3128";
|
||||
const options = resolveSlackWriteClientOptions();
|
||||
|
||||
expect(options.agent).toBeDefined();
|
||||
expect(options.agent!.constructor.name).toBe("HttpsProxyAgent");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { type RetryOptions, type WebClientOptions, WebClient } from "@slack/web-api";
|
||||
import { HttpsProxyAgent } from "https-proxy-agent";
|
||||
|
||||
export const SLACK_DEFAULT_RETRY_OPTIONS: RetryOptions = {
|
||||
retries: 2,
|
||||
@@ -12,9 +13,36 @@ export const SLACK_WRITE_RETRY_OPTIONS: RetryOptions = {
|
||||
retries: 0,
|
||||
};
|
||||
|
||||
/**
|
||||
* Build an HTTPS proxy agent from env vars (HTTPS_PROXY, HTTP_PROXY, etc.)
|
||||
* for use as the `agent` option in Slack WebClient and Socket Mode connections.
|
||||
*
|
||||
* When set, this agent is forwarded through @slack/bolt → @slack/socket-mode →
|
||||
* SlackWebSocket as the `httpAgent`, which the `ws` library uses to tunnel the
|
||||
* WebSocket upgrade request through the proxy. This fixes Socket Mode in
|
||||
* environments where outbound traffic must go through an HTTP CONNECT proxy.
|
||||
*
|
||||
* Returns `undefined` when no proxy env var is configured.
|
||||
*/
|
||||
function resolveSlackProxyAgent(): HttpsProxyAgent<string> | undefined {
|
||||
// Match undici EnvHttpProxyAgent semantics: lower-case takes precedence,
|
||||
// HTTPS prefers https_proxy then falls back to http_proxy.
|
||||
const proxyUrl =
|
||||
process.env.https_proxy?.trim() ||
|
||||
process.env.HTTPS_PROXY?.trim() ||
|
||||
process.env.http_proxy?.trim() ||
|
||||
process.env.HTTP_PROXY?.trim() ||
|
||||
undefined;
|
||||
if (!proxyUrl) {
|
||||
return undefined;
|
||||
}
|
||||
return new HttpsProxyAgent<string>(proxyUrl);
|
||||
}
|
||||
|
||||
export function resolveSlackWebClientOptions(options: WebClientOptions = {}): WebClientOptions {
|
||||
return {
|
||||
...options,
|
||||
agent: options.agent ?? resolveSlackProxyAgent(),
|
||||
retryConfig: options.retryConfig ?? SLACK_DEFAULT_RETRY_OPTIONS,
|
||||
};
|
||||
}
|
||||
@@ -22,6 +50,7 @@ export function resolveSlackWebClientOptions(options: WebClientOptions = {}): We
|
||||
export function resolveSlackWriteClientOptions(options: WebClientOptions = {}): WebClientOptions {
|
||||
return {
|
||||
...options,
|
||||
agent: options.agent ?? resolveSlackProxyAgent(),
|
||||
retryConfig: options.retryConfig ?? SLACK_WRITE_RETRY_OPTIONS,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user