mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 14:40:43 +00:00
test: split slack client option imports
This commit is contained in:
95
extensions/slack/src/client-options.ts
Normal file
95
extensions/slack/src/client-options.ts
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import type { RetryOptions, WebClientOptions } 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,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,98 +1,11 @@
|
|||||||
import { type RetryOptions, type WebClientOptions, WebClient } from "@slack/web-api";
|
import { type WebClientOptions, WebClient } from "@slack/web-api";
|
||||||
import { HttpsProxyAgent } from "https-proxy-agent";
|
import { resolveSlackWebClientOptions, resolveSlackWriteClientOptions } from "./client-options.js";
|
||||||
import { resolveEnvHttpProxyUrl } from "openclaw/plugin-sdk/infra-runtime";
|
export {
|
||||||
|
resolveSlackWebClientOptions,
|
||||||
export const SLACK_DEFAULT_RETRY_OPTIONS: RetryOptions = {
|
resolveSlackWriteClientOptions,
|
||||||
retries: 2,
|
SLACK_DEFAULT_RETRY_OPTIONS,
|
||||||
factor: 2,
|
SLACK_WRITE_RETRY_OPTIONS,
|
||||||
minTimeout: 500,
|
} from "./client-options.js";
|
||||||
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 = {}) {
|
export function createSlackWebClient(token: string, options: WebClientOptions = {}) {
|
||||||
return new WebClient(token, resolveSlackWebClientOptions(options));
|
return new WebClient(token, resolveSlackWebClientOptions(options));
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import { normalizeResolvedSecretInputString } from "openclaw/plugin-sdk/secret-i
|
|||||||
import { normalizeStringEntries } from "openclaw/plugin-sdk/text-runtime";
|
import { normalizeStringEntries } from "openclaw/plugin-sdk/text-runtime";
|
||||||
import { installRequestBodyLimitGuard } from "openclaw/plugin-sdk/webhook-request-guards";
|
import { installRequestBodyLimitGuard } from "openclaw/plugin-sdk/webhook-request-guards";
|
||||||
import { resolveSlackAccount } from "../accounts.js";
|
import { resolveSlackAccount } from "../accounts.js";
|
||||||
import { resolveSlackWebClientOptions } from "../client.js";
|
import { resolveSlackWebClientOptions } from "../client-options.js";
|
||||||
import { isSlackExecApprovalClientEnabled } from "../exec-approvals.js";
|
import { isSlackExecApprovalClientEnabled } from "../exec-approvals.js";
|
||||||
import { normalizeSlackWebhookPath, registerSlackHttpHandler } from "../http/index.js";
|
import { normalizeSlackWebhookPath, registerSlackHttpHandler } from "../http/index.js";
|
||||||
import { SLACK_TEXT_LIMIT } from "../limits.js";
|
import { SLACK_TEXT_LIMIT } from "../limits.js";
|
||||||
|
|||||||
Reference in New Issue
Block a user