Better lifecycle handling (#242758)

I moved to a factory model because there was just so much that needed to be async.

I think the amount of async code will be reduced in the future as we remove some migration logic, but this makes sure we don't accidentally create instances without awaiting their initialization.
This commit is contained in:
Tyler James Leonhardt
2025-03-05 17:50:14 -08:00
committed by GitHub
parent 0664c2e142
commit eab6f90c72
7 changed files with 137 additions and 57 deletions

View File

@@ -7,32 +7,47 @@ import { Disposable, Event, EventEmitter, LogOutputChannel, SecretStorage } from
import { AccountInfo } from '@azure/msal-node';
export interface IAccountAccess {
initialize(): Promise<void>;
onDidAccountAccessChange: Event<void>;
isAllowedAccess(account: AccountInfo): boolean;
setAllowedAccess(account: AccountInfo, allowed: boolean): Promise<void>;
}
export class ScopedAccountAccess implements IAccountAccess {
export class ScopedAccountAccess implements IAccountAccess, Disposable {
private readonly _onDidAccountAccessChangeEmitter = new EventEmitter<void>();
readonly onDidAccountAccessChange = this._onDidAccountAccessChangeEmitter.event;
private readonly _accountAccessSecretStorage: AccountAccessSecretStorage;
private value = new Array<string>();
constructor(
private readonly _disposable: Disposable;
private constructor(
private readonly _accountAccessSecretStorage: IAccountAccessSecretStorage,
disposables: Disposable[] = []
) {
this._disposable = Disposable.from(
...disposables,
this._onDidAccountAccessChangeEmitter,
this._accountAccessSecretStorage.onDidChange(() => this.update())
);
}
static async create(
secretStorage: SecretStorage,
cloudName: string,
logger: LogOutputChannel,
migrations?: { clientId: string; authority: string }[],
) {
this._accountAccessSecretStorage = new AccountAccessSecretStorage(secretStorage, cloudName, logger, migrations);
this._accountAccessSecretStorage.onDidChange(() => this.update());
migrations: { clientId: string; authority: string }[] | undefined,
): Promise<ScopedAccountAccess> {
const storage = await AccountAccessSecretStorage.create(secretStorage, cloudName, logger, migrations);
const access = new ScopedAccountAccess(storage, [storage]);
await access.initialize();
return access;
}
async initialize() {
await this._accountAccessSecretStorage.initialize();
dispose() {
this._disposable.dispose();
}
private async initialize(): Promise<void> {
await this.update();
}
@@ -62,15 +77,22 @@ export class ScopedAccountAccess implements IAccountAccess {
}
}
export class AccountAccessSecretStorage {
interface IAccountAccessSecretStorage {
get(): Promise<string[] | undefined>;
store(value: string[]): Thenable<void>;
delete(): Thenable<void>;
onDidChange: Event<void>;
}
class AccountAccessSecretStorage implements IAccountAccessSecretStorage, Disposable {
private _disposable: Disposable;
private readonly _onDidChangeEmitter = new EventEmitter<void>;
private readonly _onDidChangeEmitter = new EventEmitter<void>();
readonly onDidChange: Event<void> = this._onDidChangeEmitter.event;
private readonly _key = `accounts-${this._cloudName}`;
constructor(
private constructor(
private readonly _secretStorage: SecretStorage,
private readonly _cloudName: string,
private readonly _logger: LogOutputChannel,
@@ -86,10 +108,21 @@ export class AccountAccessSecretStorage {
);
}
static async create(
secretStorage: SecretStorage,
cloudName: string,
logger: LogOutputChannel,
migrations?: { clientId: string; authority: string }[],
): Promise<AccountAccessSecretStorage> {
const storage = new AccountAccessSecretStorage(secretStorage, cloudName, logger, migrations);
await storage.initialize();
return storage;
}
/**
* TODO: Remove this method after a release with the migration
*/
async initialize(): Promise<void> {
private async initialize(): Promise<void> {
if (!this._migrations) {
return;
}

View File

@@ -6,7 +6,7 @@
import { ICachePlugin, TokenCacheContext } from '@azure/msal-node';
import { Disposable, EventEmitter, SecretStorage } from 'vscode';
export class SecretStorageCachePlugin implements ICachePlugin {
export class SecretStorageCachePlugin implements ICachePlugin, Disposable {
private readonly _onDidChange: EventEmitter<void> = new EventEmitter<void>();
readonly onDidChange = this._onDidChange.event;

View File

@@ -5,8 +5,7 @@
import type { AccountInfo, AuthenticationResult, InteractiveRequest, RefreshTokenRequest, SilentFlowRequest } from '@azure/msal-node';
import type { Disposable, Event } from 'vscode';
export interface ICachedPublicClientApplication extends Disposable {
initialize(): Promise<void>;
export interface ICachedPublicClientApplication {
onDidAccountsChange: Event<{ added: AccountInfo[]; changed: AccountInfo[]; deleted: AccountInfo[] }>;
onDidRemoveLastAccount: Event<void>;
acquireTokenSilent(request: SilentFlowRequest): Promise<AuthenticationResult>;