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