diff --git a/extensions/microsoft-authentication/src/common/async.ts b/extensions/microsoft-authentication/src/common/async.ts index 094861518fc..9eebbb24f65 100644 --- a/extensions/microsoft-authentication/src/common/async.ts +++ b/extensions/microsoft-authentication/src/common/async.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancellationError, CancellationToken, Disposable, Event, EventEmitter } from 'vscode'; +import { CancellationError, CancellationToken, Disposable, Event } from 'vscode'; /** * Can be passed into the Delayed to defer using a microtask @@ -67,12 +67,6 @@ export function raceCancellationError(promise: Promise, token: Cancellatio }); } -export class TimeoutError extends Error { - constructor() { - super('Timed out'); - } -} - export function raceTimeoutError(promise: Promise, timeout: number): Promise { return new Promise((resolve, reject) => { const ref = setTimeout(() => { @@ -86,114 +80,6 @@ export function raceCancellationAndTimeoutError(promise: Promise, token: C return raceCancellationError(raceTimeoutError(promise, timeout), token); } -interface ILimitedTaskFactory { - factory: () => Promise; - c: (value: T | Promise) => void; - e: (error?: unknown) => void; -} - -export interface ILimiter { - - readonly size: number; - - queue(factory: () => Promise): Promise; - - clear(): void; -} - -/** - * A helper to queue N promises and run them all with a max degree of parallelism. The helper - * ensures that at any time no more than M promises are running at the same time. - */ -export class Limiter implements ILimiter { - - private _size = 0; - private _isDisposed = false; - private runningPromises: number; - private readonly maxDegreeOfParalellism: number; - private readonly outstandingPromises: ILimitedTaskFactory[]; - private readonly _onDrained: EventEmitter; - - constructor(maxDegreeOfParalellism: number) { - this.maxDegreeOfParalellism = maxDegreeOfParalellism; - this.outstandingPromises = []; - this.runningPromises = 0; - this._onDrained = new EventEmitter(); - } - - /** - * - * @returns A promise that resolved when all work is done (onDrained) or when - * there is nothing to do - */ - whenIdle(): Promise { - return this.size > 0 - ? toPromise(this.onDrained) - : Promise.resolve(); - } - - get onDrained(): Event { - return this._onDrained.event; - } - - get size(): number { - return this._size; - } - - queue(factory: () => Promise): Promise { - if (this._isDisposed) { - throw new Error('Object has been disposed'); - } - this._size++; - - return new Promise((c, e) => { - this.outstandingPromises.push({ factory, c, e }); - this.consume(); - }); - } - - private consume(): void { - while (this.outstandingPromises.length && this.runningPromises < this.maxDegreeOfParalellism) { - const iLimitedTask = this.outstandingPromises.shift()!; - this.runningPromises++; - - const promise = iLimitedTask.factory(); - promise.then(iLimitedTask.c, iLimitedTask.e); - promise.then(() => this.consumed(), () => this.consumed()); - } - } - - private consumed(): void { - if (this._isDisposed) { - return; - } - this.runningPromises--; - if (--this._size === 0) { - this._onDrained.fire(); - } - - if (this.outstandingPromises.length > 0) { - this.consume(); - } - } - - clear(): void { - if (this._isDisposed) { - throw new Error('Object has been disposed'); - } - this.outstandingPromises.length = 0; - this._size = this.runningPromises; - } - - dispose(): void { - this._isDisposed = true; - this.outstandingPromises.length = 0; // stop further processing - this._size = 0; - this._onDrained.dispose(); - } -} - - interface IScheduledLater extends Disposable { isTriggered(): boolean; } @@ -320,143 +206,6 @@ export class Delayer implements Disposable { } } -/** - * A helper to prevent accumulation of sequential async tasks. - * - * Imagine a mail man with the sole task of delivering letters. As soon as - * a letter submitted for delivery, he drives to the destination, delivers it - * and returns to his base. Imagine that during the trip, N more letters were submitted. - * When the mail man returns, he picks those N letters and delivers them all in a - * single trip. Even though N+1 submissions occurred, only 2 deliveries were made. - * - * The throttler implements this via the queue() method, by providing it a task - * factory. Following the example: - * - * const throttler = new Throttler(); - * const letters = []; - * - * function deliver() { - * const lettersToDeliver = letters; - * letters = []; - * return makeTheTrip(lettersToDeliver); - * } - * - * function onLetterReceived(l) { - * letters.push(l); - * throttler.queue(deliver); - * } - */ -export class Throttler implements Disposable { - - private activePromise: Promise | null; - private queuedPromise: Promise | null; - private queuedPromiseFactory: (() => Promise) | null; - - private isDisposed = false; - - constructor() { - this.activePromise = null; - this.queuedPromise = null; - this.queuedPromiseFactory = null; - } - - queue(promiseFactory: () => Promise): Promise { - if (this.isDisposed) { - return Promise.reject(new Error('Throttler is disposed')); - } - - if (this.activePromise) { - this.queuedPromiseFactory = promiseFactory; - - if (!this.queuedPromise) { - const onComplete = () => { - this.queuedPromise = null; - - if (this.isDisposed) { - return; - } - - const result = this.queue(this.queuedPromiseFactory!); - this.queuedPromiseFactory = null; - - return result; - }; - - this.queuedPromise = new Promise(resolve => { - this.activePromise!.then(onComplete, onComplete).then(resolve); - }); - } - - return new Promise((resolve, reject) => { - this.queuedPromise!.then(resolve, reject); - }); - } - - this.activePromise = promiseFactory(); - - return new Promise((resolve, reject) => { - this.activePromise!.then((result: T) => { - this.activePromise = null; - resolve(result); - }, (err: unknown) => { - this.activePromise = null; - reject(err); - }); - }); - } - - dispose(): void { - this.isDisposed = true; - } -} - -/** - * A helper to delay execution of a task that is being requested often, while - * preventing accumulation of consecutive executions, while the task runs. - * - * The mail man is clever and waits for a certain amount of time, before going - * out to deliver letters. While the mail man is going out, more letters arrive - * and can only be delivered once he is back. Once he is back the mail man will - * do one more trip to deliver the letters that have accumulated while he was out. - */ -export class ThrottledDelayer { - - private delayer: Delayer>; - private throttler: Throttler; - - constructor(defaultDelay: number) { - this.delayer = new Delayer(defaultDelay); - this.throttler = new Throttler(); - } - - trigger(promiseFactory: () => Promise, delay?: number): Promise { - return this.delayer.trigger(() => this.throttler.queue(promiseFactory), delay) as unknown as Promise; - } - - isTriggered(): boolean { - return this.delayer.isTriggered(); - } - - cancel(): void { - this.delayer.cancel(); - } - - dispose(): void { - this.delayer.dispose(); - this.throttler.dispose(); - } -} - -/** - * A queue is handles one promise at a time and guarantees that at any time only one promise is executing. - */ -export class Queue extends Limiter { - - constructor() { - super(1); - } -} - /** * Given an event, returns another event which only fires once. * @@ -493,65 +242,3 @@ export function once(event: Event): Event { export function toPromise(event: Event): Promise { return new Promise(resolve => once(event)(resolve)); } - -export type ValueCallback = (value: T | Promise) => void; - -const enum DeferredOutcome { - Resolved, - Rejected -} - -/** - * Creates a promise whose resolution or rejection can be controlled imperatively. - */ -export class DeferredPromise { - - private completeCallback!: ValueCallback; - private errorCallback!: (err: unknown) => void; - private outcome?: { outcome: DeferredOutcome.Rejected; value: any } | { outcome: DeferredOutcome.Resolved; value: T }; - - public get isRejected() { - return this.outcome?.outcome === DeferredOutcome.Rejected; - } - - public get isResolved() { - return this.outcome?.outcome === DeferredOutcome.Resolved; - } - - public get isSettled() { - return !!this.outcome; - } - - public get value() { - return this.outcome?.outcome === DeferredOutcome.Resolved ? this.outcome?.value : undefined; - } - - public readonly p: Promise; - - constructor() { - this.p = new Promise((c, e) => { - this.completeCallback = c; - this.errorCallback = e; - }); - } - - public complete(value: T) { - return new Promise(resolve => { - this.completeCallback(value); - this.outcome = { outcome: DeferredOutcome.Resolved, value }; - resolve(); - }); - } - - public error(err: unknown) { - return new Promise(resolve => { - this.errorCallback(err); - this.outcome = { outcome: DeferredOutcome.Rejected, value: err }; - resolve(); - }); - } - - public cancel() { - return this.error(new CancellationError()); - } -} diff --git a/extensions/microsoft-authentication/src/common/loggerOptions.ts b/extensions/microsoft-authentication/src/common/loggerOptions.ts index 86443c0281f..d572f655f92 100644 --- a/extensions/microsoft-authentication/src/common/loggerOptions.ts +++ b/extensions/microsoft-authentication/src/common/loggerOptions.ts @@ -17,9 +17,13 @@ export class MsalLoggerOptions { loggerCallback(level: MsalLogLevel, message: string, containsPii: boolean): void { if (containsPii) { + // TODO: Should we still log the message if it contains PII? It's just going to + // an output channel that doesn't leave the machine. + this._output.debug('Skipped logging message because it may contain PII'); return; } + // Log to output channel one level lower than the MSAL log level switch (level) { case MsalLogLevel.Error: this._output.error(message); @@ -28,16 +32,16 @@ export class MsalLoggerOptions { this._output.warn(message); return; case MsalLogLevel.Info: - this._output.info(message); - return; - case MsalLogLevel.Verbose: this._output.debug(message); return; - case MsalLogLevel.Trace: + case MsalLogLevel.Verbose: this._output.trace(message); return; + case MsalLogLevel.Trace: + // Do not log trace messages + return; default: - this._output.info(message); + this._output.debug(message); return; } } diff --git a/extensions/microsoft-authentication/src/extension.ts b/extensions/microsoft-authentication/src/extension.ts index 333a183f4b7..00f24e6d25c 100644 --- a/extensions/microsoft-authentication/src/extension.ts +++ b/extensions/microsoft-authentication/src/extension.ts @@ -15,26 +15,26 @@ function shouldUseMsal(expService: IExperimentationService): boolean { // First check if there is a setting value to allow user to override the default const inspect = workspace.getConfiguration('microsoft-authentication').inspect<'msal' | 'classic'>('implementation'); if (inspect?.workspaceFolderValue !== undefined) { - Logger.debug(`Acquired MSAL enablement value from 'workspaceFolderValue'. Value: ${inspect.workspaceFolderValue}`); + Logger.info(`Acquired MSAL enablement value from 'workspaceFolderValue'. Value: ${inspect.workspaceFolderValue}`); return inspect.workspaceFolderValue === 'msal'; } if (inspect?.workspaceValue !== undefined) { - Logger.debug(`Acquired MSAL enablement value from 'workspaceValue'. Value: ${inspect.workspaceValue}`); + Logger.info(`Acquired MSAL enablement value from 'workspaceValue'. Value: ${inspect.workspaceValue}`); return inspect.workspaceValue === 'msal'; } if (inspect?.globalValue !== undefined) { - Logger.debug(`Acquired MSAL enablement value from 'globalValue'. Value: ${inspect.globalValue}`); + Logger.info(`Acquired MSAL enablement value from 'globalValue'. Value: ${inspect.globalValue}`); return inspect.globalValue === 'msal'; } // Then check if the experiment value const expValue = expService.getTreatmentVariable('vscode', 'microsoft.useMsal'); if (expValue !== undefined) { - Logger.debug(`Acquired MSAL enablement value from 'exp'. Value: ${expValue}`); + Logger.info(`Acquired MSAL enablement value from 'exp'. Value: ${expValue}`); return expValue; } - Logger.debug('Acquired MSAL enablement value from default. Value: false'); + Logger.info('Acquired MSAL enablement value from default. Value: true'); // If no setting or experiment value is found, default to false return true; } diff --git a/extensions/microsoft-authentication/src/node/cachedPublicClientApplication.ts b/extensions/microsoft-authentication/src/node/cachedPublicClientApplication.ts index bc6a392d16c..dc97fb3a352 100644 --- a/extensions/microsoft-authentication/src/node/cachedPublicClientApplication.ts +++ b/extensions/microsoft-authentication/src/node/cachedPublicClientApplication.ts @@ -33,7 +33,7 @@ export class CachedPublicClientApplication implements ICachedPublicClientApplica loggerOptions: { correlationId: `${this._clientId}] [${this._authority}`, loggerCallback: (level, message, containsPii) => this._loggerOptions.loggerCallback(level, message, containsPii), - logLevel: LogLevel.Info + logLevel: LogLevel.Trace } }, broker: {