Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | 9x 9x 9x 85x 85x 85x 72x 68x 3x 67x 13x 13x 13x | /**
* @module mcp-client/transport
* @description HTTP transport layer for JSON-RPC 2.0 communication.
* Tries `globalThis.fetch` first (allows test mocking), then falls
* back to Node.js `http`/`https` request when Cloudflare blocks undici/fetch.
*
* @author Hack23 AB
* @license Apache-2.0
*/
import { request as httpsRequest } from 'https';
import { request as httpRequest } from 'http';
import { URL } from 'url';
/** Minimal fetch-like response interface for transport abstraction */
export interface FetchLike {
ok: boolean;
status: number;
statusText: string;
headers: { get(name: string): string | null };
text(): Promise<string>;
json(): Promise<unknown>;
}
/**
* Low-level HTTP/HTTPS POST using Node.js built-in `http`/`https` modules.
* Used as fallback when `globalThis.fetch` is unavailable or blocked.
* Automatically selects `http.request` or `https.request` based on the URL protocol.
*/
export function nodeHttpsPost(
url: string,
headers: Record<string, string>,
body: string,
signal: AbortSignal,
): Promise<FetchLike> {
return new Promise<FetchLike>((resolve, reject) => {
const parsed = new URL(url);
const isHttp = parsed.protocol === 'http:';
const requestFn = isHttp ? httpRequest : httpsRequest;
const options = {
hostname: parsed.hostname,
port: parsed.port || (isHttp ? 80 : 443),
path: parsed.pathname + parsed.search,
method: 'POST',
headers: {
...headers,
'Content-Length': Buffer.byteLength(body),
},
};
const req = requestFn(options, (res) => {
let data = '';
res.on('data', (chunk: Buffer) => {
data += chunk.toString();
});
res.on('end', () => {
resolve({
ok: (res.statusCode ?? 0) >= 200 && (res.statusCode ?? 0) < 300,
status: res.statusCode ?? 0,
statusText: res.statusMessage ?? '',
headers: {
get(name: string): string | null {
const val = res.headers[name.toLowerCase()];
return typeof val === 'string' ? val : val?.[0] ?? null;
},
},
text: () => Promise.resolve(data),
json: () => Promise.resolve(JSON.parse(data) as unknown),
});
});
});
req.on('error', reject);
signal.addEventListener('abort', () => {
req.destroy();
reject(new DOMException('The operation was aborted.', 'AbortError'));
});
req.write(body);
req.end();
});
}
/**
* Perform an HTTP POST, preferring globalThis.fetch with nodeHttpsPost fallback.
* This abstraction enables test mocking via globalThis.fetch override.
*/
export async function performPost(
url: string,
headers: Record<string, string>,
body: string,
signal: AbortSignal,
): Promise<FetchLike> {
Eif (typeof globalThis.fetch === 'function') {
try {
const resp = await globalThis.fetch(url, {
method: 'POST',
headers,
body,
signal,
});
return {
ok: resp.ok,
status: resp.status,
statusText: resp.statusText,
headers: {
get(name: string): string | null {
return resp.headers?.get?.(name) ?? null;
},
},
text: () => resp.text(),
json: () => resp.json() as Promise<unknown>,
};
} catch (fetchErr: unknown) {
const msg = ((fetchErr as Error).message ?? '').toLowerCase();
Iif (
msg.includes('typeerror') ||
msg.includes('not implemented') ||
msg.includes('bad request') ||
msg.includes('400')
) {
return nodeHttpsPost(url, headers, body, signal);
}
throw fetchErr;
}
}
return nodeHttpsPost(url, headers, body, signal);
}
|