fix(feishu): pass proxy agent to WSClient for proxy environments (#26397)

* fix(feishu): pass proxy agent to WSClient for environments behind HTTPS proxy

The Lark SDK WSClient uses the `ws` library which does not automatically
respect https_proxy/HTTP_PROXY environment variables. This causes WebSocket
connection failures in proxy environments (e.g. WSL2 with a local proxy).

Detect proxy env vars and pass an HttpsProxyAgent to WSClient via the
existing `agent` constructor option.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(feishu): add generic type parameter to HttpsProxyAgent return type

Fix TS2314: `HttpsProxyAgent<Uri>` requires a type argument.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(feishu): wire ws proxy dependency and coverage

* chore(lockfile): resolve axios peer lock entry after rebase

---------

Co-authored-by: lirui <lirui@fxiaoke.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
This commit is contained in:
Colin Lee
2026-02-28 13:15:11 +08:00
committed by GitHub
parent 4dc55ea88d
commit 0a23739c37
5 changed files with 121 additions and 0 deletions

View File

@@ -0,0 +1,103 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { FeishuConfig, ResolvedFeishuAccount } from "./types.js";
const wsClientCtorMock = vi.hoisted(() =>
vi.fn(function wsClientCtor() {
return { connected: true };
}),
);
const httpsProxyAgentCtorMock = vi.hoisted(() =>
vi.fn(function httpsProxyAgentCtor(proxyUrl: string) {
return { proxyUrl };
}),
);
vi.mock("@larksuiteoapi/node-sdk", () => ({
AppType: { SelfBuild: "self" },
Domain: { Feishu: "https://open.feishu.cn", Lark: "https://open.larksuite.com" },
LoggerLevel: { info: "info" },
Client: vi.fn(),
WSClient: wsClientCtorMock,
EventDispatcher: vi.fn(),
}));
vi.mock("https-proxy-agent", () => ({
HttpsProxyAgent: httpsProxyAgentCtorMock,
}));
import { createFeishuWSClient } from "./client.js";
const proxyEnvKeys = ["https_proxy", "HTTPS_PROXY", "http_proxy", "HTTP_PROXY"] as const;
type ProxyEnvKey = (typeof proxyEnvKeys)[number];
let priorProxyEnv: Partial<Record<ProxyEnvKey, string | undefined>> = {};
const baseAccount: ResolvedFeishuAccount = {
accountId: "main",
enabled: true,
configured: true,
appId: "app_123",
appSecret: "secret_123",
domain: "feishu",
config: {} as FeishuConfig,
};
function firstWsClientOptions(): { agent?: unknown } {
const calls = wsClientCtorMock.mock.calls as unknown as Array<[options: { agent?: unknown }]>;
return calls[0]?.[0] ?? {};
}
beforeEach(() => {
priorProxyEnv = {};
for (const key of proxyEnvKeys) {
priorProxyEnv[key] = process.env[key];
delete process.env[key];
}
vi.clearAllMocks();
});
afterEach(() => {
for (const key of proxyEnvKeys) {
const value = priorProxyEnv[key];
if (value === undefined) {
delete process.env[key];
} else {
process.env[key] = value;
}
}
});
describe("createFeishuWSClient proxy handling", () => {
it("does not set a ws proxy agent when proxy env is absent", () => {
createFeishuWSClient(baseAccount);
expect(httpsProxyAgentCtorMock).not.toHaveBeenCalled();
const options = firstWsClientOptions();
expect(options?.agent).toBeUndefined();
});
it("uses proxy env precedence: https_proxy first, then HTTPS_PROXY, then http_proxy/HTTP_PROXY", () => {
process.env.https_proxy = "http://lower-https:8001";
process.env.HTTPS_PROXY = "http://upper-https:8002";
process.env.http_proxy = "http://lower-http:8003";
process.env.HTTP_PROXY = "http://upper-http:8004";
createFeishuWSClient(baseAccount);
expect(httpsProxyAgentCtorMock).toHaveBeenCalledTimes(1);
expect(httpsProxyAgentCtorMock).toHaveBeenCalledWith("http://lower-https:8001");
const options = firstWsClientOptions();
expect(options.agent).toEqual({ proxyUrl: "http://lower-https:8001" });
});
it("passes HTTP_PROXY to ws client when https vars are unset", () => {
process.env.HTTP_PROXY = "http://upper-http:8999";
createFeishuWSClient(baseAccount);
expect(httpsProxyAgentCtorMock).toHaveBeenCalledTimes(1);
expect(httpsProxyAgentCtorMock).toHaveBeenCalledWith("http://upper-http:8999");
const options = firstWsClientOptions();
expect(options.agent).toEqual({ proxyUrl: "http://upper-http:8999" });
});
});