Преглед на файлове

Adds proxy support

Refs: #1886
main
Eric Amodio преди 2 години
родител
ревизия
cf2066ebb8
променени са 10 файла, в които са добавени 159 реда и са изтрити 17 реда
  1. +6
    -0
      CHANGELOG.md
  2. +35
    -0
      package.json
  3. +4
    -0
      src/config.ts
  4. +3
    -1
      src/container.ts
  5. +10
    -0
      src/env/browser/fetch.ts
  6. +39
    -0
      src/env/node/fetch.ts
  7. +2
    -2
      src/env/node/git/localGitProvider.ts
  8. +42
    -4
      src/plus/github/github.ts
  9. +2
    -1
      src/plus/subscription/serverConnection.ts
  10. +16
    -9
      src/plus/subscription/subscriptionService.ts

+ 6
- 0
CHANGELOG.md Целия файл

@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
## [Unreleased]
### Added
- Adds proxy support to network requests
- By default, uses a proxy configuration based on VS Code settings or OS configuration
- Adds a `gitlens.proxy` setting to specify a GitLens specific proxy configuration
### Changed
- Changes local repositories to be considered public rather than private for GitLens+ features (so only a free account would be required)

+ 35
- 0
package.json Целия файл

@ -3261,6 +3261,40 @@
"scope": "window",
"order": 50
},
"gitlens.proxy": {
"type": [
"object",
"null"
],
"default": null,
"items": {
"type": "object",
"required": [
"url",
"strictSSL"
],
"properties": {
"url": {
"type": [
"string",
"null"
],
"default": null,
"description": "Specifies the url of the proxy server to use"
},
"strictSSL": {
"type": "boolean",
"description": "Specifies whether the proxy server certificate should be verified against the list of supplied CAs",
"default": true
}
},
"additionalProperties": false
},
"uniqueItems": true,
"description": "Specifies the proxy configuration to use. If not specified, the proxy configuration will be determined based on VS Code or OS settings",
"scope": "window",
"order": 55
},
"gitlens.plusFeatures.enabled": {
"type": "boolean",
"default": true,
@ -11035,6 +11069,7 @@
"ansi-regex": "6.0.1",
"billboard.js": "3.3.2",
"chroma-js": "2.3.0",
"https-proxy-agent": "5.0.0",
"iconv-lite": "0.6.3",
"lodash-es": "4.17.21",
"md5.js": "1.3.5",

+ 4
- 0
src/config.ts Целия файл

@ -116,6 +116,10 @@ export interface Config {
plusFeatures: {
enabled: boolean;
};
proxy: {
url: string | null;
strictSSL: boolean;
} | null;
remotes: RemotesConfig[] | null;
showWelcomeOnInstall: boolean;
showWhatsNewAfterUpgrades: boolean;

+ 3
- 1
src/container.ts Целия файл

@ -366,7 +366,9 @@ export class Container {
private async _loadGitHubApi() {
try {
return new (await import(/* webpackChunkName: "github" */ './plus/github/github')).GitHubApi();
const github = new (await import(/* webpackChunkName: "github" */ './plus/github/github')).GitHubApi(this);
this.context.subscriptions.push(github);
return github;
} catch (ex) {
Logger.error(ex);
return undefined;

+ 10
- 0
src/env/browser/fetch.ts Целия файл

@ -1,7 +1,17 @@
const fetch = globalThis.fetch;
export { fetch };
declare global {
interface RequestInit {
agent?: undefined;
}
}
declare type _BodyInit = BodyInit;
declare type _RequestInit = RequestInit;
declare type _Response = Response;
export type { _BodyInit as BodyInit, _RequestInit as RequestInit, _Response as Response };
export function getProxyAgent(): undefined {
return undefined;
}

+ 39
- 0
src/env/node/fetch.ts Целия файл

@ -1,4 +1,43 @@
import * as url from 'url';
import { HttpsProxyAgent } from 'https-proxy-agent';
import fetch from 'node-fetch';
import { configuration } from '../../configuration';
export { fetch };
export type { BodyInit, RequestInit, Response } from 'node-fetch';
export function getProxyAgent(): HttpsProxyAgent | undefined {
let strictSSL: boolean | undefined;
let proxyUrl: string | undefined;
const proxy = configuration.get('proxy');
if (proxy != null) {
proxyUrl = proxy.url ?? undefined;
strictSSL = proxy.strictSSL;
} else {
const proxySupport = configuration.getAny<'off' | 'on' | 'override' | 'fallback'>(
'http.proxySupport',
undefined,
'override',
);
if (proxySupport === 'off') return undefined;
strictSSL = configuration.getAny<boolean>('http.proxyStrictSSL', undefined, true);
proxyUrl = configuration.getAny<string>('http.proxy') || process.env.HTTPS_PROXY || process.env.HTTP_PROXY;
}
if (proxyUrl) {
return new HttpsProxyAgent({
...url.parse(proxyUrl),
rejectUnauthorized: strictSSL,
});
}
if (!strictSSL) {
return new HttpsProxyAgent({
rejectUnauthorized: false,
});
}
return undefined;
}

+ 2
- 2
src/env/node/git/localGitProvider.ts Целия файл

@ -15,7 +15,7 @@ import {
workspace,
WorkspaceFolder,
} from 'vscode';
import { fetch } from '@env/fetch';
import { fetch, getProxyAgent } from '@env/fetch';
import { hrtime } from '@env/hrtime';
import { isLinux, isWindows } from '@env/platform';
import type {
@ -480,7 +480,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
// Check if the url returns a 200 status code
try {
const response = await fetch(url, { method: 'HEAD' });
const response = await fetch(url, { method: 'HEAD', agent: getProxyAgent() });
if (response.status === 200) {
return RepositoryVisibility.Public;
}

+ 42
- 4
src/plus/github/github.ts Целия файл

@ -2,9 +2,12 @@ import { Octokit } from '@octokit/core';
import { GraphqlResponseError } from '@octokit/graphql';
import { RequestError } from '@octokit/request-error';
import type { Endpoints, OctokitResponse, RequestParameters } from '@octokit/types';
import { Event, EventEmitter, window } from 'vscode';
import { fetch } from '@env/fetch';
import type { HttpsProxyAgent } from 'https-proxy-agent';
import { Disposable, Event, EventEmitter, window } from 'vscode';
import { fetch, getProxyAgent } from '@env/fetch';
import { isWeb } from '@env/platform';
import { configuration } from '../../configuration';
import type { Container } from '../../container';
import {
AuthenticationError,
AuthenticationErrorReason,
@ -31,12 +34,47 @@ import { Stopwatch } from '../../system/stopwatch';
const emptyPagedResult: PagedResult<any> = Object.freeze({ values: [] });
const emptyBlameResult: GitHubBlame = Object.freeze({ ranges: [] });
export class GitHubApi {
export class GitHubApi implements Disposable {
private readonly _onDidReauthenticate = new EventEmitter<void>();
get onDidReauthenticate(): Event<void> {
return this._onDidReauthenticate.event;
}
private _disposable: Disposable | undefined;
constructor(_container: Container) {
if (isWeb) return;
this._disposable = Disposable.from(
configuration.onDidChange(e => {
if (configuration.changed(e, 'proxy')) {
this._proxyAgent = null;
this._octokits.clear();
}
}),
configuration.onDidChangeAny(e => {
if (e.affectsConfiguration('http.proxy') || e.affectsConfiguration('http.proxyStrictSSL')) {
this._proxyAgent = null;
this._octokits.clear();
}
}),
);
}
dispose(): void {
this._disposable?.dispose();
}
private _proxyAgent: HttpsProxyAgent | null | undefined = null;
private get proxyAgent(): HttpsProxyAgent | undefined {
if (isWeb) return undefined;
if (this._proxyAgent === null) {
this._proxyAgent = getProxyAgent();
}
return this._proxyAgent;
}
@debug<GitHubApi['getAccountForCommit']>({ args: { 0: p => p.name, 1: '<token>' } })
async getAccountForCommit(
provider: RichRemoteProvider,
@ -1670,7 +1708,7 @@ export class GitHubApi {
request: { fetch: fetchCore },
});
} else {
defaults = Octokit.defaults({ auth: `token ${token}` });
defaults = Octokit.defaults({ auth: `token ${token}`, request: { agent: this.proxyAgent } });
}
octokit = new defaults(options);

+ 2
- 1
src/plus/subscription/serverConnection.ts Целия файл

@ -1,6 +1,6 @@
import { v4 as uuid } from 'uuid';
import { Disposable, env, EventEmitter, StatusBarAlignment, StatusBarItem, Uri, UriHandler, window } from 'vscode';
import { fetch, Response } from '@env/fetch';
import { fetch, getProxyAgent, Response } from '@env/fetch';
import { Container } from '../../container';
import { Logger } from '../../logger';
import { debug, log } from '../../system/decorators/log';
@ -60,6 +60,7 @@ export class ServerConnection implements Disposable {
let rsp: Response;
try {
rsp = await fetch(Uri.joinPath(this.baseApiUri, 'user').toString(), {
agent: getProxyAgent(),
headers: {
Authorization: `Bearer ${token}`,
// TODO: What user-agent should we use?

+ 16
- 9
src/plus/subscription/subscriptionService.ts Целия файл

@ -16,7 +16,7 @@ import {
Uri,
window,
} from 'vscode';
import { fetch } from '@env/fetch';
import { fetch, getProxyAgent } from '@env/fetch';
import { getPlatform } from '@env/platform';
import { configuration } from '../../configuration';
import { Commands, ContextKeys } from '../../constants';
@ -71,6 +71,7 @@ export class SubscriptionService implements Disposable {
private _disposable: Disposable;
private _subscription!: Subscription;
private _statusBarSubscription: StatusBarItem | undefined;
private _validationTimer: ReturnType<typeof setInterval> | undefined;
constructor(private readonly container: Container) {
this._disposable = Disposable.from(
@ -269,6 +270,11 @@ export class SubscriptionService implements Disposable {
@gate()
@log()
logout(reset: boolean = false): void {
if (this._validationTimer != null) {
clearInterval(this._validationTimer);
this._validationTimer = undefined;
}
this._sessionPromise = undefined;
if (this._session != null) {
void this.container.subscriptionAuthentication.removeSession(this._session.id);
@ -323,6 +329,7 @@ export class SubscriptionService implements Disposable {
try {
const rsp = await fetch(Uri.joinPath(this.baseApiUri, 'resend-email').toString(), {
method: 'POST',
agent: getProxyAgent(),
headers: {
Authorization: `Bearer ${session.accessToken}`,
'User-Agent': userAgent,
@ -477,6 +484,7 @@ export class SubscriptionService implements Disposable {
const rsp = await fetch(Uri.joinPath(this.baseApiUri, 'gitlens/checkin').toString(), {
method: 'POST',
agent: getProxyAgent(),
headers: {
Authorization: `Bearer ${session.accessToken}`,
'User-Agent': userAgent,
@ -499,22 +507,21 @@ export class SubscriptionService implements Disposable {
throw new AccountValidationError('Unable to validate account', ex);
} finally {
this.startDailyCheckInTimer();
this.startDailyValidationTimer();
}
}
private _dailyCheckInTimer: ReturnType<typeof setInterval> | undefined;
private startDailyCheckInTimer(): void {
if (this._dailyCheckInTimer != null) {
clearInterval(this._dailyCheckInTimer);
private startDailyValidationTimer(): void {
if (this._validationTimer != null) {
clearInterval(this._validationTimer);
}
// Check twice a day to ensure we check in at least once a day
this._dailyCheckInTimer = setInterval(() => {
// Check 4 times a day to ensure we validate at least once a day
this._validationTimer = setInterval(() => {
if (this._lastCheckInDate == null || this._lastCheckInDate.getDate() !== new Date().getDate()) {
void this.ensureSession(false, true);
}
}, 1000 * 60 * 60 * 12);
}, 1000 * 60 * 60 * 6);
}
@debug()

Зареждане…
Отказ
Запис