Merge pull request #103 from evanfang0054/fix/feishu-ws-honor-no-proxy

fix(feishu): honor NO_PROXY before proxying the long connection
This commit is contained in:
Evan Fang 2026-09-01 10:01:28 +08:00 • committed by GitHub
commit 165def416a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 351 additions and 217 deletions

File diff suppressed because one or more lines are too long

View file

@ -25,7 +25,34 @@ import {
import { listAgentPresetCatalog } from '../../../../src/channels/shared/agent-preset.mjs';
import { createDeliveryAdapter } from '../../delivery-adapter.mjs';
// The WebSocket agent built here is only used for the Feishu long connection,
// whose endpoint is open.feishu.cn (Feishu) or open.larksuite.com (Lark).
// Honor NO_PROXY/no_proxy for those hosts so users with blanket proxy
// environments exported in their shell (Clash/V2Ray etc.) keep a direct
// connection to Feishu instead of routing the WSS handshake through the proxy.
const LONG_CONNECTION_HOSTS = ['open.feishu.cn', 'open.larksuite.com'];
function hostExcludedByNoProxyEntry(host, entry) {
if (entry === '*') return true;
const bare = entry.startsWith('.') ? entry.slice(1) : entry;
if (!bare) return false;
return host === bare || host.endsWith(`.${bare}`);
}
function longConnectionExcludedByNoProxy(env) {
for (const key of ['no_proxy', 'NO_PROXY']) {
const value = env?.[key];
if (typeof value !== 'string' || !value.trim()) continue;
const entries = value.split(',').map((entry) => entry.trim().toLowerCase()).filter(Boolean);
if (LONG_CONNECTION_HOSTS.some((host) => entries.some((entry) => hostExcludedByNoProxyEntry(host, entry)))) {
return true;
}
}
return false;
}
function webSocketProxyUrl(env) {
if (longConnectionExcludedByNoProxy(env)) return undefined;
for (const key of ['https_proxy', 'HTTPS_PROXY', 'http_proxy', 'HTTP_PROXY']) {
const value = env?.[key];
if (typeof value === 'string' && value.trim()) return value.trim();

View file

@ -197,3 +197,31 @@ test('Feishu WebSocket agent sends an upgrade through an HTTPS_PROXY CONNECT tun
test('Feishu WebSocket agent is absent when no proxy is configured', () => {
assert.equal(createFeishuWebSocketAgent({}), undefined);
});
test('Feishu WebSocket agent honors NO_PROXY for the long-connection endpoints', () => {
const proxyUrl = 'http://127.0.0.1:8080';
assert.equal(createFeishuWebSocketAgent({
HTTPS_PROXY: proxyUrl,
NO_PROXY: 'localhost,127.0.0.1,::1,*.local,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,.cn',
}), undefined, 'a .cn NO_PROXY entry must exclude open.feishu.cn from proxying');
assert.equal(createFeishuWebSocketAgent({
https_proxy: proxyUrl,
no_proxy: 'open.feishu.cn',
}), undefined, 'an exact-host NO_PROXY entry must exclude the Feishu endpoint');
assert.equal(createFeishuWebSocketAgent({
HTTPS_PROXY: proxyUrl,
NO_PROXY: 'feishu.cn',
}), undefined, 'a bare parent domain in NO_PROXY must exclude subdomains');
assert.equal(createFeishuWebSocketAgent({
HTTPS_PROXY: proxyUrl,
NO_PROXY: '*',
}), undefined, 'a wildcard NO_PROXY must disable the WebSocket agent');
assert.equal(createFeishuWebSocketAgent({
HTTPS_PROXY: proxyUrl,
NO_PROXY: 'larksuite.com',
}), undefined, 'NO_PROXY must also cover the Lark endpoint');
assert.notEqual(createFeishuWebSocketAgent({
HTTPS_PROXY: proxyUrl,
NO_PROXY: 'example.com,other.org',
}), undefined, 'unrelated NO_PROXY entries must keep the WebSocket agent');
});