Files
openclaw/extensions/slack/src/client-options.ts
Dallin Romney 39e1be080c feat(slack): support alternate Web API roots (#97154)
* Allow Slack to target alternate Web API roots

* Protect Slack API URL routing

* Simplify Slack API URL routing

* Use env-only Slack API root routing

* Remove Slack client option alias

* Keep Slack API URL dotenv test inline

* Remove leftover Slack PR churn

* Block workspace Slack API URL dotenv

* Preserve Slack proxy HTTPS protocol

* Keep Slack option resolution explicit

* Remove Slack probe formatting diff

* fix(slack): preserve explicit API root precedence

* fix(slack): narrow cached write client options
2026-06-28 18:52:37 -07:00

74 lines
2.6 KiB
TypeScript

// Slack plugin module implements client options behavior.
import type { Agent } from "node:http";
import type { RetryOptions, WebClientOptions } from "@slack/web-api";
import { createNodeProxyAgent } from "openclaw/plugin-sdk/fetch-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,
};
/**
* 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(targetUrl: string): Agent | undefined {
try {
return createNodeProxyAgent({
mode: "env",
targetUrl,
});
} catch {
// Malformed proxy URL; degrade gracefully to direct connection.
return undefined;
}
}
function resolveSlackApiUrlFromEnv(): string | undefined {
return process.env.SLACK_API_URL?.trim() || undefined;
}
function applySlackApiUrlAndProxyOptions(options: WebClientOptions): void {
const slackApiUrl = options.slackApiUrl ?? resolveSlackApiUrlFromEnv();
const proxyTargetUrl = slackApiUrl ?? "https://slack.com/";
options.agent ??= resolveSlackProxyAgent(proxyTargetUrl);
if (slackApiUrl !== undefined) {
options.slackApiUrl = slackApiUrl;
} else {
delete options.slackApiUrl;
}
}
export function resolveSlackWebClientOptions(options: WebClientOptions = {}): WebClientOptions {
const resolved: WebClientOptions = Object.assign({}, options);
applySlackApiUrlAndProxyOptions(resolved);
resolved.retryConfig ??= SLACK_DEFAULT_RETRY_OPTIONS;
return resolved;
}
export function resolveSlackWriteClientOptions(options: WebClientOptions = {}): WebClientOptions {
const resolved: WebClientOptions = Object.assign({}, options);
applySlackApiUrlAndProxyOptions(resolved);
resolved.retryConfig ??= SLACK_WRITE_RETRY_OPTIONS;
resolved.maxRequestConcurrency ??= 1;
return resolved;
}