Path: blob/main/extensions/copilot/src/platform/networking/node/nodeFetchFetcher.ts
13400 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as stream from 'stream';6import * as undici from 'undici';7import { Lazy } from '../../../util/vs/base/common/lazy';8import { IEnvService } from '../../env/common/envService';9import { HeadersImpl, IHeaders, ReportFetchEvent, WebSocketConnection, WebSocketConnectOptions } from '../common/fetcherService';10import { BaseFetchFetcher } from './baseFetchFetcher';1112export class NodeFetchFetcher extends BaseFetchFetcher {1314static readonly ID = 'node-fetch' as const;1516constructor(17envService: IEnvService,18reportEvent: ReportFetchEvent = () => { },19userAgentLibraryUpdate?: (original: string) => string,20) {21super(getFetch(), envService, NodeFetchFetcher.ID, reportEvent, userAgentLibraryUpdate);22}2324getUserAgentLibrary(): string {25return NodeFetchFetcher.ID;26}2728isInternetDisconnectedError(_e: any): boolean {29return false;30}31isFetcherError(e: any): boolean {32const code = e?.code || e?.cause?.code;33return code && ['EADDRINUSE', 'ECONNREFUSED', 'ECONNRESET', 'ENOTFOUND', 'EPIPE', 'ETIMEDOUT'].includes(code);34}35}3637function getFetch(): typeof globalThis.fetch {38const fetch = (globalThis as any).__vscodePatchedFetch || globalThis.fetch;39return function (input: string | URL | globalThis.Request, init?: RequestInit) {40return fetch(input, { dispatcher: agent.value, ...init });41};42}4344// Cache agent to reuse connections.45const agent = new Lazy(() => new undici.Agent({ allowH2: true }));4647export function createWebSocket(url: string, options?: WebSocketConnectOptions): WebSocketConnection {48const wsAgent = new undici.Agent();49const originalDispatch = wsAgent.dispatch;50let responseHeaders: IHeaders = new HeadersImpl({});51let responseStatusCode: number | undefined;52let responseStatusText: string | undefined;53wsAgent.dispatch = function (dispatchOptions: undici.Dispatcher.DispatchOptions, handler: undici.Dispatcher.DispatchHandler): boolean {54const wrappedHandler: undici.Dispatcher.DispatchHandler = {55...handler,56onUpgrade(statusCode: number, rawHeaders: Buffer[] | string[] | null, socket: stream.Duplex) {57responseStatusCode = statusCode;58if (rawHeaders) {59responseHeaders = HeadersImpl.fromMap(parseRawHeaders(rawHeaders));60}61return handler.onUpgrade?.(statusCode, rawHeaders, socket);62},63onHeaders(statusCode: number, rawHeaders: Buffer[], resume: () => void, statusText: string) {64responseStatusCode = statusCode;65responseStatusText = statusText;66if (rawHeaders) {67responseHeaders = HeadersImpl.fromMap(parseRawHeaders(rawHeaders));68}69return handler.onHeaders?.(statusCode, rawHeaders, resume, statusText) ?? true;70},71};72return originalDispatch.call(this, dispatchOptions, wrappedHandler);73};7475const webSocket = new WebSocket(url, {76headers: options?.headers,77dispatcher: wsAgent as any,78});7980webSocket.addEventListener('close', () => {81wsAgent.destroy().catch(() => { });82});8384return {85webSocket,86get responseHeaders() {87const wsResponseHeaders = (webSocket as { responseHeaders?: Record<string, string | string[] | undefined> }).responseHeaders;88return wsResponseHeaders ? new HeadersImpl(wsResponseHeaders) : responseHeaders;89},90get responseStatusCode() {91return (webSocket as { responseStatusCode?: number }).responseStatusCode ?? responseStatusCode;92},93get responseStatusText() {94return (webSocket as { responseStatusText?: string }).responseStatusText ?? responseStatusText;95},96get networkError() {97return (webSocket as { networkError?: Error }).networkError ?? undefined;98}99};100}101102function parseRawHeaders(rawHeaders: readonly (Buffer | string)[]): Map<string, string> {103const headers = new Map<string, string>();104for (let i = 0; i + 1 < rawHeaders.length; i += 2) {105const name = rawHeaders[i].toString().toLowerCase();106const value = rawHeaders[i + 1].toString();107const existing = headers.get(name);108headers.set(name, existing !== undefined ? `${existing}, ${value}` : value);109}110return headers;111}112113114