Files
openclaw/extensions/feishu/src/client.ts
Colin Lee 0a23739c37 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>
2026-02-27 23:15:11 -06:00

132 lines
3.3 KiB
TypeScript

import * as Lark from "@larksuiteoapi/node-sdk";
import { HttpsProxyAgent } from "https-proxy-agent";
import type { FeishuDomain, ResolvedFeishuAccount } from "./types.js";
function getWsProxyAgent(): HttpsProxyAgent<string> | undefined {
const proxyUrl =
process.env.https_proxy ||
process.env.HTTPS_PROXY ||
process.env.http_proxy ||
process.env.HTTP_PROXY;
if (!proxyUrl) return undefined;
return new HttpsProxyAgent(proxyUrl);
}
// Multi-account client cache
const clientCache = new Map<
string,
{
client: Lark.Client;
config: { appId: string; appSecret: string; domain?: FeishuDomain };
}
>();
function resolveDomain(domain: FeishuDomain | undefined): Lark.Domain | string {
if (domain === "lark") {
return Lark.Domain.Lark;
}
if (domain === "feishu" || !domain) {
return Lark.Domain.Feishu;
}
return domain.replace(/\/+$/, ""); // Custom URL for private deployment
}
/**
* Credentials needed to create a Feishu client.
* Both FeishuConfig and ResolvedFeishuAccount satisfy this interface.
*/
export type FeishuClientCredentials = {
accountId?: string;
appId?: string;
appSecret?: string;
domain?: FeishuDomain;
};
/**
* Create or get a cached Feishu client for an account.
* Accepts any object with appId, appSecret, and optional domain/accountId.
*/
export function createFeishuClient(creds: FeishuClientCredentials): Lark.Client {
const { accountId = "default", appId, appSecret, domain } = creds;
if (!appId || !appSecret) {
throw new Error(`Feishu credentials not configured for account "${accountId}"`);
}
// Check cache
const cached = clientCache.get(accountId);
if (
cached &&
cached.config.appId === appId &&
cached.config.appSecret === appSecret &&
cached.config.domain === domain
) {
return cached.client;
}
// Create new client
const client = new Lark.Client({
appId,
appSecret,
appType: Lark.AppType.SelfBuild,
domain: resolveDomain(domain),
});
// Cache it
clientCache.set(accountId, {
client,
config: { appId, appSecret, domain },
});
return client;
}
/**
* Create a Feishu WebSocket client for an account.
* Note: WSClient is not cached since each call creates a new connection.
*/
export function createFeishuWSClient(account: ResolvedFeishuAccount): Lark.WSClient {
const { accountId, appId, appSecret, domain } = account;
if (!appId || !appSecret) {
throw new Error(`Feishu credentials not configured for account "${accountId}"`);
}
const agent = getWsProxyAgent();
return new Lark.WSClient({
appId,
appSecret,
domain: resolveDomain(domain),
loggerLevel: Lark.LoggerLevel.info,
...(agent ? { agent } : {}),
});
}
/**
* Create an event dispatcher for an account.
*/
export function createEventDispatcher(account: ResolvedFeishuAccount): Lark.EventDispatcher {
return new Lark.EventDispatcher({
encryptKey: account.encryptKey,
verificationToken: account.verificationToken,
});
}
/**
* Get a cached client for an account (if exists).
*/
export function getFeishuClient(accountId: string): Lark.Client | null {
return clientCache.get(accountId)?.client ?? null;
}
/**
* Clear client cache for a specific account or all accounts.
*/
export function clearClientCache(accountId?: string): void {
if (accountId) {
clientCache.delete(accountId);
} else {
clientCache.clear();
}
}