Skip to content

Commit

Permalink
Use Electron's fetch
Browse files Browse the repository at this point in the history
  • Loading branch information
chrmarti committed Sep 20, 2024
1 parent 55ed7b6 commit 1a9738a
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 1 deletion.
6 changes: 6 additions & 0 deletions src/vs/platform/request/common/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,12 @@ function registerProxyConfigurations(scope: ConfigurationScope): void {
default: false,
description: localize('systemCertificatesV2', "Controls whether experimental loading of CA certificates from the OS should be enabled. This uses a more general approach than the default implemenation."),
restricted: true
},
'http.electronFetch': {
type: 'boolean',
default: true,
description: localize('electronFetch', "Controls whether use of Electron's fetch implementation instead of Node.js' should be enabled. All local extensions will get Electron's fetch implementation for the global fetch API."),
restricted: true
}
}
};
Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/api/node/extHostExtensionService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {

// Do this when extension service exists, but extensions are not being activated yet.
const configProvider = await this._extHostConfiguration.getConfigProvider();
await connectProxyResolver(this._extHostWorkspace, configProvider, this, this._logService, this._mainThreadTelemetryProxy, this._initData);
await connectProxyResolver(this._extHostWorkspace, configProvider, this, this._logService, this._mainThreadTelemetryProxy, this._initData, this._store);
performance.mark('code/extHost/didInitProxyResolver');
}

Expand Down
65 changes: 65 additions & 0 deletions src/vs/workbench/api/node/proxyResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { ILogService, LogLevel as LogServiceLevel } from '../../../platform/log/
import { IExtensionDescription } from '../../../platform/extensions/common/extensions.js';
import { LogLevel, createHttpPatch, createProxyResolver, createTlsPatch, ProxySupportSetting, ProxyAgentParams, createNetPatch, loadSystemCertificates } from '@vscode/proxy-agent';
import { AuthInfo } from '../../../platform/request/common/request.js';
import { DisposableStore } from '../../../base/common/lifecycle.js';

// ESM-uncomment-begin
import { createRequire } from 'node:module';
Expand All @@ -31,6 +32,7 @@ const net = require('net');
// ESM-uncomment-end

const systemCertificatesV2Default = false;
const useElectronFetchDefault = true;

export function connectProxyResolver(
extHostWorkspace: IExtHostWorkspaceProvider,
Expand All @@ -39,7 +41,11 @@ export function connectProxyResolver(
extHostLogService: ILogService,
mainThreadTelemetry: MainThreadTelemetryShape,
initData: IExtensionHostInitData,
disposables: DisposableStore,
) {

patchGlobalFetch(configProvider, initData, disposables);

const useHostProxy = initData.environment.useHostProxy;
const doUseHostProxy = typeof useHostProxy === 'boolean' ? useHostProxy : !initData.remote.isRemote;
const params: ProxyAgentParams = {
Expand Down Expand Up @@ -94,6 +100,65 @@ export function connectProxyResolver(
return configureModuleLoading(extensionService, lookup);
}

const unsafeHeaders = [
'content-length',
'host',
'trailer',
'te',
'upgrade',
'cookie2',
'keep-alive',
'transfer-encoding',
'set-cookie',
];

function patchGlobalFetch(configProvider: ExtHostConfigProvider, initData: IExtensionHostInitData, disposables: DisposableStore) {
if (!initData.remote.isRemote && !(globalThis as any).__originalFetch) {
const originalFetch = globalThis.fetch;
(globalThis as any).__originalFetch = originalFetch;
let useElectronFetch = configProvider.getConfiguration('http').get<boolean>('electronFetch', useElectronFetchDefault);
disposables.add(configProvider.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('http.electronFetch')) {
useElectronFetch = configProvider.getConfiguration('http').get<boolean>('electronFetch', useElectronFetchDefault);
}
}));
const electron = require('electron');
// https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
globalThis.fetch = function fetch(input: any /* RequestInfo */ | URL, init?: RequestInit) {
if (!useElectronFetch) {
return originalFetch(input, init);
}
// Limitations: https://github.com/electron/electron/pull/36733#issuecomment-1405615494
const urlStart = typeof input === 'string' ? input : 'cache' in input ? input.url : input.protocol;
if (urlStart.startsWith('data:') || urlStart.startsWith('blob:')) {
return originalFetch(input, init);
}
function getRequestProperty(name: keyof Request & keyof RequestInit) {
return init && name in init ? init[name] : typeof input === 'object' && 'cache' in input ? input[name] : undefined;
}
// net.fetch fails on manual redirect: https://github.com/electron/electron/issues/43715
if (getRequestProperty('redirect') === 'manual') {
return originalFetch(input, init);
}
// Limitations: https://github.com/electron/electron/pull/36733#issuecomment-1405615494
if (getRequestProperty('integrity')) {
return originalFetch(input, init);
}
// Unsupported headers: https://source.chromium.org/chromium/chromium/src/+/main:services/network/public/cpp/header_util.cc;l=32;drc=ee7299f8961a1b05a3554efcc496b6daa0d7f6e1
if (init?.headers) {
const headers = new Headers(init.headers);
for (const header of unsafeHeaders) {
headers.delete(header);
}
init = { ...init, headers };
}
// Support for URL: https://github.com/electron/electron/issues/43712
const electronInput = input instanceof URL ? input.toString() : input;
return electron.net.fetch(electronInput, init);
};
}
}

function createPatchedModules(params: ProxyAgentParams, resolveProxy: ReturnType<typeof createProxyResolver>) {

function mergeModules(module: any, patch: any) {
Expand Down

0 comments on commit 1a9738a

Please sign in to comment.