diff --git a/extensions/vscode-account/src/AADHelper.ts b/extensions/vscode-account/src/AADHelper.ts index 3838bf2b22a..866786dc5bd 100644 --- a/extensions/vscode-account/src/AADHelper.ts +++ b/extensions/vscode-account/src/AADHelper.ts @@ -23,7 +23,7 @@ interface IToken { refreshToken: string; } -export const onDidChangeAccounts = new vscode.EventEmitter(); +export const onDidChangeSessions = new vscode.EventEmitter(); export class AzureActiveDirectoryService { private _token: IToken | undefined; @@ -44,20 +44,20 @@ export class AzureActiveDirectoryService { // Another window has logged in, generate access token for this instance. if (refreshToken && !this._token) { await this.refreshToken(refreshToken); - onDidChangeAccounts.fire(this.accounts); + onDidChangeSessions.fire(); } // Another window has logged out if (!refreshToken && this._token) { await this.logout(); - onDidChangeAccounts.fire(this.accounts); + onDidChangeSessions.fire(); } this.pollForChange(); }, 1000 * 30); } - private tokenToAccount(token: IToken): vscode.Account { + private tokenToAccount(token: IToken): vscode.Session { return { id: '', accessToken: token.accessToken, @@ -77,7 +77,7 @@ export class AzureActiveDirectoryService { return displayName; } - get accounts(): vscode.Account[] { + get sessions(): vscode.Session[] { return this._token ? [this.tokenToAccount(this._token)] : []; } @@ -146,7 +146,7 @@ export class AzureActiveDirectoryService { } catch (e) { await this.logout(); } finally { - onDidChangeAccounts.fire(this.accounts); + onDidChangeSessions.fire(); } }, 1000 * (parseInt(token.expiresIn) - 10)); diff --git a/extensions/vscode-account/src/extension.ts b/extensions/vscode-account/src/extension.ts index 19786e3608f..144d8d2b815 100644 --- a/extensions/vscode-account/src/extension.ts +++ b/extensions/vscode-account/src/extension.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { AzureActiveDirectoryService, onDidChangeAccounts } from './AADHelper'; +import { AzureActiveDirectoryService, onDidChangeSessions } from './AADHelper'; export async function activate(context: vscode.ExtensionContext) { @@ -15,12 +15,12 @@ export async function activate(context: vscode.ExtensionContext) { vscode.authentication.registerAuthenticationProvider({ id: 'MSA', displayName: 'Microsoft Account', // TODO localize - onDidChangeAccounts: onDidChangeAccounts.event, - getAccounts: () => Promise.resolve(loginService.accounts), + onDidChangeSessions: onDidChangeSessions.event, + getSessions: () => Promise.resolve(loginService.sessions), login: async () => { try { await loginService.login(); - return loginService.accounts[0]!; + return loginService.sessions[0]!; } catch (e) { vscode.window.showErrorMessage(`Logging in failed: ${e}`); throw e; diff --git a/extensions/vscode-account/src/vscode.proposed.d.ts b/extensions/vscode-account/src/vscode.proposed.d.ts index d9a9a1a76ef..6eca3720fb3 100644 --- a/extensions/vscode-account/src/vscode.proposed.d.ts +++ b/extensions/vscode-account/src/vscode.proposed.d.ts @@ -16,25 +16,45 @@ declare module 'vscode' { - export interface Account { - readonly id: string; - readonly accessToken: string; - readonly displayName: string; + export interface Session { + id: string; + accessToken: string; + displayName: string; } export interface AuthenticationProvider { readonly id: string; readonly displayName: string; + readonly onDidChangeSessions: Event; - getAccounts(): Promise>; - readonly onDidChangeAccounts: Event>; + /** + * Returns an array of current sessions. + */ + getSessions(): Promise>; - login(): Promise; - logout(accountId: string): Promise; + /** + * Prompts a user to login. + */ + login(): Promise; + logout(sessionId: string): Promise; } export namespace authentication { export function registerAuthenticationProvider(provider: AuthenticationProvider): Disposable; + + /** + * Fires with the provider id that was registered or unregistered. + */ + export const onDidRegisterAuthenticationProvider: Event; + export const onDidUnregisterAuthenticationProvider: Event; + + /** + * Fires with the provider id that changed sessions. + */ + export const onDidChangeSessions: Event; + export function login(providerId: string): Promise; + export function logout(providerId: string, accountId: string): Promise; + export function getSessions(providerId: string): Promise | undefined>; } // #region Ben - extension auth flow (desktop+web) diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index 52c783c9117..56fba6eb6b7 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -1283,23 +1283,12 @@ export interface RenameProvider { /** * @internal */ -export interface Account { +export interface Session { id: string; accessToken: string; displayName: string; } -/** - * @internal - */ -export interface AuthenticationProvider { - getAccount(): Promise; - onDidChangeAccount: Event; - login(): Promise; - logout(accountId: string): Promise; -} - - export interface Command { id: string; title: string; diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index d988edb2e90..d06d3f68105 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -16,25 +16,45 @@ declare module 'vscode' { - export interface Account { - readonly id: string; - readonly accessToken: string; - readonly displayName: string; + export interface Session { + id: string; + accessToken: string; + displayName: string; } export interface AuthenticationProvider { readonly id: string; readonly displayName: string; + readonly onDidChangeSessions: Event; - getAccounts(): Promise>; - readonly onDidChangeAccounts: Event>; + /** + * Returns an array of current sessions. + */ + getSessions(): Promise>; - login(): Promise; - logout(accountId: string): Promise; + /** + * Prompts a user to login. + */ + login(): Promise; + logout(sessionId: string): Promise; } export namespace authentication { export function registerAuthenticationProvider(provider: AuthenticationProvider): Disposable; + + /** + * Fires with the provider id that was registered or unregistered. + */ + export const onDidRegisterAuthenticationProvider: Event; + export const onDidUnregisterAuthenticationProvider: Event; + + /** + * Fires with the provider id that changed sessions. + */ + export const onDidChangeSessions: Event; + export function login(providerId: string): Promise; + export function logout(providerId: string, accountId: string): Promise; + export function getSessions(providerId: string): Promise>; } //#region Alex - resolvers diff --git a/src/vs/workbench/api/browser/mainThreadAuthentication.ts b/src/vs/workbench/api/browser/mainThreadAuthentication.ts index c9fbc6e3e50..593871bc297 100644 --- a/src/vs/workbench/api/browser/mainThreadAuthentication.ts +++ b/src/vs/workbench/api/browser/mainThreadAuthentication.ts @@ -10,32 +10,27 @@ import { IAuthenticationService } from 'vs/workbench/services/authentication/bro import { ExtHostAuthenticationShape, ExtHostContext, IExtHostContext, MainContext, MainThreadAuthenticationShape } from '../common/extHost.protocol'; export class MainThreadAuthenticationProvider { - public readonly handle: number; constructor( private readonly _proxy: ExtHostAuthenticationShape, - public readonly id: string, - handle: number - ) { - this.handle = handle; + public readonly id: string + ) { } + + getSessions(): Promise> { + return this._proxy.$getSessions(this.id); } - accounts(): Promise> { - return this._proxy.$accounts(this.handle); - } - - login(): Promise { - return this._proxy.$login(this.handle); + login(): Promise { + return this._proxy.$login(this.id); } logout(accountId: string): Promise { - return this._proxy.$logout(this.handle, accountId); + return this._proxy.$logout(this.id, accountId); } } @extHostNamedCustomer(MainContext.MainThreadAuthentication) export class MainThreadAuthentication extends Disposable implements MainThreadAuthenticationShape { private readonly _proxy: ExtHostAuthenticationShape; - private _handlers = new Map(); constructor( extHostContext: IExtHostContext, @@ -45,25 +40,16 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostAuthentication); } - $registerAuthenticationProvider(handle: number, id: string): void { - const provider = new MainThreadAuthenticationProvider(this._proxy, id, handle); - this._handlers.set(handle, id); + $registerAuthenticationProvider(id: string): void { + const provider = new MainThreadAuthenticationProvider(this._proxy, id); this.authenticationService.registerAuthenticationProvider(id, provider); } - $unregisterAuthenticationProvider(handle: number): void { - const id = this._handlers.get(handle); - if (!id) { - throw new Error(`No authentication provider registered with id ${id}`); - } - + $unregisterAuthenticationProvider(id: string): void { this.authenticationService.unregisterAuthenticationProvider(id); } - $onDidChangeAccounts(handle: number, accounts: ReadonlyArray) { - const id = this._handlers.get(handle); - if (id) { - this.authenticationService.accountsUpdate(id, accounts); - } + $onDidChangeSessions(id: string) { + this.authenticationService.sessionsUpdate(id); } } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 1cc6c7e6108..8cd44e1bc53 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -182,6 +182,24 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const authentication: typeof vscode.authentication = { registerAuthenticationProvider(provider: vscode.AuthenticationProvider): vscode.Disposable { return extHostAuthentication.registerAuthenticationProvider(provider); + }, + login(providerId: string): Promise { + return extHostAuthentication.$login(providerId); + }, + logout(providerId: string, accountId: string): Promise { + return extHostAuthentication.$logout(providerId, accountId); + }, + getSessions(providerId: string): Promise> { + return extHostAuthentication.$getSessions(providerId); + }, + get onDidChangeSessions() { + return extHostAuthentication.onDidChangeSessions; + }, + get onDidRegisterAuthenticationProvider() { + return extHostAuthentication.onDidRegisterAuthenticationProvider; + }, + get onDidUnregisterAuthenticationProvider() { + return extHostAuthentication.onDidUnregisterAuthenticationProvider; } }; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 12081788f04..c14f1d0b26f 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -148,9 +148,9 @@ export interface MainThreadCommentsShape extends IDisposable { } export interface MainThreadAuthenticationShape extends IDisposable { - $registerAuthenticationProvider(handle: number, id: string): void; - $unregisterAuthenticationProvider(handle: number): void; - $onDidChangeAccounts(handle: number, accounts: ReadonlyArray): void; + $registerAuthenticationProvider(id: string): void; + $unregisterAuthenticationProvider(id: string): void; + $onDidChangeSessions(id: string): void; } export interface MainThreadConfigurationShape extends IDisposable { @@ -898,10 +898,9 @@ export interface ExtHostLabelServiceShape { } export interface ExtHostAuthenticationShape { - $accounts(handle: number): Promise>; - $login(handle: number): Promise; - $logout(handle: number, accountId: string): Promise; - // TODO rmacfarlane + $getSessions(id: string): Promise>; + $login(id: string): Promise; + $logout(id: string, accountId: string): Promise; } export interface ExtHostSearchShape { diff --git a/src/vs/workbench/api/common/extHostAuthentication.ts b/src/vs/workbench/api/common/extHostAuthentication.ts index e726eb3c5fc..afde02d3918 100644 --- a/src/vs/workbench/api/common/extHostAuthentication.ts +++ b/src/vs/workbench/api/common/extHostAuthentication.ts @@ -9,75 +9,93 @@ import { Emitter, Event } from 'vs/base/common/event'; import { IMainContext, MainContext, MainThreadAuthenticationShape, ExtHostAuthenticationShape } from 'vs/workbench/api/common/extHost.protocol'; import { IDisposable } from 'vs/base/common/lifecycle'; +const _onDidUnregisterAuthenticationProvider = new Emitter(); +const _onDidChangeSessions = new Emitter(); + export class ExtHostAuthenticationProvider implements IDisposable { constructor(private _provider: vscode.AuthenticationProvider, - private readonly _handle: number, + private _id: string, private _proxy: MainThreadAuthenticationShape) { - this._provider.onDidChangeAccounts(x => this._proxy.$onDidChangeAccounts(this._handle, x)); + this._provider.onDidChangeSessions(x => { + this._proxy.$onDidChangeSessions(this._id); + _onDidChangeSessions.fire(this._id); + }); } - getAccounts(): Promise> { - return this._provider.getAccounts(); + getSessions(): Promise> { + return this._provider.getSessions(); } - login(): Promise { + login(): Promise { return this._provider.login(); } - logout(accountId: string): Promise { - return this._provider.logout(accountId); + logout(sessionId: string): Promise { + return this._provider.logout(sessionId); } dispose(): void { - this._proxy.$unregisterAuthenticationProvider(this._handle); + this._proxy.$unregisterAuthenticationProvider(this._id); + _onDidUnregisterAuthenticationProvider.fire(this._id); } } export class ExtHostAuthentication implements ExtHostAuthenticationShape { - public static _handlePool: number = 0; private _proxy: MainThreadAuthenticationShape; - private _authenticationProviders: Map = new Map(); + private _authenticationProviders: Map = new Map(); + + private _onDidRegisterAuthenticationProvider = new Emitter(); + readonly onDidRegisterAuthenticationProvider: Event = this._onDidRegisterAuthenticationProvider.event; + + readonly onDidUnregisterAuthenticationProvider: Event = _onDidUnregisterAuthenticationProvider.event; + + readonly onDidChangeSessions: Event = _onDidChangeSessions.event; constructor(mainContext: IMainContext) { this._proxy = mainContext.getProxy(MainContext.MainThreadAuthentication); + + this.onDidUnregisterAuthenticationProvider(providerId => { + this._authenticationProviders.delete(providerId); + }); } - private readonly _onDidRefreshToken = new Emitter(); - readonly onDidRefreshToken: Event = this._onDidRefreshToken.event; - registerAuthenticationProvider(provider: vscode.AuthenticationProvider) { - const handle = ExtHostAuthentication._handlePool++; - const authenticationProvider = new ExtHostAuthenticationProvider(provider, handle, this._proxy); - this._authenticationProviders.set(handle, authenticationProvider); + if (this._authenticationProviders.get(provider.id)) { + throw new Error(`An authentication provider with id '${provider.id}' is already registered.`); + } - this._proxy.$registerAuthenticationProvider(handle, provider.id); + const authenticationProvider = new ExtHostAuthenticationProvider(provider, provider.id, this._proxy); + this._authenticationProviders.set(provider.id, authenticationProvider); + + this._proxy.$registerAuthenticationProvider(provider.id); + this._onDidRegisterAuthenticationProvider.fire(provider.id); return authenticationProvider; } - $accounts(handle: number): Promise> { - const authProvider = this._authenticationProviders.get(handle); + $login(providerId: string): Promise { + const authProvider = this._authenticationProviders.get(providerId); if (authProvider) { - return Promise.resolve(authProvider.getAccounts()); + return Promise.resolve(authProvider.login()); } - throw new Error(`Unable to find authentication provider with handle: ${handle}`); + throw new Error(`Unable to find authentication provider with handle: ${0}`); } - $login(handle: number): Promise { - const authProvider = this._authenticationProviders.get(handle); + $logout(providerId: string, sessionId: string): Promise { + const authProvider = this._authenticationProviders.get(providerId); if (authProvider) { - return authProvider.login(); + return Promise.resolve(authProvider.logout(sessionId)); } - throw new Error(`Unable to find authentication provider with handle: ${handle}`); + throw new Error(`Unable to find authentication provider with handle: ${0}`); } - $logout(handle: number, accountId: string): Promise { - const authProvider = this._authenticationProviders.get(handle); + $getSessions(providerId: string): Promise> { + const authProvider = this._authenticationProviders.get(providerId); if (authProvider) { - return authProvider.logout(accountId); + return Promise.resolve(authProvider.getSessions()); } - throw new Error(`Unable to find authentication provider with handle: ${handle}`); + throw new Error(`Unable to find authentication provider with handle: ${0}`); } } diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 22304eeca47..0204707393c 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -33,8 +33,8 @@ import { UserDataSyncTrigger } from 'vs/workbench/contrib/userDataSync/browser/u import { timeout } from 'vs/base/common/async'; import { IOutputService } from 'vs/workbench/contrib/output/common/output'; import * as Constants from 'vs/workbench/contrib/logs/common/logConstants'; -import { IAuthenticationService, ChangeAccountEventData } from 'vs/workbench/services/authentication/browser/authenticationService'; -import { Account } from 'vs/editor/common/modes'; +import { IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService'; +import { Session } from 'vs/editor/common/modes'; import { isPromiseCanceledError } from 'vs/base/common/errors'; const enum MSAAuthStatus { @@ -58,7 +58,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo private readonly badgeDisposable = this._register(new MutableDisposable()); private readonly conflictsWarningDisposable = this._register(new MutableDisposable()); private readonly signInNotificationDisposable = this._register(new MutableDisposable()); - private _activeAccount: Account | undefined; + private _activeAccount: Session | undefined; constructor( @IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService, @@ -89,7 +89,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING))(() => this.onDidChangeEnablement())); this._register(this.authenticationService.onDidRegisterAuthenticationProvider(e => this.onDidRegisterAuthenticationProvider(e))); this._register(this.authenticationService.onDidUnregisterAuthenticationProvider(e => this.onDidUnregisterAuthenticationProvider(e))); - this._register(this.authenticationService.onDidChangeAccounts(e => this.onDidChangeAccounts(e))); + this._register(this.authenticationService.onDidChangeSessions(e => this.onDidChangeSessions(e))); this.registerActions(); this.initializeActiveAccount().then(_ => { if (isWeb) { @@ -102,7 +102,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } private async initializeActiveAccount(): Promise { - const accounts = await this.authenticationService.getAccounts(MSA); + const accounts = await this.authenticationService.getSessions(MSA); // MSA provider has not yet been registered if (!accounts) { return; @@ -130,11 +130,11 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } } - get activeAccount(): Account | undefined { + get activeAccount(): Session | undefined { return this._activeAccount; } - set activeAccount(account: Account | undefined) { + set activeAccount(account: Session | undefined) { this._activeAccount = account; if (account) { @@ -148,11 +148,12 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo this.updateBadge(); } - private onDidChangeAccounts(event: ChangeAccountEventData): void { - if (event.providerId === MSA) { + private async onDidChangeSessions(providerId: string): Promise { + if (providerId === MSA) { if (this.activeAccount) { // Try to update existing account, case where access token has been refreshed - const matchingAccount = event.accounts.filter(a => a.id === this.activeAccount?.id)[0]; + const accounts = (await this.authenticationService.getSessions(MSA) || []); + const matchingAccount = accounts.filter(a => a.id === this.activeAccount?.id)[0]; this.activeAccount = matchingAccount; } else { this.initializeActiveAccount(); diff --git a/src/vs/workbench/services/authentication/browser/authenticationService.ts b/src/vs/workbench/services/authentication/browser/authenticationService.ts index ca2a2fd8e1a..cd2febc38ee 100644 --- a/src/vs/workbench/services/authentication/browser/authenticationService.ts +++ b/src/vs/workbench/services/authentication/browser/authenticationService.ts @@ -5,31 +5,26 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; -import { Account } from 'vs/editor/common/modes'; +import { Session } from 'vs/editor/common/modes'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { MainThreadAuthenticationProvider } from 'vs/workbench/api/browser/mainThreadAuthentication'; export const IAuthenticationService = createDecorator('IAuthenticationService'); -export interface ChangeAccountEventData { - providerId: string; - accounts: ReadonlyArray; -} - export interface IAuthenticationService { _serviceBrand: undefined; registerAuthenticationProvider(id: string, provider: MainThreadAuthenticationProvider): void; unregisterAuthenticationProvider(id: string): void; - accountsUpdate(providerId: string, accounts: ReadonlyArray): void; + sessionsUpdate(providerId: string): void; readonly onDidRegisterAuthenticationProvider: Event; readonly onDidUnregisterAuthenticationProvider: Event; - readonly onDidChangeAccounts: Event; - getAccounts(providerId: string): Promise | undefined>; - login(providerId: string): Promise; + readonly onDidChangeSessions: Event; + getSessions(providerId: string): Promise | undefined>; + login(providerId: string): Promise; logout(providerId: string, accountId: string): Promise; } @@ -44,8 +39,8 @@ export class AuthenticationService extends Disposable implements IAuthentication private _onDidUnregisterAuthenticationProvider: Emitter = this._register(new Emitter()); readonly onDidUnregisterAuthenticationProvider: Event = this._onDidUnregisterAuthenticationProvider.event; - private _onDidChangeAccounts: Emitter = this._register(new Emitter()); - readonly onDidChangeAccounts: Event = this._onDidChangeAccounts.event; + private _onDidChangeSessions: Emitter = this._register(new Emitter()); + readonly onDidChangeSessions: Event = this._onDidChangeSessions.event; constructor() { super(); @@ -61,20 +56,20 @@ export class AuthenticationService extends Disposable implements IAuthentication this._onDidUnregisterAuthenticationProvider.fire(id); } - accountsUpdate(providerId: string, accounts: ReadonlyArray): void { - this._onDidChangeAccounts.fire({ providerId, accounts }); + sessionsUpdate(id: string): void { + this._onDidChangeSessions.fire(id); } - async getAccounts(id: string): Promise | undefined> { + async getSessions(id: string): Promise | undefined> { const authProvider = this._authenticationProviders.get(id); if (authProvider) { - return await authProvider.accounts(); + return await authProvider.getSessions(); } return undefined; } - async login(id: string): Promise { + async login(id: string): Promise { const authProvider = this._authenticationProviders.get(id); if (authProvider) { return authProvider.login();