Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions auth/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Change Log

## 5.1.1 - 2025-10-28

* [#2111](https://github.com/microsoft/vscode-azuretools/pull/2111) Same as https://github.com/microsoft/vscode-azuretools/pull/2110 but a better fix.

## 5.1.0 - 2025-10-27

* [#2102](https://github.com/microsoft/vscode-azuretools/pull/2102) Fixes an issue causing infinite event loops especially in https://vscode.dev/azure
Expand Down
4 changes: 2 additions & 2 deletions auth/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion auth/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@microsoft/vscode-azext-azureauth",
"author": "Microsoft Corporation",
"version": "5.1.0",
"version": "5.1.1",
"description": "Azure authentication helpers for Visual Studio Code",
"tags": [
"azure",
Expand Down
91 changes: 44 additions & 47 deletions auth/src/VSCodeAzureSubscriptionProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,82 +25,79 @@ let armSubs: typeof import('@azure/arm-resources-subscriptions') | undefined;
* provider.
*/
export class VSCodeAzureSubscriptionProvider implements AzureSubscriptionProvider, vscode.Disposable {
private listenerDisposable: vscode.Disposable | undefined;

private readonly onDidSignInEmitter = new vscode.EventEmitter<void>();
private lastSignInEventFired: number = 0;
private suppressSignInEvents: boolean = false;

private readonly onDidSignOutEmitter = new vscode.EventEmitter<void>();
private lastSignOutEventFired: number = 0;

private priorAccounts: vscode.AuthenticationSessionAccountInformation[] | undefined;

// So that customers can easily share logs, try to only log PII using trace level
public constructor(private readonly logger?: vscode.LogOutputChannel) {
// Load accounts initially, then onDidChangeSessions can compare against them
void vscode.authentication.getAccounts(getConfiguredAuthProviderId()).then(accounts => {
this.priorAccounts = Array.from(accounts); // The Array.from is to get rid of the readonly marker on the array returned by the API
});
void this.accountsRemoved(); // Initialize priorAccounts
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
public onDidSignIn(callback: () => any, thisArg?: any, disposables?: vscode.Disposable[]): vscode.Disposable {
return this.onDidChangeSessions(true, callback, thisArg, disposables);
public dispose(): void {
this.listenerDisposable?.dispose();
this.onDidSignInEmitter.dispose();
this.onDidSignOutEmitter.dispose();
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
public onDidSignOut(callback: () => any, thisArg?: any, disposables?: vscode.Disposable[]): vscode.Disposable {
return this.onDidChangeSessions(false, callback, thisArg, disposables);
public onDidSignIn(callback: () => unknown, thisArg?: unknown, disposables?: vscode.Disposable[]): vscode.Disposable {
this.listenIfNeeded();
return this.onDidSignInEmitter.event(callback, thisArg, disposables);
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
private onDidChangeSessions(signIn: boolean, callback: () => any, thisArg?: any, disposables?: vscode.Disposable[]): vscode.Disposable {
const isASignInEvent = async () => {
const currentAccounts = Array.from(await vscode.authentication.getAccounts(getConfiguredAuthProviderId())); // The Array.from is to get rid of the readonly marker on the array returned by the API
const priorAccountCount = this.priorAccounts?.length ?? 0;
this.priorAccounts = currentAccounts;

// The only way a sign out happens is if an account is removed entirely from the list of accounts
if (currentAccounts.length === 0 || currentAccounts.length < priorAccountCount) {
return false;
}

return true;
}
public onDidSignOut(callback: () => unknown, thisArg?: unknown, disposables?: vscode.Disposable[]): vscode.Disposable {
this.listenIfNeeded();
return this.onDidSignOutEmitter.event(callback, thisArg, disposables);
}

const wrappedCallback = () => {
const immediate = setImmediate(() => {
clearImmediate(immediate);
void callback.call(thisArg);
});
private listenIfNeeded(): void {
if (this.listenerDisposable) {
return;
}

const disposable = vscode.authentication.onDidChangeSessions(async e => {
this.listenerDisposable = vscode.authentication.onDidChangeSessions(async e => {
// Ignore any sign in that isn't for the configured auth provider
if (e.provider.id !== getConfiguredAuthProviderId()) {
return;
}

if (signIn) {
if (this.suppressSignInEvents || Date.now() < this.lastSignInEventFired + EventDebounce) {
return;
} else if (await isASignInEvent()) {
if (!await this.accountsRemoved()) {
if (!this.suppressSignInEvents && Date.now() > this.lastSignInEventFired + EventDebounce) {
this.logger?.debug('auth: Firing onDidSignIn event');
this.lastSignInEventFired = Date.now();
wrappedCallback();
}
} else {
if (Date.now() < this.lastSignOutEventFired + EventDebounce) {
return;
} else if (!await isASignInEvent()) {
this.lastSignOutEventFired = Date.now();
wrappedCallback();
this.onDidSignInEmitter.fire();
}
} else if (Date.now() > this.lastSignOutEventFired + EventDebounce) {
this.logger?.debug('auth: Firing onDidSignOut event');
this.lastSignOutEventFired = Date.now();
this.onDidSignOutEmitter.fire();
}
});

disposables?.push(disposable);
return disposable;
}

public dispose(): void {
// No-op, this class no longer has disposables
private async accountsRemoved(): Promise<boolean> {
try {
this.suppressSignInEvents = true;
const currentAccounts = Array.from(await vscode.authentication.getAccounts(getConfiguredAuthProviderId()));
const priorAccountCount = this.priorAccounts?.length ?? 0;
this.priorAccounts = currentAccounts;

// The only way a sign out happens is if an account is removed entirely from the list of accounts
if (currentAccounts.length === 0 || currentAccounts.length < priorAccountCount) {
return true;
}

return false;
} finally {
this.suppressSignInEvents = false;
}
}

/**
Expand Down
Loading