mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-16 11:41:08 +00:00
104 lines
3.4 KiB
TypeScript
104 lines
3.4 KiB
TypeScript
import { type RetryOptions, type WebClientOptions, WebClient } from "@slack/web-api";
|
|
import { HttpsProxyAgent } from "https-proxy-agent";
|
|
import { resolveEnvHttpProxyUrl } from "openclaw/plugin-sdk/infra-runtime";
|
|
|
|
export const SLACK_DEFAULT_RETRY_OPTIONS: RetryOptions = {
|
|
retries: 2,
|
|
factor: 2,
|
|
minTimeout: 500,
|
|
maxTimeout: 3000,
|
|
randomize: true,
|
|
};
|
|
|
|
export const SLACK_WRITE_RETRY_OPTIONS: RetryOptions = {
|
|
retries: 0,
|
|
};
|
|
|
|
/**
|
|
* Check whether a hostname is excluded from proxying by `NO_PROXY` / `no_proxy`.
|
|
* Supports comma-separated entries with optional leading dots (e.g. `.slack.com`).
|
|
*/
|
|
function isHostExcludedByNoProxy(hostname: string, env: NodeJS.ProcessEnv = process.env): boolean {
|
|
const raw = env.no_proxy ?? env.NO_PROXY;
|
|
if (!raw) {
|
|
return false;
|
|
}
|
|
const entries = raw
|
|
.split(/[,\s]+/)
|
|
.map((e) => e.trim().toLowerCase())
|
|
.filter(Boolean);
|
|
const lower = hostname.toLowerCase();
|
|
for (const entry of entries) {
|
|
if (entry === "*") {
|
|
return true;
|
|
}
|
|
// Strip optional wildcard/leading dot so `*.slack.com` and `.slack.com`
|
|
// match both `slack.com` (apex) and Slack subdomains.
|
|
const bare = entry.startsWith("*.")
|
|
? entry.slice(2)
|
|
: entry.startsWith(".")
|
|
? entry.slice(1)
|
|
: entry;
|
|
if (lower === bare || lower.endsWith(`.${bare}`)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* Respects `NO_PROXY` / `no_proxy` — if `*.slack.com` (or a matching pattern)
|
|
* appears in the exclusion list, returns `undefined` so the connection is direct.
|
|
*
|
|
* Returns `undefined` when no proxy env var is configured or when Slack hosts
|
|
* are excluded by `NO_PROXY`.
|
|
*/
|
|
function resolveSlackProxyAgent(): HttpsProxyAgent<string> | undefined {
|
|
const proxyUrl = resolveEnvHttpProxyUrl("https");
|
|
if (!proxyUrl) {
|
|
return undefined;
|
|
}
|
|
// Slack Socket Mode connects to these hosts; skip proxy if excluded.
|
|
if (isHostExcludedByNoProxy("slack.com")) {
|
|
return undefined;
|
|
}
|
|
try {
|
|
return new HttpsProxyAgent(proxyUrl);
|
|
} catch {
|
|
// Malformed proxy URL — degrade gracefully to direct connection.
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
export function resolveSlackWebClientOptions(options: WebClientOptions = {}): WebClientOptions {
|
|
return {
|
|
...options,
|
|
agent: options.agent ?? resolveSlackProxyAgent(),
|
|
retryConfig: options.retryConfig ?? SLACK_DEFAULT_RETRY_OPTIONS,
|
|
};
|
|
}
|
|
|
|
export function resolveSlackWriteClientOptions(options: WebClientOptions = {}): WebClientOptions {
|
|
return {
|
|
...options,
|
|
agent: options.agent ?? resolveSlackProxyAgent(),
|
|
retryConfig: options.retryConfig ?? SLACK_WRITE_RETRY_OPTIONS,
|
|
};
|
|
}
|
|
|
|
export function createSlackWebClient(token: string, options: WebClientOptions = {}) {
|
|
return new WebClient(token, resolveSlackWebClientOptions(options));
|
|
}
|
|
|
|
export function createSlackWriteClient(token: string, options: WebClientOptions = {}) {
|
|
return new WebClient(token, resolveSlackWriteClientOptions(options));
|
|
}
|