refactor(msteams): share response release wrapper

This commit is contained in:
Vincent Koc
2026-06-22 18:50:59 +08:00
parent bdf75474b9
commit 35be382e56
3 changed files with 47 additions and 91 deletions

View File

@@ -14,6 +14,7 @@ import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalString,
} from "openclaw/plugin-sdk/string-coerce-runtime";
import { responseWithRelease } from "../response-with-release.js";
import type { MSTeamsAttachmentLike } from "./types.js";
type InlineImageCandidate =
@@ -576,52 +577,6 @@ export async function resolveAndValidateIP(
/** Maximum number of redirects to follow in safeFetch. */
const MAX_SAFE_REDIRECTS = 5;
const NULL_BODY_STATUSES = new Set([101, 204, 205, 304]);
function responseWithRelease(response: Response, release: () => Promise<void>): Response {
let released = false;
const releaseOnce = async () => {
if (released) {
return;
}
released = true;
await release();
};
if (!response.body || NULL_BODY_STATUSES.has(response.status)) {
void releaseOnce();
return response;
}
const reader = response.body.getReader();
const body = new ReadableStream<Uint8Array>({
async pull(controller) {
try {
const next = await reader.read();
if (next.done) {
controller.close();
await releaseOnce();
return;
}
controller.enqueue(next.value);
} catch (err) {
await releaseOnce();
throw err;
}
},
async cancel(reason) {
void reader.cancel(reason).catch(() => {});
await releaseOnce();
},
});
return new Response(body, {
status: response.status,
statusText: response.statusText,
headers: response.headers,
});
}
/**
* Fetch a URL with redirect: "manual", validating each redirect target
* against the hostname allowlist and optional DNS-resolved IP (anti-SSRF).

View File

@@ -4,13 +4,13 @@ import { fetchWithSsrFGuard, type MSTeamsConfig } from "../runtime-api.js";
import { GRAPH_ROOT } from "./attachments/shared.js";
import { resolveMSTeamsSdkCloudOptions } from "./cloud.js";
import { createMSTeamsHttpError } from "./http-error.js";
import { responseWithRelease } from "./response-with-release.js";
import { createMSTeamsTokenProvider, loadMSTeamsSdkWithAuth } from "./sdk.js";
import { readAccessToken } from "./token-response.js";
import { resolveDelegatedAccessToken, resolveMSTeamsCredentials } from "./token.js";
import { buildUserAgent } from "./user-agent.js";
const GRAPH_BETA = "https://graph.microsoft.com/beta";
const NULL_BODY_STATUSES = new Set([101, 204, 205, 304]);
export type GraphUser = {
id?: string;
@@ -31,50 +31,6 @@ type GraphChannel = {
export type GraphResponse<T> = { value?: T[] };
function responseWithRelease(response: Response, release: () => Promise<void>): Response {
let released = false;
const releaseOnce = async () => {
if (released) {
return;
}
released = true;
await release();
};
if (!response.body || NULL_BODY_STATUSES.has(response.status)) {
void releaseOnce();
return response;
}
const reader = response.body.getReader();
const body = new ReadableStream<Uint8Array>({
async pull(controller) {
try {
const next = await reader.read();
if (next.done) {
controller.close();
await releaseOnce();
return;
}
controller.enqueue(next.value);
} catch (error) {
await releaseOnce();
throw error;
}
},
async cancel(reason) {
void reader.cancel(reason).catch(() => undefined);
await releaseOnce();
},
});
return new Response(body, {
status: response.status,
statusText: response.statusText,
headers: response.headers,
});
}
export function normalizeQuery(value?: string | null): string {
return value?.trim() ?? "";
}

View File

@@ -0,0 +1,45 @@
const NULL_BODY_STATUSES = new Set([101, 204, 205, 304]);
export function responseWithRelease(response: Response, release: () => Promise<void>): Response {
let released = false;
const releaseOnce = async () => {
if (released) {
return;
}
released = true;
await release();
};
if (!response.body || NULL_BODY_STATUSES.has(response.status)) {
void releaseOnce();
return response;
}
const reader = response.body.getReader();
const body = new ReadableStream<Uint8Array>({
async pull(controller) {
try {
const next = await reader.read();
if (next.done) {
controller.close();
await releaseOnce();
return;
}
controller.enqueue(next.value);
} catch (error) {
await releaseOnce();
throw error;
}
},
async cancel(reason) {
void reader.cancel(reason).catch(() => undefined);
await releaseOnce();
},
});
return new Response(body, {
status: response.status,
statusText: response.statusText,
headers: response.headers,
});
}