@ -3,7 +3,7 @@ import { GraphqlResponseError } from '@octokit/graphql';
import { RequestError } from '@octokit/request-error' ;
import type { Endpoints , OctokitResponse , RequestParameters } from '@octokit/types' ;
import type { HttpsProxyAgent } from 'https-proxy-agent' ;
import { Disposable , Event , EventEmitter , window } from 'vscode' ;
import { Disposable , Event , EventEmitter , Uri , window } from 'vscode' ;
import { fetch , getProxyAgent , wrapForForcedInsecureSSL } from '@env/fetch' ;
import { isWeb } from '@env/platform' ;
import { configuration } from '../../configuration' ;
@ -22,6 +22,7 @@ import type { RichRemoteProvider } from '../../git/remotes/provider';
import { LogCorrelationContext , Logger , LogLevel } from '../../logger' ;
import { debug } from '../../system/decorators/log' ;
import { Stopwatch } from '../../system/stopwatch' ;
import { fromString , satisfies , Version } from '../../system/version' ;
import {
GitHubBlame ,
GitHubBlameRange ,
@ -51,17 +52,13 @@ export class GitHubApi implements Disposable {
constructor ( _container : Container ) {
this . _disposable = Disposable . from (
configuration . onDidChange ( e = > {
if ( configuration . changed ( e , 'proxy' ) ) {
this . _proxyAgent = null ;
this . _octokits . clear ( ) ;
} else if ( configuration . changed ( e , 'outputLevel' ) ) {
this . _octokits . clear ( ) ;
if ( configuration . changed ( e , 'proxy' ) || configuration . changed ( e , 'outputLevel' ) ) {
this . resetCaches ( ) ;
}
} ) ,
configuration . onDidChangeAny ( e = > {
if ( e . affectsConfiguration ( 'http.proxy' ) || e . affectsConfiguration ( 'http.proxyStrictSSL' ) ) {
this . _proxyAgent = null ;
this . _octokits . clear ( ) ;
this . resetCaches ( ) ;
}
} ) ,
) ;
@ -71,6 +68,12 @@ export class GitHubApi implements Disposable {
this . _disposable ? . dispose ( ) ;
}
private resetCaches ( ) : void {
this . _proxyAgent = null ;
this . _octokits . clear ( ) ;
this . _enterpriseVersions . clear ( ) ;
}
private _proxyAgent : HttpsProxyAgent | null | undefined = null ;
private get proxyAgent ( ) : HttpsProxyAgent | undefined {
if ( isWeb ) return undefined ;
@ -147,7 +150,13 @@ export class GitHubApi implements Disposable {
provider : provider ,
name : author.name ? ? undefined ,
email : author.email ? ? undefined ,
avatarUrl : author.avatarUrl ,
// If we are GitHub Enterprise, we may need to convert the avatar URL since it might require authentication
avatarUrl :
! author . avatarUrl || isGitHubDotCom ( options )
? author . avatarUrl ? ? undefined
: author . email && options ? . baseUrl != null
? await this . createEnterpriseAvatarUrl ( token , options . baseUrl , author . email , options . avatarSize )
: undefined ,
} ;
} catch ( ex ) {
if ( ex instanceof ProviderRequestNotFoundError ) return undefined ;
@ -216,7 +225,13 @@ export class GitHubApi implements Disposable {
provider : provider ,
name : author.name ? ? undefined ,
email : author.email ? ? undefined ,
avatarUrl : author.avatarUrl ,
// If we are GitHub Enterprise, we may need to convert the avatar URL since it might require authentication
avatarUrl :
! author . avatarUrl || isGitHubDotCom ( options )
? author . avatarUrl ? ? undefined
: author . email && options ? . baseUrl != null
? await this . createEnterpriseAvatarUrl ( token , options . baseUrl , author . email , options . avatarSize )
: undefined ,
} ;
} catch ( ex ) {
if ( ex instanceof ProviderRequestNotFoundError ) return undefined ;
@ -1715,6 +1730,27 @@ export class GitHubApi implements Disposable {
}
}
private _enterpriseVersions = new Map < string , Version | null > ( ) ;
@debug < GitHubApi [ ' getEnterpriseVersion ' ] > ( { args : { 0 : '<token>' } } )
private async getEnterpriseVersion ( token : string , options ? : { baseUrl? : string } ) : Promise < Version | undefined > {
let version = this . _enterpriseVersions . get ( token ) ;
if ( version != null ) return version ;
if ( version === null ) return undefined ;
try {
const rsp = await this . request ( undefined , token , 'GET /meta' , options ) ;
const v = ( rsp ? . data as any ) ? . installed_version as string | null | undefined ;
version = v ? fromString ( v ) : null ;
} catch ( ex ) {
debugger ;
version = null ;
}
this . _enterpriseVersions . set ( token , version ) ;
return version ? ? undefined ;
}
private _octokits = new Map < string , Octokit > ( ) ;
private octokit ( token : string , options? : ConstructorParameters < typeof Octokit > [ 0 ] ) : Octokit {
let octokit = this . _octokits . get ( token ) ;
@ -1915,4 +1951,43 @@ export class GitHubApi implements Disposable {
void window . showErrorMessage ( ex . message ) ;
}
}
private async createEnterpriseAvatarUrl (
token : string ,
baseUrl : string ,
email : string ,
avatarSize : number | undefined ,
) : Promise < string | undefined > {
avatarSize = avatarSize ? ? 16 ;
let avatarEndpointUrl = ` https://avatars.githubusercontent.com ` ;
const version = await this . getEnterpriseVersion ( token , { baseUrl : baseUrl } ) ;
if ( satisfies ( version , '>= 3.0.0' ) ) {
avatarEndpointUrl = ` ${ baseUrl } /enterprise/avatars ` ;
const match = /^(?:(\d+)\+)?(.+?)@users\.noreply\.(.*)$/i . exec ( email ) ;
if ( match != null ) {
const uri = Uri . parse ( baseUrl ) ;
const [ , userId , login , authority ] = match ;
if ( uri . authority === authority ) {
if ( userId != null ) {
return ` ${ avatarEndpointUrl } /u/ ${ encodeURIComponent ( userId ) } ?s= ${ avatarSize } ` ;
}
if ( login != null ) {
return ` ${ avatarEndpointUrl } / ${ encodeURIComponent ( login ) } ?s= ${ avatarSize } ` ;
}
}
}
}
// The /u/e endpoint automatically falls back to gravatar if not found
return ` ${ avatarEndpointUrl } /u/e?email= ${ encodeURIComponent ( email ) } &s= ${ avatarSize } ` ;
}
}
function isGitHubDotCom ( options ? : { baseUrl? : string } ) {
return options ? . baseUrl == null || options . baseUrl === 'https://api.github.com' ;
}