mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-02 19:17:14 +00:00
fix: harden Discord gateway metadata wrapping
Address PR review feedback by preserving Authorization when fetch init fields are merged into the Discord gateway metadata request and by treating response body read failures as transient wrapped fetch failures. Add a regression test for body stream errors during metadata fetch. Regeneration-Prompt: | Follow up on a Discord gateway startup hardening PR after bot review comments. The code already normalizes plain-text Discord /gateway/bot failures, but two edge cases remained: spreading fetchInit after headers could let a future caller clobber the Bot Authorization header, and response.text() could throw while streaming the body and bypass the wrapper that marks startup failures as transient. Keep the fix narrow to the Discord gateway metadata helper and its tests. Preserve existing behavior for successful fetches and existing proxy support, but make sure both request construction and body-read failures stay inside the same wrapped error path so launchd-managed gateways do not exit on these edge cases.
This commit is contained in:
@@ -11,9 +11,12 @@ const DISCORD_GATEWAY_BOT_URL = "https://discord.com/api/v10/gateway/bot";
|
||||
const DEFAULT_DISCORD_GATEWAY_URL = "wss://gateway.discord.gg/";
|
||||
|
||||
type DiscordGatewayMetadataResponse = Pick<Response, "ok" | "status" | "text">;
|
||||
type DiscordGatewayFetchInit = Record<string, unknown> & {
|
||||
headers?: Record<string, string>;
|
||||
};
|
||||
type DiscordGatewayFetch = (
|
||||
input: string,
|
||||
init?: Record<string, unknown>,
|
||||
init?: DiscordGatewayFetchInit,
|
||||
) => Promise<DiscordGatewayMetadataResponse>;
|
||||
|
||||
export function resolveDiscordGatewayIntents(
|
||||
@@ -74,15 +77,16 @@ function createGatewayMetadataError(params: {
|
||||
async function fetchDiscordGatewayInfo(params: {
|
||||
token: string;
|
||||
fetchImpl: DiscordGatewayFetch;
|
||||
fetchInit?: Record<string, unknown>;
|
||||
fetchInit?: DiscordGatewayFetchInit;
|
||||
}): Promise<APIGatewayBotInfo> {
|
||||
let response: DiscordGatewayMetadataResponse;
|
||||
try {
|
||||
response = await params.fetchImpl(DISCORD_GATEWAY_BOT_URL, {
|
||||
...params.fetchInit,
|
||||
headers: {
|
||||
...params.fetchInit?.headers,
|
||||
Authorization: `Bot ${params.token}`,
|
||||
},
|
||||
...params.fetchInit,
|
||||
});
|
||||
} catch (error) {
|
||||
throw createGatewayMetadataError({
|
||||
@@ -92,7 +96,16 @@ async function fetchDiscordGatewayInfo(params: {
|
||||
});
|
||||
}
|
||||
|
||||
const body = await response.text();
|
||||
let body: string;
|
||||
try {
|
||||
body = await response.text();
|
||||
} catch (error) {
|
||||
throw createGatewayMetadataError({
|
||||
detail: error instanceof Error ? error.message : String(error),
|
||||
transient: true,
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
const summary = summarizeGatewayResponseBody(body);
|
||||
const transient = isTransientDiscordGatewayResponse(response.status, body);
|
||||
|
||||
@@ -128,7 +141,7 @@ function createGatewayPlugin(params: {
|
||||
autoInteractions: boolean;
|
||||
};
|
||||
fetchImpl: DiscordGatewayFetch;
|
||||
fetchInit?: Record<string, unknown>;
|
||||
fetchInit?: DiscordGatewayFetchInit;
|
||||
wsAgent?: HttpsProxyAgent<string>;
|
||||
}): GatewayPlugin {
|
||||
class SafeGatewayPlugin extends GatewayPlugin {
|
||||
|
||||
@@ -255,4 +255,30 @@ describe("createDiscordGatewayPlugin", () => {
|
||||
);
|
||||
expect(baseRegisterClientSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("maps body read failures to fetch failed", async () => {
|
||||
const runtime = createRuntime();
|
||||
globalFetchMock.mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
text: async () => {
|
||||
throw new Error("body stream closed");
|
||||
},
|
||||
} as unknown as Response);
|
||||
const plugin = createDiscordGatewayPlugin({
|
||||
discordConfig: {},
|
||||
runtime,
|
||||
});
|
||||
|
||||
await expect(
|
||||
(
|
||||
plugin as unknown as {
|
||||
registerClient: (client: { options: { token: string } }) => Promise<void>;
|
||||
}
|
||||
).registerClient({
|
||||
options: { token: "token-123" },
|
||||
}),
|
||||
).rejects.toThrow("Failed to get gateway information from Discord: fetch failed");
|
||||
expect(baseRegisterClientSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user