diff --git a/extensions/azurecore/src/account-provider/auths/azureAuth.ts b/extensions/azurecore/src/account-provider/auths/azureAuth.ts index 3623f941a2..e8732d88b0 100644 --- a/extensions/azurecore/src/account-provider/auths/azureAuth.ts +++ b/extensions/azurecore/src/account-provider/auths/azureAuth.ts @@ -241,7 +241,7 @@ export abstract class AzureAuth implements vscode.Disposable { - protected abstract login(tenant: Tenant, resource: Resource): Promise<{ response: OAuthTokenResponse, authComplete: Deferred }>; + protected abstract login(tenant: Tenant, resource: Resource): Promise<{ response: OAuthTokenResponse | undefined, authComplete: Deferred }>; /** * Refreshes a token, if a refreshToken is passed in then we use that. If it is not passed in then we will prompt the user for consent. diff --git a/extensions/azurecore/src/account-provider/auths/azureAuthCodeGrant.ts b/extensions/azurecore/src/account-provider/auths/azureAuthCodeGrant.ts index 5912954872..2b6ace1e39 100644 --- a/extensions/azurecore/src/account-provider/auths/azureAuthCodeGrant.ts +++ b/extensions/azurecore/src/account-provider/auths/azureAuthCodeGrant.ts @@ -35,7 +35,6 @@ interface CryptoValues { export class AzureAuthCodeGrant extends AzureAuth { private static readonly USER_FRIENDLY_NAME: string = localize('azure.azureAuthCodeGrantName', 'Azure Auth Code Grant'); - private server: SimpleWebServer; constructor( metadata: AzureAccountProviderMetadata, @@ -47,7 +46,7 @@ export class AzureAuthCodeGrant extends AzureAuth { } - protected async login(tenant: Tenant, resource: Resource): Promise<{ response: OAuthTokenResponse, authComplete: Deferred }> { + protected async login(tenant: Tenant, resource: Resource): Promise<{ response: OAuthTokenResponse | undefined, authComplete: Deferred }> { let authCompleteDeferred: Deferred; let authCompletePromise = new Promise((resolve, reject) => authCompleteDeferred = { resolve, reject }); let authResponse: AuthCodeResponse; @@ -60,7 +59,7 @@ export class AzureAuthCodeGrant extends AzureAuth { return { response: await this.getTokenWithAuthorizationCode(tenant, resource, authResponse), - authComplete: authCompleteDeferred + authComplete: authCompleteDeferred! }; } @@ -143,11 +142,11 @@ export class AzureAuthCodeGrant extends AzureAuth { } private async loginDesktop(tenant: Tenant, resource: Resource, authCompletePromise: Promise): Promise { - this.server = new SimpleWebServer(); + const server = new SimpleWebServer(); let serverPort: string; try { - serverPort = await this.server.startup(); + serverPort = await server.startup(); } catch (ex) { const msg = localize('azure.serverCouldNotStart', 'Server could not start. This could be a permissions error or an incompatibility on your system. You can try enabling device code authentication from settings.'); throw new AzureAuthError(msg, 'Server could not start', ex); @@ -167,7 +166,7 @@ export class AzureAuthCodeGrant extends AzureAuth { }; const loginUrl = `${this.loginEndpointUrl}${tenant.id}/oauth2/authorize?${qs.stringify(loginQuery)}`; await vscode.env.openExternal(vscode.Uri.parse(`http://localhost:${serverPort}/signin?nonce=${encodeURIComponent(nonce)}`)); - const authCode = await this.addServerListeners(this.server, nonce, loginUrl, authCompletePromise); + const authCode = await this.addServerListeners(server, nonce, loginUrl, authCompletePromise); return { authCode, codeVerifier, diff --git a/extensions/azurecore/src/account-provider/auths/azureDeviceCode.ts b/extensions/azurecore/src/account-provider/auths/azureDeviceCode.ts index df9bf180fd..92da34ded1 100644 --- a/extensions/azurecore/src/account-provider/auths/azureDeviceCode.ts +++ b/extensions/azurecore/src/account-provider/auths/azureDeviceCode.ts @@ -84,7 +84,7 @@ export class AzureDeviceCode extends AzureAuth { return { response: result, - authComplete: authCompleteDeferred + authComplete: authCompleteDeferred! }; } @@ -98,7 +98,7 @@ export class AzureDeviceCode extends AzureAuth { const timeoutMessage = localize('azure.timeoutDeviceCode', 'Timed out when waiting for device code login.'); const fiveMinutes = 5 * 60 * 1000; - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { let timeout: NodeJS.Timer; const timer = setInterval(async () => { diff --git a/extensions/azurecore/src/account-provider/utils/fileDatabase.ts b/extensions/azurecore/src/account-provider/utils/fileDatabase.ts index be9f61c3fb..d6388deda7 100644 --- a/extensions/azurecore/src/account-provider/utils/fileDatabase.ts +++ b/extensions/azurecore/src/account-provider/utils/fileDatabase.ts @@ -15,11 +15,11 @@ export class AlreadyInitializedError extends Error { } export class FileDatabase { - private db: { [key: string]: string }; + private db: { [key: string]: string } = {}; private isDirty = false; private isSaving = false; private isInitialized = false; - private saveInterval: NodeJS.Timer; + private saveInterval: NodeJS.Timer | undefined; constructor( private readonly dbPath: string, @@ -91,7 +91,7 @@ export class FileDatabase { public async initialize(): Promise { this.isInitialized = true; - this.setupSaveTask(); + this.saveInterval = setInterval(() => this.save(), 20 * 1000); let fileContents: string; try { await fs.access(this.dbPath, fsConstants.R_OK | fsConstants.R_OK); @@ -114,13 +114,11 @@ export class FileDatabase { } } - private setupSaveTask(): NodeJS.Timer { - return setInterval(() => this.save(), 20 * 1000); - } - public async shutdown(): Promise { await this.waitForFileSave(); - clearInterval((this.saveInterval)); + if (this.saveInterval) { + clearInterval(this.saveInterval); + } await this.save(); } diff --git a/extensions/azurecore/src/account-provider/utils/simpleWebServer.ts b/extensions/azurecore/src/account-provider/utils/simpleWebServer.ts index 9b7249ee0b..706a928c62 100644 --- a/extensions/azurecore/src/account-provider/utils/simpleWebServer.ts +++ b/extensions/azurecore/src/account-provider/utils/simpleWebServer.ts @@ -11,21 +11,27 @@ export type WebHandler = (req: http.IncomingMessage, reqUrl: url.UrlWithParsedQu export class AlreadyRunningError extends Error { } export class SimpleWebServer { - private hasStarted: boolean; + private hasStarted: boolean = false; private readonly pathMappings = new Map(); private readonly server: http.Server; - private lastUsed: number; + private lastUsed: number = new Date().getTime(); private shutoffInterval: NodeJS.Timer; constructor(private readonly autoShutoffTimer = 5 * 60 * 1000) { // Default to five minutes. this.bumpLastUsed(); - this.autoShutoff(); + this.shutoffInterval = setInterval(() => { + const time = new Date().getTime(); + + if (time - this.lastUsed > this.autoShutoffTimer) { + console.log('Shutting off webserver...'); + this.shutdown().catch(console.error); + } + }, 1000); this.server = http.createServer((req, res) => { this.bumpLastUsed(); const reqUrl = url.parse(req.url!, /* parseQueryString */ true); - - const handler = this.pathMappings.get(reqUrl.pathname); + const handler = reqUrl.pathname ? this.pathMappings.get(reqUrl.pathname) : undefined; if (handler) { return handler(req, reqUrl, res); } @@ -95,15 +101,4 @@ export class SimpleWebServer { public on(pathMapping: string, handler: WebHandler) { this.pathMappings.set(pathMapping, handler); } - - private autoShutoff(): void { - this.shutoffInterval = setInterval(() => { - const time = new Date().getTime(); - - if (time - this.lastUsed > this.autoShutoffTimer) { - console.log('Shutting off webserver...'); - this.shutdown().catch(console.error); - } - }, 1000); - } }