import {
fetchWithSsrFGuard,
ssrfPolicyFromPrivateNetworkOptIn,
} from "openclaw/plugin-sdk/ssrf-runtime"; import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalString,
} from "openclaw/plugin-sdk/text-runtime"; import { z } from "openclaw/plugin-sdk/zod";
export type MattermostClient = {
baseUrl: string;
apiBaseUrl: string;
token: string;
request: <T>(path: string, init?: RequestInit) => Promise<T>; /** Guarded fetch implementation; use in place of raw fetch for outbound requests. */
fetchImpl: MattermostFetch;
};
export async function readMattermostError(res: Response): Promise<string> { const contentType = res.headers.get("content-type") ?? ""; if (contentType.includes("application/json")) { const data = (await res.json()) as { message?: string } | undefined; if (data?.message) { return data.message;
} return JSON.stringify(data);
} return await res.text();
}
export function createMattermostClient(params: {
baseUrl: string;
botToken: string;
fetchImpl?: MattermostFetch; /** Allow requests to private/internal IPs (self-hosted/LAN deployments). */
allowPrivateNetwork?: boolean;
}): MattermostClient { const baseUrl = normalizeMattermostBaseUrl(params.baseUrl); if (!baseUrl) { thrownew Error("Mattermost baseUrl is required");
} const apiBaseUrl = `${baseUrl}/api/v4`; const token = params.botToken.trim(); // When no custom fetchImpl is provided (production path), use an SSRF-guarded wrapper // that validates the target URL before making the request (DNS rebinding protection etc.). // A custom fetchImpl is accepted for testing and special cases. const externalFetchImpl = params.fetchImpl;
// Guarded fetch adapter: calls fetchWithSsrFGuard and returns a plain Response. // Body is buffered before releasing the dispatcher so callers get a complete Response. // Null-body status codes per Fetch spec — Response constructor rejects a body for these. const NULL_BODY_STATUSES = new Set([101, 204, 205, 304]);
// Retry on 5xx server errors FIRST (before checking 4xx) // Use "mattermost api" prefix to avoid matching port numbers (e.g., :443) or IP octets // This prevents misclassification when a 5xx error detail contains a 4xx substring // e.g., "Mattermost API 503: upstream returned 404" if (messages.some((message) => /mattermost api 5\d{2}\b/.test(message))) { returntrue;
}
// Check for explicit 429 rate limiting FIRST (before generic "429" text match) // This avoids retrying when error detail contains "429" but it's not the status code if (
messages.some(
(message) => /mattermost api 429\b/.test(message) || message.includes("too many requests"),
)
) { returntrue;
}
// Check for explicit 4xx status codes - these are client errors and should NOT be retried // (except 429 which is handled above) // Use "mattermost api" prefix to avoid matching port numbers like :443 for (const message of messages) { const clientErrorMatch = message.match(/mattermost api (4\d{2})\b/); if (!clientErrorMatch) { continue;
} const statusCode = Number.parseInt(clientErrorMatch[1], 10); if (statusCode >= 400 && statusCode < 500) { returnfalse;
}
}
// Retry on network/transient errors only if no explicit Mattermost API status code is present // This avoids false positives like: // - "400 Bad Request: connection timed out" (has status code) // - "connect ECONNRESET 104.18.32.10:443" (has port number, not status) const hasMattermostApiStatusCode = messages.some((message) =>
/mattermost api \d{3}\b/.test(message),
); if (hasMattermostApiStatusCode) { returnfalse;
}
const codes = candidates
.map((candidate) => readErrorCode(candidate))
.filter((code): code is string => Boolean(code)); if (codes.some((code) => RETRYABLE_NETWORK_ERROR_CODES.has(code))) { returntrue;
}
const names = candidates
.map((candidate) => readErrorName(candidate))
.filter((name): name is string => Boolean(name)); if (names.some((name) => RETRYABLE_NETWORK_ERROR_NAMES.has(name))) { returntrue;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.