diff --git a/extensions/vscode-account/.vscodeignore b/extensions/vscode-account/.vscodeignore
new file mode 100644
index 00000000000..ed3f9d37c1f
--- /dev/null
+++ b/extensions/vscode-account/.vscodeignore
@@ -0,0 +1,10 @@
+.vscode/**
+.vscode-test/**
+out/test/**
+src/**
+.gitignore
+vsc-extension-quickstart.md
+**/tsconfig.json
+**/tslint.json
+**/*.map
+**/*.ts
\ No newline at end of file
diff --git a/src/vs/platform/auth/common/auth.css b/extensions/vscode-account/media/auth.css
similarity index 100%
rename from src/vs/platform/auth/common/auth.css
rename to extensions/vscode-account/media/auth.css
diff --git a/src/vs/platform/auth/common/auth.html b/extensions/vscode-account/media/auth.html
similarity index 99%
rename from src/vs/platform/auth/common/auth.html
rename to extensions/vscode-account/media/auth.html
index 8fe3e50e7b7..0fcba4e3c62 100644
--- a/src/vs/platform/auth/common/auth.html
+++ b/extensions/vscode-account/media/auth.html
@@ -1,6 +1,7 @@
+
@@ -8,6 +9,7 @@
+
Visual Studio Code
@@ -32,4 +34,5 @@
}
+
diff --git a/extensions/vscode-account/package.json b/extensions/vscode-account/package.json
new file mode 100644
index 00000000000..46b6c92ed36
--- /dev/null
+++ b/extensions/vscode-account/package.json
@@ -0,0 +1,30 @@
+{
+ "name": "login",
+ "publisher": "vscode",
+ "displayName": "Account",
+ "description": "",
+ "version": "0.0.1",
+ "engines": {
+ "vscode": "^1.42.0"
+ },
+ "categories": [
+ "Other"
+ ],
+ "enableProposedApi": true,
+ "activationEvents": [
+ "*"
+ ],
+ "main": "./out/extension.js",
+ "scripts": {
+ "vscode:prepublish": "npm run compile",
+ "compile": "tsc -p ./",
+ "watch": "tsc -watch -p ./"
+ },
+ "devDependencies": {
+ "typescript": "^3.7.4",
+ "tslint": "^5.12.1",
+ "@types/node": "^10.12.21",
+ "@types/keytar": "^4.0.1",
+ "@types/vscode": "^1.41.0"
+ }
+}
diff --git a/extensions/vscode-account/src/AADHelper.ts b/extensions/vscode-account/src/AADHelper.ts
new file mode 100644
index 00000000000..b15aec32697
--- /dev/null
+++ b/extensions/vscode-account/src/AADHelper.ts
@@ -0,0 +1,245 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as crypto from 'crypto';
+import * as vscode from 'vscode';
+import * as https from 'https';
+import * as querystring from 'querystring';
+import { keychain } from './keychain';
+import { toBase64UrlEncoding } from './utils';
+import { createServer, startServer } from './authServer';
+
+const redirectUrl = 'https://vscode-redirect.azurewebsites.net/';
+const loginEndpointUrl = 'https://login.microsoftonline.com/';
+const clientId = 'aebc6443-996d-45c2-90f0-388ff96faa56';
+const scope = 'https://management.core.windows.net/.default offline_access';
+const tenant = 'common';
+
+interface IToken {
+ expiresIn: string; // How long access token is valid, in seconds
+ accessToken: string;
+ refreshToken: string;
+}
+
+export const onDidChangeAccounts = new vscode.EventEmitter();
+
+export class AzureActiveDirectoryService {
+ private _token: IToken | undefined;
+ private _refreshTimeout: NodeJS.Timeout | undefined;
+
+ public async initialize(): Promise {
+ const existingRefreshToken = await keychain.getToken();
+ if (existingRefreshToken) {
+ await this.refreshToken(existingRefreshToken);
+ }
+ }
+
+ private tokenToAccount(token: IToken): vscode.Account {
+ return {
+ id: '',
+ accessToken: token.accessToken,
+ displayName: this.getDisplayNameFromToken(token.accessToken)
+ };
+ }
+
+ private getDisplayNameFromToken(accessToken: string): string {
+ let displayName = 'user@example.com';
+ try {
+ // TODO fixme
+ displayName = JSON.parse(atob(accessToken.split('.')[1]));
+ } catch (e) {
+ // Fall back to example display name
+ }
+
+ return displayName;
+ }
+
+ get accounts(): vscode.Account[] {
+ return this._token ? [this.tokenToAccount(this._token)] : [];
+ }
+
+ public async login(): Promise {
+ const nonce = crypto.randomBytes(16).toString('base64');
+ const { server, redirectPromise, codePromise } = createServer(nonce);
+
+ let token: IToken | undefined;
+ try {
+ const port = await startServer(server);
+ vscode.env.openExternal(vscode.Uri.parse(`http://localhost:${port}/signin?nonce=${encodeURIComponent(nonce)}`));
+
+ const redirectReq = await redirectPromise;
+ if ('err' in redirectReq) {
+ const { err, res } = redirectReq;
+ res.writeHead(302, { Location: `/?error=${encodeURIComponent(err && err.message || 'Unknown error')}` });
+ res.end();
+ throw err;
+ }
+
+ const host = redirectReq.req.headers.host || '';
+ const updatedPortStr = (/^[^:]+:(\d+)$/.exec(Array.isArray(host) ? host[0] : host) || [])[1];
+ const updatedPort = updatedPortStr ? parseInt(updatedPortStr, 10) : port;
+
+ const state = `${updatedPort},${encodeURIComponent(nonce)}`;
+
+ const codeVerifier = toBase64UrlEncoding(crypto.randomBytes(32).toString('base64'));
+ const codeChallenge = toBase64UrlEncoding(crypto.createHash('sha256').update(codeVerifier).digest('base64'));
+ const loginUrl = `${loginEndpointUrl}${tenant}/oauth2/v2.0/authorize?response_type=code&response_mode=query&client_id=${encodeURIComponent(clientId)}&redirect_uri=${encodeURIComponent(redirectUrl)}&state=${state}&scope=${encodeURIComponent(scope)}&prompt=select_account&code_challenge_method=S256&code_challenge=${codeChallenge}`;
+
+ await redirectReq.res.writeHead(302, { Location: loginUrl });
+ redirectReq.res.end();
+
+ const codeRes = await codePromise;
+ const res = codeRes.res;
+
+ try {
+ if ('err' in codeRes) {
+ throw codeRes.err;
+ }
+ token = await this.exchangeCodeForToken(codeRes.code, codeVerifier);
+ this.setToken(token);
+ res.writeHead(302, { Location: '/' });
+ res.end();
+ } catch (err) {
+ res.writeHead(302, { Location: `/?error=${encodeURIComponent(err && err.message || 'Unknown error')}` });
+ res.end();
+ }
+ } finally {
+ setTimeout(() => {
+ server.close();
+ }, 5000);
+ }
+ }
+
+ private async setToken(token: IToken): Promise {
+ this._token = token;
+
+ if (this._refreshTimeout) {
+ clearTimeout(this._refreshTimeout);
+ }
+
+ this._refreshTimeout = setTimeout(async () => {
+ try {
+ await this.refreshToken(token.refreshToken);
+ } catch (e) {
+ vscode.window.showErrorMessage(`You have been signed out.`);
+ this._token = undefined;
+ } finally {
+ onDidChangeAccounts.fire(this.accounts);
+ }
+ }, 1000 * (parseInt(token.expiresIn) - 10));
+
+ await keychain.setToken(token.refreshToken);
+ }
+
+ private async exchangeCodeForToken(code: string, codeVerifier: string): Promise {
+ return new Promise((resolve: (value: IToken) => void, reject) => {
+ try {
+ const postData = querystring.stringify({
+ grant_type: 'authorization_code',
+ code: code,
+ client_id: clientId,
+ scope: scope,
+ code_verifier: codeVerifier,
+ redirect_uri: redirectUrl
+ });
+
+ const tokenUrl = vscode.Uri.parse(`${loginEndpointUrl}${tenant}/oauth2/v2.0/token`);
+
+ const post = https.request({
+ host: tokenUrl.authority,
+ path: tokenUrl.path,
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ 'Content-Length': postData.length
+ }
+ }, result => {
+ const buffer: Buffer[] = [];
+ result.on('data', (chunk: Buffer) => {
+ buffer.push(chunk);
+ });
+ result.on('end', () => {
+ if (result.statusCode === 200) {
+ const json = JSON.parse(Buffer.concat(buffer).toString());
+ resolve({
+ expiresIn: json.expires_in,
+ accessToken: json.access_token,
+ refreshToken: json.refresh_token
+ });
+ } else {
+ reject(new Error('Unable to login.'));
+ }
+ });
+ });
+
+ post.write(postData);
+
+ post.end();
+ post.on('error', err => {
+ reject(err);
+ });
+
+ } catch (e) {
+ reject(e);
+ }
+ });
+ }
+
+ private async refreshToken(refreshToken: string): Promise {
+ return new Promise((resolve: (value: IToken) => void, reject) => {
+ const postData = querystring.stringify({
+ refresh_token: refreshToken,
+ client_id: clientId,
+ grant_type: 'refresh_token',
+ scope: scope
+ });
+
+ const post = https.request({
+ host: 'login.microsoftonline.com',
+ path: `/${tenant}/oauth2/v2.0/token`,
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ 'Content-Length': postData.length
+ }
+ }, result => {
+ const buffer: Buffer[] = [];
+ result.on('data', (chunk: Buffer) => {
+ buffer.push(chunk);
+ });
+ result.on('end', () => {
+ if (result.statusCode === 200) {
+ const json = JSON.parse(Buffer.concat(buffer).toString());
+ const token = {
+ expiresIn: json.expires_in,
+ accessToken: json.access_token,
+ refreshToken: json.refresh_token
+ };
+ this.setToken(token);
+ resolve(token);
+ } else {
+ vscode.window.showInformationMessage(`error`);
+ reject(new Error('Bad!'));
+ }
+ });
+ });
+
+ post.write(postData);
+
+ post.end();
+ post.on('error', err => {
+ reject(err);
+ });
+ });
+ }
+
+ public async logout() {
+ delete this._token;
+ await keychain.deleteToken();
+ if (this._refreshTimeout) {
+ clearTimeout(this._refreshTimeout);
+ }
+ }
+}
diff --git a/src/vs/platform/auth/electron-browser/authServer.ts b/extensions/vscode-account/src/authServer.ts
similarity index 81%
rename from src/vs/platform/auth/electron-browser/authServer.ts
rename to extensions/vscode-account/src/authServer.ts
index 57bd4fdfb5f..b78803cec16 100644
--- a/src/vs/platform/auth/electron-browser/authServer.ts
+++ b/extensions/vscode-account/src/authServer.ts
@@ -7,14 +7,46 @@ import * as http from 'http';
import * as url from 'url';
import * as fs from 'fs';
import * as net from 'net';
-import { getPathFromAmdModule } from 'vs/base/common/amd';
-import { assertIsDefined } from 'vs/base/common/types';
+import * as path from 'path';
interface Deferred {
resolve: (result: T | Promise) => void;
reject: (reason: any) => void;
}
+const _typeof = {
+ number: 'number',
+ string: 'string',
+ undefined: 'undefined',
+ object: 'object',
+ function: 'function'
+};
+
+/**
+ * @returns whether the provided parameter is undefined.
+ */
+export function isUndefined(obj: any): obj is undefined {
+ return typeof (obj) === _typeof.undefined;
+}
+
+/**
+ * @returns whether the provided parameter is undefined or null.
+ */
+export function isUndefinedOrNull(obj: any): obj is undefined | null {
+ return isUndefined(obj) || obj === null;
+}
+
+/**
+ * Asserts that the argument passed in is neither undefined nor null.
+ */
+export function assertIsDefined(arg: T | null | undefined): T {
+ if (isUndefinedOrNull(arg)) {
+ throw new Error('Assertion Failed: argument is undefined or null');
+ }
+
+ return arg;
+}
+
export function createTerminateServer(server: http.Server) {
const sockets: Record = {};
let socketCount = 0;
@@ -140,10 +172,10 @@ export function createServer(nonce: string) {
}
break;
case '/':
- sendFile(res, getPathFromAmdModule(require, '../common/auth.html'), 'text/html; charset=utf-8');
+ sendFile(res, path.join(__dirname, '../media/auth.html'), 'text/html; charset=utf-8');
break;
case '/auth.css':
- sendFile(res, getPathFromAmdModule(require, '../common/auth.css'), 'text/css; charset=utf-8');
+ sendFile(res, path.join(__dirname, '../media/auth.css'), 'text/css; charset=utf-8');
break;
case '/callback':
deferredCode.resolve(callback(nonce, reqUrl)
diff --git a/extensions/vscode-account/src/extension.ts b/extensions/vscode-account/src/extension.ts
new file mode 100644
index 00000000000..53793dc4c43
--- /dev/null
+++ b/extensions/vscode-account/src/extension.ts
@@ -0,0 +1,38 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as vscode from 'vscode';
+import { AzureActiveDirectoryService, onDidChangeAccounts } from './AADHelper';
+
+export async function activate(context: vscode.ExtensionContext) {
+
+ const loginService = new AzureActiveDirectoryService();
+
+ await loginService.initialize();
+
+ vscode.authentication.registerAuthenticationProvider({
+ id: 'MSA',
+ displayName: 'Microsoft Account', // TODO localize
+ onDidChangeAccounts: onDidChangeAccounts.event,
+ accounts: loginService.accounts,
+ login: async () => {
+ try {
+ await loginService.login();
+ return loginService.accounts[0]!;
+ } catch (e) {
+ vscode.window.showErrorMessage(`Logging in failed: ${e}`);
+ throw e;
+ }
+ },
+ logout: async (id: string) => {
+ return loginService.logout();
+ }
+ });
+
+ return;
+}
+
+// this method is called when your extension is deactivated
+export function deactivate() { }
diff --git a/extensions/vscode-account/src/keychain.ts b/extensions/vscode-account/src/keychain.ts
new file mode 100644
index 00000000000..a53d848c76c
--- /dev/null
+++ b/extensions/vscode-account/src/keychain.ts
@@ -0,0 +1,66 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+// keytar depends on a native module shipped in vscode, so this is
+// how we load it
+import * as keytarType from 'keytar';
+
+function getKeytar(): Keytar | undefined {
+ try {
+ return require('keytar');
+ } catch (err) {
+ console.log(err);
+ }
+
+ return undefined;
+}
+
+export type Keytar = {
+ getPassword: typeof keytarType['getPassword'];
+ setPassword: typeof keytarType['setPassword'];
+ deletePassword: typeof keytarType['deletePassword'];
+};
+
+const SERVICE_ID = 'vscode.login';
+const ACCOUNT_ID = 'account';
+
+export class Keychain {
+ private keytar: Keytar;
+
+ constructor() {
+ const keytar = getKeytar();
+ if (!keytar) {
+ throw new Error('System keychain unavailable');
+ }
+
+ this.keytar = keytar;
+ }
+
+ async setToken(token: string): Promise {
+ try {
+ return await this.keytar.setPassword(SERVICE_ID, ACCOUNT_ID, token);
+ } catch (e) {
+ // Ignore
+ }
+ }
+
+ async getToken() {
+ try {
+ return await this.keytar.getPassword(SERVICE_ID, ACCOUNT_ID);
+ } catch (e) {
+ // Ignore
+ }
+ }
+
+ async deleteToken() {
+ try {
+ return await this.keytar.deletePassword(SERVICE_ID, ACCOUNT_ID);
+ } catch (e) {
+ // Ignore
+ }
+ }
+}
+
+export const keychain = new Keychain();
diff --git a/extensions/vscode-account/src/utils.ts b/extensions/vscode-account/src/utils.ts
new file mode 100644
index 00000000000..164f2236221
--- /dev/null
+++ b/extensions/vscode-account/src/utils.ts
@@ -0,0 +1,8 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+export function toBase64UrlEncoding(base64string: string) {
+ return base64string.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_'); // Need to use base64url encoding
+}
diff --git a/extensions/vscode-account/src/vscode.proposed.d.ts b/extensions/vscode-account/src/vscode.proposed.d.ts
new file mode 100644
index 00000000000..0f4a53fee1a
--- /dev/null
+++ b/extensions/vscode-account/src/vscode.proposed.d.ts
@@ -0,0 +1,46 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+/**
+ * This is the place for API experiments and proposals.
+ * These API are NOT stable and subject to change. They are only available in the Insiders
+ * distribution and CANNOT be used in published extensions.
+ *
+ * To test these API in local environment:
+ * - Use Insiders release of VS Code.
+ * - Add `"enableProposedApi": true` to your package.json.
+ * - Copy this file to your project.
+ */
+
+declare module 'vscode' {
+
+ export interface Account {
+ readonly id: string;
+ readonly accessToken: string;
+ readonly displayName: string;
+ }
+
+ export interface AuthenticationProvider {
+ readonly id: string;
+ readonly displayName: string;
+
+ readonly accounts: ReadonlyArray;
+ readonly onDidChangeAccounts: Event>;
+
+ login(): Promise;
+ logout(accountId: string): Promise;
+ }
+
+ export namespace authentication {
+ export function registerAuthenticationProvider(provider: AuthenticationProvider): Disposable;
+ }
+
+ // #region Ben - extension auth flow (desktop+web)
+
+ export namespace env {
+
+ export function asExternalUri(target: Uri): Thenable
+ }
+}
diff --git a/extensions/vscode-account/tsconfig.json b/extensions/vscode-account/tsconfig.json
new file mode 100644
index 00000000000..46be6dc9581
--- /dev/null
+++ b/extensions/vscode-account/tsconfig.json
@@ -0,0 +1,24 @@
+{
+ "compilerOptions": {
+ "module": "commonjs",
+ "target": "es6",
+ "outDir": "out",
+ "lib": [
+ "es6",
+ "es2016",
+ "dom"
+ ],
+ "typeRoots": [
+ "node_modules/@types",
+ "src/typings"
+ ],
+ "sourceMap": true,
+ "rootDir": "src",
+ "strict": true,
+ "noImplicitAny": true
+ },
+ "exclude": [
+ "node_modules",
+ ".vscode-test"
+ ]
+}
diff --git a/extensions/vscode-account/yarn.lock b/extensions/vscode-account/yarn.lock
new file mode 100644
index 00000000000..4fc295de4b9
--- /dev/null
+++ b/extensions/vscode-account/yarn.lock
@@ -0,0 +1,658 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@babel/code-frame@^7.0.0":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e"
+ integrity sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==
+ dependencies:
+ "@babel/highlight" "^7.8.3"
+
+"@babel/highlight@^7.8.3":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.8.3.tgz#28f173d04223eaaa59bc1d439a3836e6d1265797"
+ integrity sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==
+ dependencies:
+ chalk "^2.0.0"
+ esutils "^2.0.2"
+ js-tokens "^4.0.0"
+
+"@types/keytar@^4.0.1":
+ version "4.4.2"
+ resolved "https://registry.yarnpkg.com/@types/keytar/-/keytar-4.4.2.tgz#49ef917d6cbb4f19241c0ab50cd35097b5729b32"
+ integrity sha512-xtQcDj9ruGnMwvSu1E2BH4SFa5Dv2PvSPd0CKEBLN5hEj/v5YpXJY+B6hAfuKIbvEomD7vJTc/P1s1xPNh2kRw==
+ dependencies:
+ keytar "*"
+
+"@types/node@^10.12.21":
+ version "10.17.13"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.13.tgz#ccebcdb990bd6139cd16e84c39dc2fb1023ca90c"
+ integrity sha512-pMCcqU2zT4TjqYFrWtYHKal7Sl30Ims6ulZ4UFXxI4xbtQqK/qqKwkDoBFCfooRqqmRu9vY3xaJRwxSh673aYg==
+
+"@types/vscode@^1.41.0":
+ version "1.41.0"
+ resolved "https://registry.yarnpkg.com/@types/vscode/-/vscode-1.41.0.tgz#b0d75920220f84e07093285e59180c0f11d336cd"
+ integrity sha512-7SfeY5u9jgiELwxyLB3z7l6l/GbN9CqpCQGkcRlB7tKRFBxzbz2PoBfGrLxI1vRfUCIq5+hg5vtDHExwq5j3+A==
+
+ansi-regex@^2.0.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
+ integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8=
+
+ansi-regex@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
+ integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=
+
+ansi-styles@^3.2.1:
+ version "3.2.1"
+ resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
+ integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
+ dependencies:
+ color-convert "^1.9.0"
+
+aproba@^1.0.3:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
+ integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==
+
+are-we-there-yet@~1.1.2:
+ version "1.1.5"
+ resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21"
+ integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==
+ dependencies:
+ delegates "^1.0.0"
+ readable-stream "^2.0.6"
+
+argparse@^1.0.7:
+ version "1.0.10"
+ resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
+ integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==
+ dependencies:
+ sprintf-js "~1.0.2"
+
+balanced-match@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
+ integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
+
+bl@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/bl/-/bl-3.0.0.tgz#3611ec00579fd18561754360b21e9f784500ff88"
+ integrity sha512-EUAyP5UHU5hxF8BPT0LKW8gjYLhq1DQIcneOX/pL/m2Alo+OYDQAJlHq+yseMP50Os2nHXOSic6Ss3vSQeyf4A==
+ dependencies:
+ readable-stream "^3.0.1"
+
+brace-expansion@^1.1.7:
+ version "1.1.11"
+ resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
+ integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
+ dependencies:
+ balanced-match "^1.0.0"
+ concat-map "0.0.1"
+
+builtin-modules@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
+ integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=
+
+chalk@^2.0.0, chalk@^2.3.0:
+ version "2.4.2"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
+ integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
+ dependencies:
+ ansi-styles "^3.2.1"
+ escape-string-regexp "^1.0.5"
+ supports-color "^5.3.0"
+
+chownr@^1.1.1:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.3.tgz#42d837d5239688d55f303003a508230fa6727142"
+ integrity sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==
+
+code-point-at@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
+ integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=
+
+color-convert@^1.9.0:
+ version "1.9.3"
+ resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
+ integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
+ dependencies:
+ color-name "1.1.3"
+
+color-name@1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
+ integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
+
+commander@^2.12.1:
+ version "2.20.3"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
+ integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
+
+concat-map@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
+ integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
+
+console-control-strings@^1.0.0, console-control-strings@~1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
+ integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=
+
+core-util-is@~1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
+ integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
+
+decompress-response@^4.2.0:
+ version "4.2.1"
+ resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-4.2.1.tgz#414023cc7a302da25ce2ec82d0d5238ccafd8986"
+ integrity sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==
+ dependencies:
+ mimic-response "^2.0.0"
+
+deep-extend@^0.6.0:
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
+ integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
+
+delegates@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
+ integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=
+
+detect-libc@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
+ integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=
+
+diff@^4.0.1:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
+ integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
+
+end-of-stream@^1.1.0, end-of-stream@^1.4.1:
+ version "1.4.4"
+ resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
+ integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
+ dependencies:
+ once "^1.4.0"
+
+escape-string-regexp@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
+ integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
+
+esprima@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
+ integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
+
+esutils@^2.0.2:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
+ integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
+
+expand-template@^2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c"
+ integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==
+
+fs-constants@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
+ integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
+
+fs.realpath@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
+ integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
+
+gauge@~2.7.3:
+ version "2.7.4"
+ resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7"
+ integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=
+ dependencies:
+ aproba "^1.0.3"
+ console-control-strings "^1.0.0"
+ has-unicode "^2.0.0"
+ object-assign "^4.1.0"
+ signal-exit "^3.0.0"
+ string-width "^1.0.1"
+ strip-ansi "^3.0.1"
+ wide-align "^1.1.0"
+
+github-from-package@0.0.0:
+ version "0.0.0"
+ resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce"
+ integrity sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4=
+
+glob@^7.1.1:
+ version "7.1.6"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
+ integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
+ dependencies:
+ fs.realpath "^1.0.0"
+ inflight "^1.0.4"
+ inherits "2"
+ minimatch "^3.0.4"
+ once "^1.3.0"
+ path-is-absolute "^1.0.0"
+
+has-flag@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
+ integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
+
+has-unicode@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
+ integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=
+
+inflight@^1.0.4:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
+ integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
+ dependencies:
+ once "^1.3.0"
+ wrappy "1"
+
+inherits@2, inherits@^2.0.3, inherits@~2.0.3:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
+ integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
+
+ini@~1.3.0:
+ version "1.3.5"
+ resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
+ integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
+
+is-fullwidth-code-point@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb"
+ integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs=
+ dependencies:
+ number-is-nan "^1.0.0"
+
+is-fullwidth-code-point@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
+ integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=
+
+isarray@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
+ integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
+
+js-tokens@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
+ integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
+
+js-yaml@^3.13.1:
+ version "3.13.1"
+ resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847"
+ integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==
+ dependencies:
+ argparse "^1.0.7"
+ esprima "^4.0.0"
+
+keytar@*:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/keytar/-/keytar-5.0.0.tgz#c89b6b7a4608fd7af633d9f8474b1a7eb97cbe6f"
+ integrity sha512-a5UheK59YOlJf9i+2Osaj/kkH6mK0RCHVMtJ84u6ZfbfRIbOJ/H4b5VlOF/LgNHF6s78dRSBzZnvIuPiBKv6wg==
+ dependencies:
+ nan "2.14.0"
+ prebuild-install "5.3.3"
+
+mimic-response@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.0.0.tgz#996a51c60adf12cb8a87d7fb8ef24c2f3d5ebb46"
+ integrity sha512-8ilDoEapqA4uQ3TwS0jakGONKXVJqpy+RpM+3b7pLdOjghCrEiGp9SRkFbUHAmZW9vdnrENWHjaweIoTIJExSQ==
+
+minimatch@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
+ integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
+ dependencies:
+ brace-expansion "^1.1.7"
+
+minimist@0.0.8:
+ version "0.0.8"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
+ integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
+
+minimist@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
+ integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
+
+mkdirp@^0.5.1:
+ version "0.5.1"
+ resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
+ integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
+ dependencies:
+ minimist "0.0.8"
+
+nan@2.14.0:
+ version "2.14.0"
+ resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c"
+ integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==
+
+napi-build-utils@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.1.tgz#1381a0f92c39d66bf19852e7873432fc2123e508"
+ integrity sha512-boQj1WFgQH3v4clhu3mTNfP+vOBxorDlE8EKiMjUlLG3C4qAESnn9AxIOkFgTR2c9LtzNjPrjS60cT27ZKBhaA==
+
+node-abi@^2.7.0:
+ version "2.13.0"
+ resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.13.0.tgz#e2f2ec444d0aca3ea1b3874b6de41d1665828f63"
+ integrity sha512-9HrZGFVTR5SOu3PZAnAY2hLO36aW1wmA+FDsVkr85BTST32TLCA1H/AEcatVRAsWLyXS3bqUDYCAjq5/QGuSTA==
+ dependencies:
+ semver "^5.4.1"
+
+noop-logger@^0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/noop-logger/-/noop-logger-0.1.1.tgz#94a2b1633c4f1317553007d8966fd0e841b6a4c2"
+ integrity sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI=
+
+npmlog@^4.0.1:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
+ integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==
+ dependencies:
+ are-we-there-yet "~1.1.2"
+ console-control-strings "~1.1.0"
+ gauge "~2.7.3"
+ set-blocking "~2.0.0"
+
+number-is-nan@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
+ integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=
+
+object-assign@^4.1.0:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
+ integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
+
+once@^1.3.0, once@^1.3.1, once@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
+ integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
+ dependencies:
+ wrappy "1"
+
+path-is-absolute@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
+ integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
+
+path-parse@^1.0.6:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
+ integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==
+
+prebuild-install@5.3.3:
+ version "5.3.3"
+ resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-5.3.3.tgz#ef4052baac60d465f5ba6bf003c9c1de79b9da8e"
+ integrity sha512-GV+nsUXuPW2p8Zy7SarF/2W/oiK8bFQgJcncoJ0d7kRpekEA0ftChjfEaF9/Y+QJEc/wFR7RAEa8lYByuUIe2g==
+ dependencies:
+ detect-libc "^1.0.3"
+ expand-template "^2.0.3"
+ github-from-package "0.0.0"
+ minimist "^1.2.0"
+ mkdirp "^0.5.1"
+ napi-build-utils "^1.0.1"
+ node-abi "^2.7.0"
+ noop-logger "^0.1.1"
+ npmlog "^4.0.1"
+ pump "^3.0.0"
+ rc "^1.2.7"
+ simple-get "^3.0.3"
+ tar-fs "^2.0.0"
+ tunnel-agent "^0.6.0"
+ which-pm-runs "^1.0.0"
+
+process-nextick-args@~2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
+ integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
+
+pump@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
+ integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==
+ dependencies:
+ end-of-stream "^1.1.0"
+ once "^1.3.1"
+
+rc@^1.2.7:
+ version "1.2.8"
+ resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
+ integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==
+ dependencies:
+ deep-extend "^0.6.0"
+ ini "~1.3.0"
+ minimist "^1.2.0"
+ strip-json-comments "~2.0.1"
+
+readable-stream@^2.0.6:
+ version "2.3.7"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
+ integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
+ dependencies:
+ core-util-is "~1.0.0"
+ inherits "~2.0.3"
+ isarray "~1.0.0"
+ process-nextick-args "~2.0.0"
+ safe-buffer "~5.1.1"
+ string_decoder "~1.1.1"
+ util-deprecate "~1.0.1"
+
+readable-stream@^3.0.1, readable-stream@^3.1.1:
+ version "3.4.0"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.4.0.tgz#a51c26754658e0a3c21dbf59163bd45ba6f447fc"
+ integrity sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==
+ dependencies:
+ inherits "^2.0.3"
+ string_decoder "^1.1.1"
+ util-deprecate "^1.0.1"
+
+resolve@^1.3.2:
+ version "1.14.2"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.14.2.tgz#dbf31d0fa98b1f29aa5169783b9c290cb865fea2"
+ integrity sha512-EjlOBLBO1kxsUxsKjLt7TAECyKW6fOh1VRkykQkKGzcBbjjPIxBqGh0jf7GJ3k/f5mxMqW3htMD3WdTUVtW8HQ==
+ dependencies:
+ path-parse "^1.0.6"
+
+safe-buffer@^5.0.1, safe-buffer@~5.2.0:
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519"
+ integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==
+
+safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
+ integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
+
+semver@^5.3.0, semver@^5.4.1:
+ version "5.7.1"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
+ integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
+
+set-blocking@~2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
+ integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
+
+signal-exit@^3.0.0:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
+ integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=
+
+simple-concat@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.0.tgz#7344cbb8b6e26fb27d66b2fc86f9f6d5997521c6"
+ integrity sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY=
+
+simple-get@^3.0.3:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.1.0.tgz#b45be062435e50d159540b576202ceec40b9c6b3"
+ integrity sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==
+ dependencies:
+ decompress-response "^4.2.0"
+ once "^1.3.1"
+ simple-concat "^1.0.0"
+
+sprintf-js@~1.0.2:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
+ integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
+
+string-width@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
+ integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=
+ dependencies:
+ code-point-at "^1.0.0"
+ is-fullwidth-code-point "^1.0.0"
+ strip-ansi "^3.0.0"
+
+"string-width@^1.0.2 || 2":
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
+ integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==
+ dependencies:
+ is-fullwidth-code-point "^2.0.0"
+ strip-ansi "^4.0.0"
+
+string_decoder@^1.1.1:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
+ integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
+ dependencies:
+ safe-buffer "~5.2.0"
+
+string_decoder@~1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
+ integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
+ dependencies:
+ safe-buffer "~5.1.0"
+
+strip-ansi@^3.0.0, strip-ansi@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
+ integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=
+ dependencies:
+ ansi-regex "^2.0.0"
+
+strip-ansi@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f"
+ integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8=
+ dependencies:
+ ansi-regex "^3.0.0"
+
+strip-json-comments@~2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
+ integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
+
+supports-color@^5.3.0:
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
+ integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
+ dependencies:
+ has-flag "^3.0.0"
+
+tar-fs@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.0.0.tgz#677700fc0c8b337a78bee3623fdc235f21d7afad"
+ integrity sha512-vaY0obB6Om/fso8a8vakQBzwholQ7v5+uy+tF3Ozvxv1KNezmVQAiWtcNmMHFSFPqL3dJA8ha6gdtFbfX9mcxA==
+ dependencies:
+ chownr "^1.1.1"
+ mkdirp "^0.5.1"
+ pump "^3.0.0"
+ tar-stream "^2.0.0"
+
+tar-stream@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.0.tgz#d1aaa3661f05b38b5acc9b7020efdca5179a2cc3"
+ integrity sha512-+DAn4Nb4+gz6WZigRzKEZl1QuJVOLtAwwF+WUxy1fJ6X63CaGaUAxJRD2KEn1OMfcbCjySTYpNC6WmfQoIEOdw==
+ dependencies:
+ bl "^3.0.0"
+ end-of-stream "^1.4.1"
+ fs-constants "^1.0.0"
+ inherits "^2.0.3"
+ readable-stream "^3.1.1"
+
+tslib@^1.8.0, tslib@^1.8.1:
+ version "1.10.0"
+ resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
+ integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==
+
+tslint@^5.12.1:
+ version "5.20.1"
+ resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.20.1.tgz#e401e8aeda0152bc44dd07e614034f3f80c67b7d"
+ integrity sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg==
+ dependencies:
+ "@babel/code-frame" "^7.0.0"
+ builtin-modules "^1.1.1"
+ chalk "^2.3.0"
+ commander "^2.12.1"
+ diff "^4.0.1"
+ glob "^7.1.1"
+ js-yaml "^3.13.1"
+ minimatch "^3.0.4"
+ mkdirp "^0.5.1"
+ resolve "^1.3.2"
+ semver "^5.3.0"
+ tslib "^1.8.0"
+ tsutils "^2.29.0"
+
+tsutils@^2.29.0:
+ version "2.29.0"
+ resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99"
+ integrity sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==
+ dependencies:
+ tslib "^1.8.1"
+
+tunnel-agent@^0.6.0:
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
+ integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=
+ dependencies:
+ safe-buffer "^5.0.1"
+
+typescript@^3.7.4:
+ version "3.7.4"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.4.tgz#1743a5ec5fef6a1fa9f3e4708e33c81c73876c19"
+ integrity sha512-A25xv5XCtarLwXpcDNZzCGvW2D1S3/bACratYBx2sax8PefsFhlYmkQicKHvpYflFS8if4zne5zT5kpJ7pzuvw==
+
+util-deprecate@^1.0.1, util-deprecate@~1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
+ integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
+
+which-pm-runs@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb"
+ integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=
+
+wide-align@^1.1.0:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457"
+ integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==
+ dependencies:
+ string-width "^1.0.2 || 2"
+
+wrappy@1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
+ integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts
index ee7fab8ba62..4db9464573e 100644
--- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts
+++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts
@@ -50,20 +50,18 @@ import { IFileService } from 'vs/platform/files/common/files';
import { DiskFileSystemProvider } from 'vs/platform/files/electron-browser/diskFileSystemProvider';
import { Schemas } from 'vs/base/common/network';
import { IProductService } from 'vs/platform/product/common/productService';
-import { IUserDataSyncService, IUserDataSyncStoreService, registerConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService, ISettingsSyncService } from 'vs/platform/userDataSync/common/userDataSync';
+import { IUserDataSyncService, IUserDataSyncStoreService, registerConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService, ISettingsSyncService, IUserDataAuthTokenService } from 'vs/platform/userDataSync/common/userDataSync';
import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService';
import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService';
-import { UserDataSyncChannel, UserDataSyncUtilServiceClient, SettingsSyncChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc';
+import { UserDataSyncChannel, UserDataSyncUtilServiceClient, SettingsSyncChannel, UserDataAuthTokenServiceChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc';
import { IElectronService } from 'vs/platform/electron/node/electron';
import { LoggerService } from 'vs/platform/log/node/loggerService';
import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog';
-import { IAuthTokenService } from 'vs/platform/auth/common/auth';
-import { AuthTokenService } from 'vs/platform/auth/electron-browser/authTokenService';
-import { AuthTokenChannel } from 'vs/platform/auth/common/authTokenIpc';
import { ICredentialsService } from 'vs/platform/credentials/common/credentials';
import { KeytarCredentialsService } from 'vs/platform/credentials/node/credentialsService';
import { UserDataAutoSync } from 'vs/platform/userDataSync/electron-browser/userDataAutoSync';
import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync';
+import { UserDataAuthTokenService } from 'vs/platform/userDataSync/common/userDataAuthTokenService';
export interface ISharedProcessConfiguration {
readonly machineId: string;
@@ -183,7 +181,7 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat
services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService));
services.set(ICredentialsService, new SyncDescriptor(KeytarCredentialsService));
- services.set(IAuthTokenService, new SyncDescriptor(AuthTokenService));
+ services.set(IUserDataAuthTokenService, new SyncDescriptor(UserDataAuthTokenService));
services.set(IUserDataSyncLogService, new SyncDescriptor(UserDataSyncLogService));
services.set(IUserDataSyncUtilService, new UserDataSyncUtilServiceClient(server.getChannel('userDataSyncUtil', activeWindowRouter)));
services.set(IUserDataSyncStoreService, new SyncDescriptor(UserDataSyncStoreService));
@@ -207,8 +205,8 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat
const diagnosticsChannel = new DiagnosticsChannel(diagnosticsService);
server.registerChannel('diagnostics', diagnosticsChannel);
- const authTokenService = accessor.get(IAuthTokenService);
- const authTokenChannel = new AuthTokenChannel(authTokenService);
+ const authTokenService = accessor.get(IUserDataAuthTokenService);
+ const authTokenChannel = new UserDataAuthTokenServiceChannel(authTokenService);
server.registerChannel('authToken', authTokenChannel);
const settingsSyncService = accessor.get(ISettingsSyncService);
diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts
index 66f65392bd6..52c783c9117 100644
--- a/src/vs/editor/common/modes.ts
+++ b/src/vs/editor/common/modes.ts
@@ -1280,6 +1280,25 @@ export interface RenameProvider {
resolveRenameLocation?(model: model.ITextModel, position: Position, token: CancellationToken): ProviderResult;
}
+/**
+ * @internal
+ */
+export interface Account {
+ 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;
diff --git a/src/vs/platform/auth/common/auth.ts b/src/vs/platform/auth/common/auth.ts
deleted file mode 100644
index 81af0bbf0a4..00000000000
--- a/src/vs/platform/auth/common/auth.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
-import { Event, Emitter } from 'vs/base/common/event';
-import { URI } from 'vs/base/common/uri';
-
-export const enum AuthTokenStatus {
- Initializing = 'Initializing',
- SignedOut = 'SignedOut',
- SignedIn = 'SignedIn',
- SigningIn = 'SigningIn',
- RefreshingToken = 'RefreshingToken'
-}
-
-export const IAuthTokenService = createDecorator('IAuthTokenService');
-
-export interface IAuthTokenService {
- _serviceBrand: undefined;
-
- readonly status: AuthTokenStatus;
- readonly onDidChangeStatus: Event;
- readonly _onDidGetCallback: Emitter;
-
- getToken(): Promise;
- refreshToken(): Promise;
- login(): Promise;
- logout(): Promise;
-}
diff --git a/src/vs/platform/auth/common/authTokenIpc.ts b/src/vs/platform/auth/common/authTokenIpc.ts
deleted file mode 100644
index eff088c1114..00000000000
--- a/src/vs/platform/auth/common/authTokenIpc.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-import { IServerChannel } from 'vs/base/parts/ipc/common/ipc';
-import { Event } from 'vs/base/common/event';
-import { IAuthTokenService } from 'vs/platform/auth/common/auth';
-
-export class AuthTokenChannel implements IServerChannel {
-
- constructor(private readonly service: IAuthTokenService) { }
-
- listen(_: unknown, event: string): Event {
- switch (event) {
- case 'onDidChangeStatus': return this.service.onDidChangeStatus;
- }
- throw new Error(`Event not found: ${event}`);
- }
-
- call(context: any, command: string, args?: any): Promise {
- switch (command) {
- case '_getInitialStatus': return Promise.resolve(this.service.status);
- case 'getToken': return this.service.getToken();
- case 'refreshToken': return this.service.refreshToken();
- case 'login': return this.service.login();
- case 'logout': return this.service.logout();
- }
- throw new Error('Invalid call');
- }
-}
diff --git a/src/vs/platform/auth/electron-browser/authTokenService.ts b/src/vs/platform/auth/electron-browser/authTokenService.ts
deleted file mode 100644
index 971e8f7a967..00000000000
--- a/src/vs/platform/auth/electron-browser/authTokenService.ts
+++ /dev/null
@@ -1,276 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-import * as crypto from 'crypto';
-import * as https from 'https';
-import { Event, Emitter } from 'vs/base/common/event';
-import { IAuthTokenService, AuthTokenStatus } from 'vs/platform/auth/common/auth';
-import { ICredentialsService } from 'vs/platform/credentials/common/credentials';
-import { Disposable } from 'vs/base/common/lifecycle';
-import { URI } from 'vs/base/common/uri';
-import { generateUuid } from 'vs/base/common/uuid';
-import { shell } from 'electron';
-import { createServer, startServer } from 'vs/platform/auth/electron-browser/authServer';
-import { IProductService } from 'vs/platform/product/common/productService';
-
-const SERVICE_NAME = 'VS Code';
-const ACCOUNT = 'MyAccount';
-
-const activeDirectoryResourceId = 'https://management.core.windows.net/';
-
-function toQuery(obj: any): string {
- return Object.keys(obj).map(key => `${key}=${obj[key]}`).join('&');
-}
-
-function toBase64UrlEncoding(base64string: string) {
- return base64string.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_'); // Need to use base64url encoding
-}
-
-export interface IToken {
- expiresIn: string; // How long access token is valid, in seconds
- expiresOn: string; // When the access token expires in epoch time
- accessToken: string;
- refreshToken: string;
-}
-
-export class AuthTokenService extends Disposable implements IAuthTokenService {
- _serviceBrand: undefined;
-
- private _status: AuthTokenStatus = AuthTokenStatus.Initializing;
- get status(): AuthTokenStatus { return this._status; }
- private _onDidChangeStatus: Emitter = this._register(new Emitter());
- readonly onDidChangeStatus: Event = this._onDidChangeStatus.event;
-
- public readonly _onDidGetCallback: Emitter = this._register(new Emitter());
- readonly onDidGetCallback: Event = this._onDidGetCallback.event;
-
- private _activeToken: IToken | undefined;
-
- constructor(
- @ICredentialsService private readonly credentialsService: ICredentialsService,
- @IProductService private readonly productService: IProductService
- ) {
- super();
- if (!this.productService.auth) {
- return;
- }
-
- this.credentialsService.getPassword(SERVICE_NAME, ACCOUNT).then(storedRefreshToken => {
- if (storedRefreshToken) {
- this.refresh(storedRefreshToken);
- } else {
- this.setStatus(AuthTokenStatus.SignedOut);
- }
- });
- }
-
- public async login(): Promise {
- if (!this.productService.auth) {
- throw new Error('Authentication is not configured.');
- }
-
- this.setStatus(AuthTokenStatus.SigningIn);
-
- const nonce = generateUuid();
- const { server, redirectPromise, codePromise } = createServer(nonce);
-
- try {
- const port = await startServer(server);
- shell.openExternal(`http://localhost:${port}/signin?nonce=${encodeURIComponent(nonce)}`);
-
- const redirectReq = await redirectPromise;
- if ('err' in redirectReq) {
- const { err, res } = redirectReq;
- res.writeHead(302, { Location: `/?error=${encodeURIComponent(err && err.message || 'Unkown error')}` });
- res.end();
- throw err;
- }
-
- const host = redirectReq.req.headers.host || '';
- const updatedPortStr = (/^[^:]+:(\d+)$/.exec(Array.isArray(host) ? host[0] : host) || [])[1];
- const updatedPort = updatedPortStr ? parseInt(updatedPortStr, 10) : port;
-
- const state = `${updatedPort},${encodeURIComponent(nonce)}`;
-
- const codeVerifier = toBase64UrlEncoding(crypto.randomBytes(32).toString('base64'));
- const codeChallenge = toBase64UrlEncoding(crypto.createHash('sha256').update(codeVerifier).digest('base64'));
-
- let uri = URI.parse(this.productService.auth.loginUrl);
- uri = uri.with({
- query: `response_type=code&client_id=${encodeURIComponent(this.productService.auth.clientId)}&redirect_uri=${this.productService.auth.redirectUrl}&state=${encodeURIComponent(state)}&resource=${activeDirectoryResourceId}&prompt=select_account&code_challenge_method=S256&code_challenge=${codeChallenge}`
- });
-
- await redirectReq.res.writeHead(302, { Location: uri.toString(true) });
- redirectReq.res.end();
-
- const codeRes = await codePromise;
- const res = codeRes.res;
-
- try {
- if ('err' in codeRes) {
- throw codeRes.err;
- }
- const token = await this.exchangeCodeForToken(codeRes.code, codeVerifier);
- this.setToken(token);
- res.writeHead(302, { Location: '/' });
- res.end();
- } catch (err) {
- res.writeHead(302, { Location: `/?error=${encodeURIComponent(err && err.message || 'Unkown error')}` });
- res.end();
- }
- } finally {
- setTimeout(() => {
- server.close();
- }, 5000);
- }
-
- }
-
- public getToken(): Promise {
- return Promise.resolve(this._activeToken?.accessToken);
- }
-
- public async refreshToken(): Promise {
- if (!this._activeToken) {
- throw new Error('No token to refresh');
- }
-
- this.refresh(this._activeToken.refreshToken);
- }
-
- private setToken(token: IToken) {
- this._activeToken = token;
- this.credentialsService.setPassword(SERVICE_NAME, ACCOUNT, token.refreshToken);
- this.setStatus(AuthTokenStatus.SignedIn);
- }
-
- private exchangeCodeForToken(code: string, codeVerifier: string): Promise {
- return new Promise((resolve: (value: IToken) => void, reject) => {
- try {
- if (!this.productService.auth) {
- throw new Error('Authentication is not configured.');
- }
-
- const postData = toQuery({
- grant_type: 'authorization_code',
- code: code,
- client_id: this.productService.auth?.clientId,
- code_verifier: codeVerifier,
- redirect_uri: this.productService.auth?.redirectUrl
- });
-
- const tokenUrl = URI.parse(this.productService.auth.tokenUrl);
-
- const post = https.request({
- host: tokenUrl.authority,
- path: tokenUrl.path,
- method: 'POST',
- headers: {
- 'Content-Type': 'application/x-www-form-urlencoded',
- 'Content-Length': postData.length
- }
- }, result => {
- const buffer: Buffer[] = [];
- result.on('data', (chunk: Buffer) => {
- buffer.push(chunk);
- });
- result.on('end', () => {
- if (result.statusCode === 200) {
- const json = JSON.parse(Buffer.concat(buffer).toString());
- resolve({
- expiresIn: json.access_token,
- expiresOn: json.expires_on,
- accessToken: json.access_token,
- refreshToken: json.refresh_token
- });
- } else {
- reject(new Error('Bad!'));
- }
- });
- });
-
- post.write(postData);
-
- post.end();
- post.on('error', err => {
- reject(err);
- });
-
- } catch (e) {
- reject(e);
- }
- });
- }
-
- private async refresh(refreshToken: string): Promise {
- return new Promise((resolve, reject) => {
- if (!this.productService.auth) {
- throw new Error('Authentication is not configured.');
- }
-
- this.setStatus(AuthTokenStatus.RefreshingToken);
- const postData = toQuery({
- refresh_token: refreshToken,
- client_id: this.productService.auth?.clientId,
- grant_type: 'refresh_token',
- resource: activeDirectoryResourceId
- });
-
- const tokenUrl = URI.parse(this.productService.auth.tokenUrl);
-
- const post = https.request({
- host: tokenUrl.authority,
- path: tokenUrl.path,
- method: 'POST',
- headers: {
- 'Content-Type': 'application/x-www-form-urlencoded',
- 'Content-Length': postData.length
- }
- }, result => {
- const buffer: Buffer[] = [];
- result.on('data', (chunk: Buffer) => {
- buffer.push(chunk);
- });
- result.on('end', () => {
- if (result.statusCode === 200) {
- const json = JSON.parse(Buffer.concat(buffer).toString());
- this.setToken({
- expiresIn: json.access_token,
- expiresOn: json.expires_on,
- accessToken: json.access_token,
- refreshToken: json.refresh_token
- });
- resolve();
- } else {
- reject(new Error('Refreshing token failed.'));
- }
- });
- });
-
- post.write(postData);
-
- post.end();
- post.on('error', err => {
- this.setStatus(AuthTokenStatus.SignedOut);
- reject(err);
- });
- });
- }
-
- async logout(): Promise {
- await this.credentialsService.deletePassword(SERVICE_NAME, ACCOUNT);
- this._activeToken = undefined;
- this.setStatus(AuthTokenStatus.SignedOut);
- }
-
- private setStatus(status: AuthTokenStatus): void {
- if (this._status !== status) {
- this._status = status;
- this._onDidChangeStatus.fire(status);
- }
- }
-
-}
-
diff --git a/src/vs/platform/product/common/productService.ts b/src/vs/platform/product/common/productService.ts
index 1c37427fd5d..120fd666444 100644
--- a/src/vs/platform/product/common/productService.ts
+++ b/src/vs/platform/product/common/productService.ts
@@ -102,13 +102,6 @@ export interface IProductConfiguration {
readonly msftInternalDomains?: string[];
readonly linkProtectionTrustedDomains?: readonly string[];
-
- readonly auth?: {
- loginUrl: string;
- tokenUrl: string;
- redirectUrl: string;
- clientId: string;
- };
}
export interface IExeBasedExtensionTip {
diff --git a/src/vs/platform/userDataSync/common/userDataAuthTokenService.ts b/src/vs/platform/userDataSync/common/userDataAuthTokenService.ts
new file mode 100644
index 00000000000..03ba0c45dfe
--- /dev/null
+++ b/src/vs/platform/userDataSync/common/userDataAuthTokenService.ts
@@ -0,0 +1,33 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { Emitter, Event } from 'vs/base/common/event';
+import { Disposable } from 'vs/base/common/lifecycle';
+import { IUserDataAuthTokenService } from 'vs/platform/userDataSync/common/userDataSync';
+
+export class UserDataAuthTokenService extends Disposable implements IUserDataAuthTokenService {
+
+ _serviceBrand: any;
+
+ private _onDidChangeToken: Emitter = this._register(new Emitter());
+ readonly onDidChangeToken: Event = this._onDidChangeToken.event;
+
+ private _token: string | undefined;
+
+ constructor() {
+ super();
+ }
+
+ async getToken(): Promise {
+ return this._token;
+ }
+
+ async setToken(token: string | undefined): Promise {
+ if (token !== this._token) {
+ this._token = token;
+ this._onDidChangeToken.fire(token);
+ }
+ }
+}
diff --git a/src/vs/platform/userDataSync/common/userDataAutoSync.ts b/src/vs/platform/userDataSync/common/userDataAutoSync.ts
index 75123459141..e6a3d612dee 100644
--- a/src/vs/platform/userDataSync/common/userDataAutoSync.ts
+++ b/src/vs/platform/userDataSync/common/userDataAutoSync.ts
@@ -3,12 +3,11 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { IUserDataSyncService, SyncStatus, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync';
-import { Disposable } from 'vs/base/common/lifecycle';
-import { Event } from 'vs/base/common/event';
-import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { timeout } from 'vs/base/common/async';
-import { IAuthTokenService, AuthTokenStatus } from 'vs/platform/auth/common/auth';
+import { Event } from 'vs/base/common/event';
+import { Disposable } from 'vs/base/common/lifecycle';
+import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
+import { IUserDataSyncLogService, IUserDataSyncService, SyncStatus, IUserDataAuthTokenService } from 'vs/platform/userDataSync/common/userDataSync';
export class UserDataAutoSync extends Disposable {
@@ -18,16 +17,17 @@ export class UserDataAutoSync extends Disposable {
@IConfigurationService private readonly configurationService: IConfigurationService,
@IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService,
@IUserDataSyncLogService private readonly logService: IUserDataSyncLogService,
- @IAuthTokenService private readonly authTokenService: IAuthTokenService,
+ @IUserDataAuthTokenService private readonly userDataAuthTokenService: IUserDataAuthTokenService,
) {
super();
this.updateEnablement(false);
- this._register(Event.any(authTokenService.onDidChangeStatus, userDataSyncService.onDidChangeStatus)(() => this.updateEnablement(true)));
+ this._register(Event.any(userDataAuthTokenService.onDidChangeToken)(() => this.updateEnablement(true)));
+ this._register(Event.any(userDataSyncService.onDidChangeStatus)(() => this.updateEnablement(true)));
this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('sync.enable'))(() => this.updateEnablement(true)));
}
- private updateEnablement(stopIfDisabled: boolean): void {
- const enabled = this.isSyncEnabled();
+ private async updateEnablement(stopIfDisabled: boolean): Promise {
+ const enabled = await this.isSyncEnabled();
if (this.enabled === enabled) {
return;
}
@@ -60,10 +60,10 @@ export class UserDataAutoSync extends Disposable {
}
}
- private isSyncEnabled(): boolean {
+ private async isSyncEnabled(): Promise {
return this.configurationService.getValue('sync.enable')
&& this.userDataSyncService.status !== SyncStatus.Uninitialized
- && this.authTokenService.status === AuthTokenStatus.SignedIn;
+ && !!(await this.userDataAuthTokenService.getToken());
}
}
diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts
index eacd383b650..71c11fdd645 100644
--- a/src/vs/platform/userDataSync/common/userDataSync.ts
+++ b/src/vs/platform/userDataSync/common/userDataSync.ts
@@ -206,6 +206,17 @@ export interface IUserDataSyncUtilService {
resolveFormattingOptions(resource: URI): Promise;
}
+export const IUserDataAuthTokenService = createDecorator('IUserDataAuthTokenService');
+
+export interface IUserDataAuthTokenService {
+ _serviceBrand: undefined;
+
+ readonly onDidChangeToken: Event;
+
+ getToken(): Promise;
+ setToken(accessToken: string | undefined): Promise;
+}
+
export const IUserDataSyncLogService = createDecorator('IUserDataSyncLogService');
export interface IUserDataSyncLogService extends ILogService { }
diff --git a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts
index 61f634471ba..8e647cfef99 100644
--- a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts
+++ b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts
@@ -5,7 +5,7 @@
import { IServerChannel, IChannel } from 'vs/base/parts/ipc/common/ipc';
import { Event } from 'vs/base/common/event';
-import { IUserDataSyncService, IUserDataSyncUtilService, ISettingsSyncService } from 'vs/platform/userDataSync/common/userDataSync';
+import { IUserDataSyncService, IUserDataSyncUtilService, ISettingsSyncService, IUserDataAuthTokenService } from 'vs/platform/userDataSync/common/userDataSync';
import { URI } from 'vs/base/common/uri';
import { IStringDictionary } from 'vs/base/common/collections';
import { FormattingOptions } from 'vs/base/common/jsonFormatter';
@@ -67,6 +67,25 @@ export class SettingsSyncChannel implements IServerChannel {
}
}
+export class UserDataAuthTokenServiceChannel implements IServerChannel {
+ constructor(private readonly service: IUserDataAuthTokenService) { }
+
+ listen(_: unknown, event: string): Event {
+ switch (event) {
+ case 'onDidChangeToken': return this.service.onDidChangeToken;
+ }
+ throw new Error(`Event not found: ${event}`);
+ }
+
+ call(context: any, command: string, args?: any): Promise {
+ switch (command) {
+ case 'setToken': return this.service.setToken(args);
+ case 'getToken': return this.service.getToken();
+ }
+ throw new Error('Invalid call');
+ }
+}
+
export class UserDataSycnUtilServiceChannel implements IServerChannel {
constructor(private readonly service: IUserDataSyncUtilService) { }
diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts
index 9c4aaf3cb0c..cf1b0d10aae 100644
--- a/src/vs/platform/userDataSync/common/userDataSyncService.ts
+++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts
@@ -3,14 +3,13 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { IUserDataSyncService, SyncStatus, ISynchroniser, IUserDataSyncStoreService, SyncSource, ISettingsSyncService, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync';
+import { IUserDataSyncService, SyncStatus, ISynchroniser, IUserDataSyncStoreService, SyncSource, ISettingsSyncService, IUserDataSyncLogService, IUserDataAuthTokenService } from 'vs/platform/userDataSync/common/userDataSync';
import { Disposable } from 'vs/base/common/lifecycle';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync';
import { Emitter, Event } from 'vs/base/common/event';
import { ExtensionsSynchroniser } from 'vs/platform/userDataSync/common/extensionsSync';
import { IExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
-import { IAuthTokenService, AuthTokenStatus } from 'vs/platform/auth/common/auth';
import { KeybindingsSynchroniser } from 'vs/platform/userDataSync/common/keybindingsSync';
import { GlobalStateSynchroniser } from 'vs/platform/userDataSync/common/globalStateSync';
import { toErrorMessage } from 'vs/base/common/errorMessage';
@@ -38,9 +37,9 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
constructor(
@IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
- @IAuthTokenService private readonly authTokenService: IAuthTokenService,
@ISettingsSyncService private readonly settingsSynchroniser: ISettingsSyncService,
@IUserDataSyncLogService private readonly logService: IUserDataSyncLogService,
+ @IUserDataAuthTokenService private readonly userDataAuthTokenService: IUserDataAuthTokenService,
) {
super();
this.keybindingsSynchroniser = this._register(this.instantiationService.createInstance(KeybindingsSynchroniser));
@@ -51,6 +50,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
if (this.userDataSyncStoreService.userDataSyncStore) {
this._register(Event.any(...this.synchronisers.map(s => Event.map(s.onDidChangeStatus, () => undefined)))(() => this.updateStatus()));
+ this._register(this.userDataAuthTokenService.onDidChangeToken(e => this.onDidChangeAuthTokenStatus(e)));
}
this.onDidChangeLocal = Event.any(...this.synchronisers.map(s => s.onDidChangeLocal));
@@ -60,7 +60,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
if (!this.userDataSyncStoreService.userDataSyncStore) {
throw new Error('Not enabled');
}
- if (this.authTokenService.status === AuthTokenStatus.SignedOut) {
+ if (!(await this.userDataAuthTokenService.getToken())) {
throw new Error('Not Authenticated. Please sign in to start sync.');
}
for (const synchroniser of this.synchronisers) {
@@ -76,7 +76,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
if (!this.userDataSyncStoreService.userDataSyncStore) {
throw new Error('Not enabled');
}
- if (this.authTokenService.status === AuthTokenStatus.SignedOut) {
+ if (!(await this.userDataAuthTokenService.getToken())) {
throw new Error('Not Authenticated. Please sign in to start sync.');
}
for (const synchroniser of this.synchronisers) {
@@ -92,7 +92,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
if (!this.userDataSyncStoreService.userDataSyncStore) {
throw new Error('Not enabled');
}
- if (this.authTokenService.status === AuthTokenStatus.SignedOut) {
+ if (!(await this.userDataAuthTokenService.getToken())) {
throw new Error('Not Authenticated. Please sign in to start sync.');
}
for (const synchroniser of this.synchronisers) {
@@ -120,7 +120,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
if (!this.userDataSyncStoreService.userDataSyncStore) {
throw new Error('Not enabled');
}
- if (this.authTokenService.status === AuthTokenStatus.SignedOut) {
+ if (!!(await this.userDataAuthTokenService.getToken())) {
throw new Error('Not Authenticated. Please sign in to start sync.');
}
for (const synchroniser of this.synchronisers) {
@@ -135,7 +135,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
if (!this.userDataSyncStoreService.userDataSyncStore) {
throw new Error('Not enabled');
}
- if (this.authTokenService.status === AuthTokenStatus.SignedOut) {
+ if (!!(await this.userDataAuthTokenService.getToken())) {
throw new Error('Not Authenticated. Please sign in to start sync.');
}
for (const synchroniser of this.synchronisers) {
@@ -192,4 +192,10 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
}
return SyncSource.UIState;
}
+
+ private onDidChangeAuthTokenStatus(token: string | undefined): void {
+ if (!token) {
+ this.stop();
+ }
+ }
}
diff --git a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts
index bc499a7c48a..a77449c0577 100644
--- a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts
+++ b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts
@@ -4,13 +4,12 @@
*--------------------------------------------------------------------------------------------*/
import { Disposable, } from 'vs/base/common/lifecycle';
-import { IUserData, IUserDataSyncStoreService, UserDataSyncStoreErrorCode, UserDataSyncStoreError, IUserDataSyncStore, getUserDataSyncStore } from 'vs/platform/userDataSync/common/userDataSync';
+import { IUserData, IUserDataSyncStoreService, UserDataSyncStoreErrorCode, UserDataSyncStoreError, IUserDataSyncStore, getUserDataSyncStore, IUserDataAuthTokenService } from 'vs/platform/userDataSync/common/userDataSync';
import { IRequestService, asText, isSuccess } from 'vs/platform/request/common/request';
import { URI } from 'vs/base/common/uri';
import { joinPath } from 'vs/base/common/resources';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IHeaders, IRequestOptions, IRequestContext } from 'vs/base/parts/request/common/request';
-import { IAuthTokenService } from 'vs/platform/auth/common/auth';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
export class UserDataSyncStoreService extends Disposable implements IUserDataSyncStoreService {
@@ -22,7 +21,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
constructor(
@IConfigurationService configurationService: IConfigurationService,
@IRequestService private readonly requestService: IRequestService,
- @IAuthTokenService private readonly authTokenService: IAuthTokenService,
+ @IUserDataAuthTokenService private readonly authTokenService: IUserDataAuthTokenService,
) {
super();
this.userDataSyncStore = getUserDataSyncStore(configurationService);
@@ -98,7 +97,6 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
const context = await this.requestService.request(options, token);
if (context.res.statusCode === 401) {
- this.authTokenService.refreshToken();
// Throw Unauthorized Error
throw new UserDataSyncStoreError('Unauthorized', UserDataSyncStoreErrorCode.Unauthroized);
}
diff --git a/src/vs/platform/userDataSync/electron-browser/userDataAutoSync.ts b/src/vs/platform/userDataSync/electron-browser/userDataAutoSync.ts
index 90ec9533ecd..821d8a05ed5 100644
--- a/src/vs/platform/userDataSync/electron-browser/userDataAutoSync.ts
+++ b/src/vs/platform/userDataSync/electron-browser/userDataAutoSync.ts
@@ -3,12 +3,11 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { IUserDataSyncService, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync';
+import { IUserDataSyncService, IUserDataSyncLogService, IUserDataAuthTokenService } from 'vs/platform/userDataSync/common/userDataSync';
import { Event } from 'vs/base/common/event';
import { IElectronService } from 'vs/platform/electron/node/electron';
import { UserDataAutoSync as BaseUserDataAutoSync } from 'vs/platform/userDataSync/common/userDataAutoSync';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
-import { IAuthTokenService } from 'vs/platform/auth/common/auth';
export class UserDataAutoSync extends BaseUserDataAutoSync {
@@ -17,7 +16,7 @@ export class UserDataAutoSync extends BaseUserDataAutoSync {
@IElectronService electronService: IElectronService,
@IConfigurationService configurationService: IConfigurationService,
@IUserDataSyncLogService logService: IUserDataSyncLogService,
- @IAuthTokenService authTokenService: IAuthTokenService,
+ @IUserDataAuthTokenService authTokenService: IUserDataAuthTokenService,
) {
super(configurationService, userDataSyncService, logService, authTokenService);
diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts
index 27a17629b13..be480f96639 100644
--- a/src/vs/vscode.proposed.d.ts
+++ b/src/vs/vscode.proposed.d.ts
@@ -16,7 +16,28 @@
declare module 'vscode' {
- //#region Alex - resolvers, AlexR - ports
+ export interface Account {
+ readonly id: string;
+ readonly accessToken: string;
+ readonly displayName: string;
+ }
+
+ export interface AuthenticationProvider {
+ readonly id: string;
+ readonly displayName: string;
+
+ readonly accounts: ReadonlyArray;
+ readonly onDidChangeAccounts: Event>;
+
+ login(): Promise;
+ logout(accountId: string): Promise;
+ }
+
+ export namespace authentication {
+ export function registerAuthenticationProvider(provider: AuthenticationProvider): Disposable;
+ }
+
+ //#region Alex - resolvers
export interface RemoteAuthorityResolverContext {
resolveAttempt: number;
diff --git a/src/vs/workbench/api/browser/extensionHost.contribution.ts b/src/vs/workbench/api/browser/extensionHost.contribution.ts
index 5560678f607..d77e37c35d8 100644
--- a/src/vs/workbench/api/browser/extensionHost.contribution.ts
+++ b/src/vs/workbench/api/browser/extensionHost.contribution.ts
@@ -59,6 +59,7 @@ import './mainThreadComments';
import './mainThreadTask';
import './mainThreadLabelService';
import './mainThreadTunnelService';
+import './mainThreadAuthentication';
import 'vs/workbench/api/common/apiCommands';
export class ExtensionPoints implements IWorkbenchContribution {
diff --git a/src/vs/workbench/api/browser/mainThreadAuthentication.ts b/src/vs/workbench/api/browser/mainThreadAuthentication.ts
new file mode 100644
index 00000000000..c9fbc6e3e50
--- /dev/null
+++ b/src/vs/workbench/api/browser/mainThreadAuthentication.ts
@@ -0,0 +1,69 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { Disposable } from 'vs/base/common/lifecycle';
+import * as modes from 'vs/editor/common/modes';
+import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
+import { IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService';
+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;
+ }
+
+ accounts(): Promise> {
+ return this._proxy.$accounts(this.handle);
+ }
+
+ login(): Promise {
+ return this._proxy.$login(this.handle);
+ }
+
+ logout(accountId: string): Promise {
+ return this._proxy.$logout(this.handle, accountId);
+ }
+}
+
+@extHostNamedCustomer(MainContext.MainThreadAuthentication)
+export class MainThreadAuthentication extends Disposable implements MainThreadAuthenticationShape {
+ private readonly _proxy: ExtHostAuthenticationShape;
+ private _handlers = new Map();
+
+ constructor(
+ extHostContext: IExtHostContext,
+ @IAuthenticationService private readonly authenticationService: IAuthenticationService,
+ ) {
+ super();
+ 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);
+ 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}`);
+ }
+
+ this.authenticationService.unregisterAuthenticationProvider(id);
+ }
+
+ $onDidChangeAccounts(handle: number, accounts: ReadonlyArray) {
+ const id = this._handlers.get(handle);
+ if (id) {
+ this.authenticationService.accountsUpdate(id, accounts);
+ }
+ }
+}
diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts
index d38277ed35b..0438ad43777 100644
--- a/src/vs/workbench/api/common/extHost.api.impl.ts
+++ b/src/vs/workbench/api/common/extHost.api.impl.ts
@@ -69,6 +69,7 @@ import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
import { ExtHostTheming } from 'vs/workbench/api/common/extHostTheming';
import { IExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService';
+import { ExtHostAuthentication } from 'vs/workbench/api/common/extHostAuthentication';
export interface IExtensionApiFactory {
(extension: IExtensionDescription, registry: ExtensionDescriptionRegistry, configProvider: ExtHostConfigProvider): typeof vscode;
@@ -128,6 +129,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
const extHostProgress = rpcProtocol.set(ExtHostContext.ExtHostProgress, new ExtHostProgress(rpcProtocol.getProxy(MainContext.MainThreadProgress)));
const extHostLabelService = rpcProtocol.set(ExtHostContext.ExtHostLabelService, new ExtHostLabelService(rpcProtocol));
const extHostTheming = rpcProtocol.set(ExtHostContext.ExtHostTheming, new ExtHostTheming(rpcProtocol));
+ const extHostAuthentication = rpcProtocol.set(ExtHostContext.ExtHostAuthentication, new ExtHostAuthentication(rpcProtocol));
// Check that no named customers are missing
const expected: ProxyIdentifier[] = values(ExtHostContext);
@@ -175,6 +177,11 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
};
})();
+ const authentication: typeof vscode.authentication = {
+ registerAuthenticationProvider(provider: vscode.AuthenticationProvider): vscode.Disposable {
+ return extHostAuthentication.registerAuthenticationProvider(provider);
+ }
+ };
// namespace: commands
const commands: typeof vscode.commands = {
@@ -830,6 +837,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
return {
version: initData.version,
// namespaces
+ authentication,
commands,
debug,
env,
diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts
index 227a885f876..12081788f04 100644
--- a/src/vs/workbench/api/common/extHost.protocol.ts
+++ b/src/vs/workbench/api/common/extHost.protocol.ts
@@ -147,6 +147,12 @@ export interface MainThreadCommentsShape extends IDisposable {
$onDidCommentThreadsChange(handle: number, event: modes.CommentThreadChangedEvent): void;
}
+export interface MainThreadAuthenticationShape extends IDisposable {
+ $registerAuthenticationProvider(handle: number, id: string): void;
+ $unregisterAuthenticationProvider(handle: number): void;
+ $onDidChangeAccounts(handle: number, accounts: ReadonlyArray): void;
+}
+
export interface MainThreadConfigurationShape extends IDisposable {
$updateConfigurationOption(target: ConfigurationTarget | null, key: string, value: any, overrides: IConfigurationOverrides | undefined, scopeToLanguage: boolean | undefined): Promise;
$removeConfigurationOption(target: ConfigurationTarget | null, key: string, overrides: IConfigurationOverrides | undefined, scopeToLanguage: boolean | undefined): Promise;
@@ -891,6 +897,13 @@ export interface ExtHostLabelServiceShape {
$registerResourceLabelFormatter(formatter: ResourceLabelFormatter): IDisposable;
}
+export interface ExtHostAuthenticationShape {
+ $accounts(handle: number): Promise>;
+ $login(handle: number): Promise;
+ $logout(handle: number, accountId: string): Promise;
+ // TODO rmacfarlane
+}
+
export interface ExtHostSearchShape {
$provideFileSearchResults(handle: number, session: number, query: search.IRawQuery, token: CancellationToken): Promise;
$provideTextSearchResults(handle: number, session: number, query: search.IRawTextQuery, token: CancellationToken): Promise;
@@ -1412,6 +1425,7 @@ export interface ExtHostTunnelServiceShape {
// --- proxy identifiers
export const MainContext = {
+ MainThreadAuthentication: createMainId('MainThreadAuthentication'),
MainThreadClipboard: createMainId('MainThreadClipboard'),
MainThreadCommands: createMainId('MainThreadCommands'),
MainThreadComments: createMainId('MainThreadComments'),
@@ -1487,5 +1501,6 @@ export const ExtHostContext = {
ExtHostOutputService: createMainId('ExtHostOutputService'),
ExtHostLabelService: createMainId('ExtHostLabelService'),
ExtHostTheming: createMainId('ExtHostTheming'),
- ExtHostTunnelService: createMainId('ExtHostTunnelService')
+ ExtHostTunnelService: createMainId('ExtHostTunnelService'),
+ ExtHostAuthentication: createMainId('ExtHostAuthentication')
};
diff --git a/src/vs/workbench/api/common/extHostAuthentication.ts b/src/vs/workbench/api/common/extHostAuthentication.ts
new file mode 100644
index 00000000000..1ab42296c86
--- /dev/null
+++ b/src/vs/workbench/api/common/extHostAuthentication.ts
@@ -0,0 +1,83 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as vscode from 'vscode';
+import * as modes from 'vs/editor/common/modes';
+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';
+
+export class ExtHostAuthenticationProvider implements IDisposable {
+ constructor(private _provider: vscode.AuthenticationProvider,
+ private readonly _handle: number,
+ private _proxy: MainThreadAuthenticationShape) {
+ this._provider.onDidChangeAccounts(x => this._proxy.$onDidChangeAccounts(this._handle, this._provider.accounts));
+ }
+
+ get accounts(): ReadonlyArray {
+ return this._provider.accounts;
+ }
+
+ login(): Promise {
+ return this._provider.login();
+ }
+
+ logout(accountId: string): Promise {
+ return this._provider.logout(accountId);
+ }
+
+ dispose(): void {
+ this._proxy.$unregisterAuthenticationProvider(this._handle);
+ }
+}
+
+export class ExtHostAuthentication implements ExtHostAuthenticationShape {
+ public static _handlePool: number = 0;
+ private _proxy: MainThreadAuthenticationShape;
+ private _authenticationProviders: Map = new Map();
+
+ constructor(mainContext: IMainContext) {
+ this._proxy = mainContext.getProxy(MainContext.MainThreadAuthentication);
+ }
+
+ 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);
+
+ this._proxy.$registerAuthenticationProvider(handle, provider.id);
+ return authenticationProvider;
+ }
+
+ $accounts(handle: number): Promise> {
+ const authProvider = this._authenticationProviders.get(handle);
+ if (authProvider) {
+ return Promise.resolve(authProvider.accounts);
+ }
+
+ throw new Error(`Unable to find authentication provider with handle: ${handle}`);
+ }
+
+ $login(handle: number): Promise {
+ const authProvider = this._authenticationProviders.get(handle);
+ if (authProvider) {
+ return authProvider.login();
+ }
+
+ throw new Error(`Unable to find authentication provider with handle: ${handle}`);
+ }
+
+ $logout(handle: number, accountId: string): Promise {
+ const authProvider = this._authenticationProviders.get(handle);
+ if (authProvider) {
+ return authProvider.logout(accountId);
+ }
+
+ throw new Error(`Unable to find authentication provider with handle: ${handle}`);
+ }
+}
diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataAutoSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataAutoSync.ts
index d0d54c3c403..31ec8de3166 100644
--- a/src/vs/workbench/contrib/userDataSync/browser/userDataAutoSync.ts
+++ b/src/vs/workbench/contrib/userDataSync/browser/userDataAutoSync.ts
@@ -3,9 +3,8 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { IUserDataSyncService, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync';
+import { IUserDataSyncService, IUserDataSyncLogService, IUserDataAuthTokenService } from 'vs/platform/userDataSync/common/userDataSync';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
-import { IAuthTokenService } from 'vs/platform/auth/common/auth';
import { Event } from 'vs/base/common/event';
import { UserDataAutoSync as BaseUserDataAutoSync } from 'vs/platform/userDataSync/common/userDataAutoSync';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -18,7 +17,7 @@ export class UserDataAutoSync extends BaseUserDataAutoSync {
@IUserDataSyncService userDataSyncService: IUserDataSyncService,
@IConfigurationService configurationService: IConfigurationService,
@IUserDataSyncLogService logService: IUserDataSyncLogService,
- @IAuthTokenService authTokenService: IAuthTokenService,
+ @IUserDataAuthTokenService authTokenService: IUserDataAuthTokenService,
@IInstantiationService instantiationService: IInstantiationService,
@IHostService hostService: IHostService,
) {
diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts
index 222643aa7b4..5d87233cb66 100644
--- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts
+++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
-import { IUserDataSyncService, SyncStatus, SyncSource, CONTEXT_SYNC_STATE, IUserDataSyncStore, registerConfiguration, getUserDataSyncStore, ISyncConfiguration } from 'vs/platform/userDataSync/common/userDataSync';
+import { IUserDataSyncService, SyncStatus, SyncSource, CONTEXT_SYNC_STATE, IUserDataSyncStore, registerConfiguration, getUserDataSyncStore, ISyncConfiguration, IUserDataAuthTokenService } from 'vs/platform/userDataSync/common/userDataSync';
import { localize } from 'vs/nls';
import { Disposable, MutableDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
@@ -24,9 +24,7 @@ import { IHistoryService } from 'vs/workbench/services/history/common/history';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { isEqual } from 'vs/base/common/resources';
import { IEditorInput } from 'vs/workbench/common/editor';
-import { IAuthTokenService, AuthTokenStatus } from 'vs/platform/auth/common/auth';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
-import { FalseContext } from 'vs/platform/contextkey/common/contextkeys';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { isWeb } from 'vs/base/common/platform';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -35,25 +33,35 @@ 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';
-const CONTEXT_AUTH_TOKEN_STATE = new RawContextKey('authTokenStatus', AuthTokenStatus.Initializing);
+const enum MSAAuthStatus {
+ Initializing = 'Initializing',
+ SignedIn = 'SignedIn',
+ SignedOut = 'SignedOut'
+}
+const CONTEXT_AUTH_TOKEN_STATE = new RawContextKey('authTokenStatus', MSAAuthStatus.Initializing);
const SYNC_PUSH_LIGHT_ICON_URI = URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/userDataSync/browser/media/check-light.svg`));
const SYNC_PUSH_DARK_ICON_URI = URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/userDataSync/browser/media/check-dark.svg`));
+const MSA = 'MSA';
+
export class UserDataSyncWorkbenchContribution extends Disposable implements IWorkbenchContribution {
private static readonly ENABLEMENT_SETTING = 'sync.enable';
private readonly userDataSyncStore: IUserDataSyncStore | undefined;
private readonly syncStatusContext: IContextKey;
- private readonly authTokenContext: IContextKey;
+ private readonly authenticationState: IContextKey;
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;
constructor(
@IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService,
- @IAuthTokenService private readonly authTokenService: IAuthTokenService,
+ @IAuthenticationService private readonly authenticationService: IAuthenticationService,
@IContextKeyService contextKeyService: IContextKeyService,
@IActivityService private readonly activityService: IActivityService,
@INotificationService private readonly notificationService: INotificationService,
@@ -66,45 +74,111 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
@IQuickInputService private readonly quickInputService: IQuickInputService,
@IInstantiationService instantiationService: IInstantiationService,
@IOutputService private readonly outputService: IOutputService,
+ @IUserDataAuthTokenService private readonly userDataAuthTokenService: IUserDataAuthTokenService,
) {
super();
this.userDataSyncStore = getUserDataSyncStore(configurationService);
this.syncStatusContext = CONTEXT_SYNC_STATE.bindTo(contextKeyService);
- this.authTokenContext = CONTEXT_AUTH_TOKEN_STATE.bindTo(contextKeyService);
-
+ this.authenticationState = CONTEXT_AUTH_TOKEN_STATE.bindTo(contextKeyService);
if (this.userDataSyncStore) {
registerConfiguration();
- this.onDidChangeAuthTokenStatus(this.authTokenService.status);
this.onDidChangeSyncStatus(this.userDataSyncService.status);
- this._register(Event.debounce(authTokenService.onDidChangeStatus, () => undefined, 500)(() => this.onDidChangeAuthTokenStatus(this.authTokenService.status)));
this._register(Event.debounce(userDataSyncService.onDidChangeStatus, () => undefined, 500)(() => this.onDidChangeSyncStatus(this.userDataSyncService.status)));
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.registerActions();
-
- if (isWeb) {
- this._register(instantiationService.createInstance(UserDataAutoSync));
- } else {
- this._register(instantiationService.createInstance(UserDataSyncTrigger).onDidTriggerSync(() => this.triggerSync()));
- }
+ this.initializeActiveAccount().then(_ => {
+ if (isWeb) {
+ this._register(instantiationService.createInstance(UserDataAutoSync));
+ } else {
+ this._register(instantiationService.createInstance(UserDataSyncTrigger).onDidTriggerSync(() => this.triggerSync()));
+ }
+ });
}
}
private triggerSync(): void {
if (this.configurationService.getValue('sync.enable')
&& this.userDataSyncService.status !== SyncStatus.Uninitialized
- && this.authTokenService.status === AuthTokenStatus.SignedIn) {
+ && this.authenticationState.get() === MSAAuthStatus.SignedIn) {
this.userDataSyncService.sync();
}
}
- private onDidChangeAuthTokenStatus(status: AuthTokenStatus) {
- this.authTokenContext.set(status);
- if (status === AuthTokenStatus.SignedIn) {
- this.signInNotificationDisposable.clear();
+ private async initializeActiveAccount(): Promise {
+ const accounts = await this.authenticationService.getAccounts(MSA);
+ // MSA provider has not yet been registered
+ if (!accounts) {
+ return;
}
+
+ if (accounts.length === 0) {
+ this.activeAccount = undefined;
+ return;
+ }
+
+ if (accounts.length === 1) {
+ this.activeAccount = accounts[0];
+ return;
+ }
+
+ const selectedAccount = await this.quickInputService.pick(accounts.map(account => {
+ return {
+ id: account.id,
+ label: account.displayName
+ };
+ }), { canPickMany: false });
+
+ if (selectedAccount) {
+ this.activeAccount = accounts.filter(account => selectedAccount.id === account.id)[0];
+ }
+ }
+
+ get activeAccount(): Account | undefined {
+ return this._activeAccount;
+ }
+
+ set activeAccount(account: Account | undefined) {
+ this._activeAccount = account;
+
+ if (account) {
+ this.userDataAuthTokenService.setToken(account.accessToken);
+ this.authenticationState.set(MSAAuthStatus.SignedIn);
+ } else {
+ this.userDataAuthTokenService.setToken(undefined);
+ this.authenticationState.set(MSAAuthStatus.SignedOut);
+ }
+
this.updateBadge();
}
+ private onDidChangeAccounts(event: ChangeAccountEventData): void {
+ if (event.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];
+ this.activeAccount = matchingAccount;
+ } else {
+ this.initializeActiveAccount();
+ }
+ }
+ }
+
+ private async onDidRegisterAuthenticationProvider(providerId: string) {
+ if (providerId === MSA) {
+ await this.initializeActiveAccount();
+ }
+ }
+
+ private onDidUnregisterAuthenticationProvider(providerId: string) {
+ if (providerId === MSA) {
+ this.activeAccount = undefined;
+ this.authenticationState.reset();
+ }
+ }
+
private onDidChangeSyncStatus(status: SyncStatus) {
this.syncStatusContext.set(status);
@@ -140,7 +214,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
this.updateBadge();
const enabled = this.configurationService.getValue(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING);
if (enabled) {
- if (this.authTokenService.status === AuthTokenStatus.SignedOut) {
+ if (this.authenticationState.get() === MSAAuthStatus.SignedOut) {
const handle = this.notificationService.prompt(Severity.Info, this.getSignInAndTurnOnDetailString(),
[
{
@@ -156,19 +230,15 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
}
}
- private updateBadge(): void {
+ private async updateBadge(): Promise {
this.badgeDisposable.clear();
let badge: IBadge | undefined = undefined;
let clazz: string | undefined;
let priority: number | undefined = undefined;
- if (this.userDataSyncService.status !== SyncStatus.Uninitialized && this.configurationService.getValue(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING) && this.authTokenService.status === AuthTokenStatus.SignedOut) {
+ if (this.userDataSyncService.status !== SyncStatus.Uninitialized && this.configurationService.getValue(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING) && this.authenticationState.get() === MSAAuthStatus.SignedOut) {
badge = new NumberBadge(1, () => localize('sign in to sync', "Sign in to Sync"));
- } else if (this.authTokenService.status === AuthTokenStatus.SigningIn) {
- badge = new ProgressBadge(() => localize('signing in', "Signing in..."));
- clazz = 'progress-badge';
- priority = 1;
} else if (this.userDataSyncService.status === SyncStatus.HasConflicts) {
badge = new NumberBadge(1, () => localize('resolve conflicts', "Resolve Conflicts"));
} else if (this.userDataSyncService.status === SyncStatus.Syncing) {
@@ -279,7 +349,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
private async turnOn(): Promise {
const message = localize('turn on sync', "Turn on Sync");
let detail: string, primaryButton: string;
- if (this.authTokenService.status === AuthTokenStatus.SignedIn) {
+ if (this.authenticationState.get() === MSAAuthStatus.SignedIn) {
detail = this.getTurnOnDetailString();
primaryButton = localize('turn on', "Turn on");
} else {
@@ -291,7 +361,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
case 1: return;
case 2: await this.configureSyncOptions(); return this.turnOn();
}
- if (this.authTokenService.status !== AuthTokenStatus.SignedIn) {
+ if (this.authenticationState.get() === MSAAuthStatus.SignedOut) {
await this.signIn();
}
await this.configurationService.updateValue(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING, true);
@@ -357,7 +427,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
private async signIn(): Promise {
try {
- await this.authTokenService.login();
+ this.activeAccount = await this.authenticationService.login(MSA);
} catch (e) {
this.notificationService.error(e);
throw e;
@@ -365,7 +435,10 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
}
private async signOut(): Promise {
- await this.authTokenService.logout();
+ if (this.activeAccount) {
+ await this.authenticationService.logout(MSA, this.activeAccount.id);
+ this.activeAccount = undefined;
+ }
}
private async continueSync(): Promise {
@@ -435,7 +508,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
private registerActions(): void {
const turnOnSyncCommandId = 'workbench.userData.actions.syncStart';
- const turnOnSyncWhenContext = ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), ContextKeyExpr.not(`config.${UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING}`), CONTEXT_AUTH_TOKEN_STATE.notEqualsTo(AuthTokenStatus.SigningIn));
+ const turnOnSyncWhenContext = ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), ContextKeyExpr.not(`config.${UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING}`), CONTEXT_AUTH_TOKEN_STATE.notEqualsTo(MSAAuthStatus.Initializing));
CommandsRegistry.registerCommand(turnOnSyncCommandId, () => this.turnOn());
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
group: '5_sync',
@@ -454,7 +527,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
});
const signInCommandId = 'workbench.userData.actions.signin';
- const signInWhenContext = ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), ContextKeyExpr.has(`config.${UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING}`), CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthTokenStatus.SignedOut));
+ const signInWhenContext = ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), ContextKeyExpr.has(`config.${UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING}`), CONTEXT_AUTH_TOKEN_STATE.isEqualTo(MSAAuthStatus.SignedOut));
CommandsRegistry.registerCommand(signInCommandId, () => this.signIn());
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
group: '5_sync',
@@ -472,18 +545,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
when: signInWhenContext,
});
- const signingInCommandId = 'workbench.userData.actions.signingin';
- CommandsRegistry.registerCommand(signingInCommandId, () => null);
- MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
- group: '5_sync',
- command: {
- id: signingInCommandId,
- title: localize('signinig in', "Signing in..."),
- precondition: FalseContext
- },
- when: CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthTokenStatus.SigningIn)
- });
-
const stopSyncCommandId = 'workbench.userData.actions.stopSync';
CommandsRegistry.registerCommand(stopSyncCommandId, () => this.turnOff());
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
@@ -492,7 +553,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
id: stopSyncCommandId,
title: localize('global activity stop sync', "Turn off sync")
},
- when: ContextKeyExpr.and(ContextKeyExpr.has(`config.${UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING}`), CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthTokenStatus.SignedIn), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.HasConflicts))
+ when: ContextKeyExpr.and(ContextKeyExpr.has(`config.${UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING}`), CONTEXT_AUTH_TOKEN_STATE.isEqualTo(MSAAuthStatus.SignedIn), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.HasConflicts))
});
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
command: {
@@ -563,7 +624,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
id: 'workbench.userData.actions.signout',
title: localize('sign out', "Sync: Sign out")
},
- when: ContextKeyExpr.and(CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthTokenStatus.SignedIn)),
+ when: ContextKeyExpr.and(CONTEXT_AUTH_TOKEN_STATE.isEqualTo(MSAAuthStatus.SignedIn)),
};
CommandsRegistry.registerCommand(signOutMenuItem.command.id, () => this.signOut());
MenuRegistry.appendMenuItem(MenuId.CommandPalette, signOutMenuItem);
diff --git a/src/vs/workbench/services/authToken/browser/authTokenService.ts b/src/vs/workbench/services/authToken/browser/authTokenService.ts
deleted file mode 100644
index ef7af7506a2..00000000000
--- a/src/vs/workbench/services/authToken/browser/authTokenService.ts
+++ /dev/null
@@ -1,74 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-import { localize } from 'vs/nls';
-import { Event, Emitter } from 'vs/base/common/event';
-import { IAuthTokenService, AuthTokenStatus } from 'vs/platform/auth/common/auth';
-import { ICredentialsService } from 'vs/platform/credentials/common/credentials';
-import { Disposable } from 'vs/base/common/lifecycle';
-import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
-import { URI } from 'vs/base/common/uri';
-
-const SERVICE_NAME = 'VS Code';
-const ACCOUNT = 'MyAccount';
-
-export class AuthTokenService extends Disposable implements IAuthTokenService {
- _serviceBrand: undefined;
-
- private _status: AuthTokenStatus = AuthTokenStatus.Initializing;
- get status(): AuthTokenStatus { return this._status; }
- private _onDidChangeStatus: Emitter = this._register(new Emitter());
- readonly onDidChangeStatus: Event = this._onDidChangeStatus.event;
-
- readonly _onDidGetCallback: Emitter = this._register(new Emitter());
-
- constructor(
- @ICredentialsService private readonly credentialsService: ICredentialsService,
- @IQuickInputService private readonly quickInputService: IQuickInputService
- ) {
- super();
- this.getToken().then(token => {
- if (token) {
- this.setStatus(AuthTokenStatus.SignedIn);
- } else {
- this.setStatus(AuthTokenStatus.SignedOut);
- }
- });
- }
-
- async getToken(): Promise {
- const token = await this.credentialsService.getPassword(SERVICE_NAME, ACCOUNT);
- if (token) {
- return token;
- }
-
- return;
- }
-
- async login(): Promise {
- const token = await this.quickInputService.input({ placeHolder: localize('enter token', "Please provide the auth bearer token"), ignoreFocusLost: true, });
- if (token) {
- await this.credentialsService.setPassword(SERVICE_NAME, ACCOUNT, token);
- this.setStatus(AuthTokenStatus.SignedIn);
- }
- }
-
- async refreshToken(): Promise {
- await this.logout();
- }
-
- async logout(): Promise {
- await this.credentialsService.deletePassword(SERVICE_NAME, ACCOUNT);
- this.setStatus(AuthTokenStatus.SignedOut);
- }
-
- private setStatus(status: AuthTokenStatus): void {
- if (this._status !== status) {
- this._status = status;
- this._onDidChangeStatus.fire(status);
- }
- }
-
-}
diff --git a/src/vs/workbench/services/authToken/electron-browser/authTokenService.ts b/src/vs/workbench/services/authToken/electron-browser/authTokenService.ts
deleted file mode 100644
index ad7abd58826..00000000000
--- a/src/vs/workbench/services/authToken/electron-browser/authTokenService.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
-import { Disposable } from 'vs/base/common/lifecycle';
-import { Emitter, Event } from 'vs/base/common/event';
-import { IChannel } from 'vs/base/parts/ipc/common/ipc';
-import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
-import { IAuthTokenService, AuthTokenStatus } from 'vs/platform/auth/common/auth';
-import { URI } from 'vs/base/common/uri';
-
-export class AuthTokenService extends Disposable implements IAuthTokenService {
-
- _serviceBrand: undefined;
-
- private readonly channel: IChannel;
-
- private _status: AuthTokenStatus = AuthTokenStatus.Initializing;
- get status(): AuthTokenStatus { return this._status; }
- private _onDidChangeStatus: Emitter = this._register(new Emitter());
- readonly onDidChangeStatus: Event = this._onDidChangeStatus.event;
-
- readonly _onDidGetCallback: Emitter = this._register(new Emitter());
-
- constructor(
- @ISharedProcessService sharedProcessService: ISharedProcessService,
- ) {
- super();
- this.channel = sharedProcessService.getChannel('authToken');
- this._register(this.channel.listen('onDidChangeStatus')(status => this.updateStatus(status)));
- this.channel.call('_getInitialStatus').then(status => this.updateStatus(status));
- }
-
- getToken(): Promise {
- return this.channel.call('getToken');
- }
-
- login(): Promise {
- return this.channel.call('login');
- }
-
- refreshToken(): Promise {
- return this.channel.call('getToken');
- }
-
- logout(): Promise {
- return this.channel.call('logout');
- }
-
- private async updateStatus(status: AuthTokenStatus): Promise {
- if (status !== AuthTokenStatus.Initializing) {
- this._status = status;
- this._onDidChangeStatus.fire(status);
- }
- }
-
-}
-
-registerSingleton(IAuthTokenService, AuthTokenService);
diff --git a/src/vs/workbench/services/authentication/browser/authenticationService.ts b/src/vs/workbench/services/authentication/browser/authenticationService.ts
new file mode 100644
index 00000000000..ca2a2fd8e1a
--- /dev/null
+++ b/src/vs/workbench/services/authentication/browser/authenticationService.ts
@@ -0,0 +1,96 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { Emitter, Event } from 'vs/base/common/event';
+import { Disposable } from 'vs/base/common/lifecycle';
+import { Account } 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;
+
+ readonly onDidRegisterAuthenticationProvider: Event;
+ readonly onDidUnregisterAuthenticationProvider: Event;
+
+ readonly onDidChangeAccounts: Event;
+ getAccounts(providerId: string): Promise | undefined>;
+ login(providerId: string): Promise;
+ logout(providerId: string, accountId: string): Promise;
+}
+
+export class AuthenticationService extends Disposable implements IAuthenticationService {
+ _serviceBrand: undefined;
+
+ private _authenticationProviders: Map = new Map();
+
+ private _onDidRegisterAuthenticationProvider: Emitter = this._register(new Emitter());
+ readonly onDidRegisterAuthenticationProvider: Event = this._onDidRegisterAuthenticationProvider.event;
+
+ 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;
+
+ constructor() {
+ super();
+ }
+
+ registerAuthenticationProvider(id: string, authenticationProvider: MainThreadAuthenticationProvider): void {
+ this._authenticationProviders.set(id, authenticationProvider);
+ this._onDidRegisterAuthenticationProvider.fire(id);
+ }
+
+ unregisterAuthenticationProvider(id: string): void {
+ this._authenticationProviders.delete(id);
+ this._onDidUnregisterAuthenticationProvider.fire(id);
+ }
+
+ accountsUpdate(providerId: string, accounts: ReadonlyArray): void {
+ this._onDidChangeAccounts.fire({ providerId, accounts });
+ }
+
+ async getAccounts(id: string): Promise | undefined> {
+ const authProvider = this._authenticationProviders.get(id);
+ if (authProvider) {
+ return await authProvider.accounts();
+ }
+
+ return undefined;
+ }
+
+ async login(id: string): Promise {
+ const authProvider = this._authenticationProviders.get(id);
+ if (authProvider) {
+ return authProvider.login();
+ } else {
+ throw new Error(`No authentication provider '${id}' is currently registered.`);
+ }
+ }
+
+ async logout(id: string, accountId: string): Promise {
+ const authProvider = this._authenticationProviders.get(id);
+ if (authProvider) {
+ return authProvider.logout(accountId);
+ } else {
+ throw new Error(`No authentication provider '${id}' is currently registered.`);
+ }
+ }
+}
+
+registerSingleton(IAuthenticationService, AuthenticationService);
diff --git a/src/vs/workbench/services/userDataSync/electron-browser/userDataAuthTokenService.ts b/src/vs/workbench/services/userDataSync/electron-browser/userDataAuthTokenService.ts
new file mode 100644
index 00000000000..998630e1ef9
--- /dev/null
+++ b/src/vs/workbench/services/userDataSync/electron-browser/userDataAuthTokenService.ts
@@ -0,0 +1,37 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { IUserDataAuthTokenService } from 'vs/platform/userDataSync/common/userDataSync';
+import { IChannel } from 'vs/base/parts/ipc/common/ipc';
+import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
+import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
+import { Disposable } from 'vs/base/common/lifecycle';
+import { Emitter, Event } from 'vs/base/common/event';
+
+export class UserDataAuthTokenService extends Disposable implements IUserDataAuthTokenService {
+
+ _serviceBrand: undefined;
+
+ private readonly channel: IChannel;
+ private _onDidChangeToken: Emitter = this._register(new Emitter());
+ readonly onDidChangeToken: Event = this._onDidChangeToken.event;
+
+ constructor(
+ @ISharedProcessService sharedProcessService: ISharedProcessService,
+ ) {
+ super();
+ this.channel = sharedProcessService.getChannel('authToken');
+ }
+
+ getToken(): Promise {
+ return this.channel.call('getToken');
+ }
+
+ setToken(token: string | undefined): Promise {
+ return this.channel.call('setToken', token);
+ }
+}
+
+registerSingleton(IUserDataAuthTokenService, UserDataAuthTokenService);
diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts
index bd8b76dad8f..f9656900129 100644
--- a/src/vs/workbench/workbench.desktop.main.ts
+++ b/src/vs/workbench/workbench.desktop.main.ts
@@ -51,7 +51,8 @@ import 'vs/workbench/services/workspaces/electron-browser/workspacesService';
import 'vs/workbench/services/workspaces/electron-browser/workspaceEditingService';
import 'vs/workbench/services/userDataSync/electron-browser/userDataSyncService';
import 'vs/workbench/services/userDataSync/electron-browser/settingsSyncService';
-import 'vs/workbench/services/authToken/electron-browser/authTokenService';
+import 'vs/workbench/services/userDataSync/electron-browser/userDataAuthTokenService';
+import 'vs/workbench/services/authentication/browser/authenticationService';
import 'vs/workbench/services/host/electron-browser/desktopHostService';
import 'vs/workbench/services/request/electron-browser/requestService';
import 'vs/workbench/services/lifecycle/electron-browser/lifecycleService';
diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts
index e6c3807eeff..9db377b87dd 100644
--- a/src/vs/workbench/workbench.web.main.ts
+++ b/src/vs/workbench/workbench.web.main.ts
@@ -63,9 +63,8 @@ import { ITunnelService } from 'vs/platform/remote/common/tunnel';
import { TunnelService } from 'vs/workbench/services/remote/common/tunnelService';
import { ILoggerService } from 'vs/platform/log/common/log';
import { FileLoggerService } from 'vs/platform/log/common/fileLogService';
-import { IAuthTokenService } from 'vs/platform/auth/common/auth';
-import { AuthTokenService } from 'vs/workbench/services/authToken/browser/authTokenService';
import { IUserDataSyncStoreService, IUserDataSyncService, IUserDataSyncLogService, ISettingsSyncService } from 'vs/platform/userDataSync/common/userDataSync';
+import { AuthenticationService, IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService';
import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog';
import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService';
import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService';
@@ -77,7 +76,7 @@ registerSingleton(IAccessibilityService, BrowserAccessibilityService, true);
registerSingleton(IContextMenuService, ContextMenuService);
registerSingleton(ITunnelService, TunnelService, true);
registerSingleton(ILoggerService, FileLoggerService);
-registerSingleton(IAuthTokenService, AuthTokenService);
+registerSingleton(IAuthenticationService, AuthenticationService);
registerSingleton(IUserDataSyncLogService, UserDataSyncLogService);
registerSingleton(IUserDataSyncStoreService, UserDataSyncStoreService);
registerSingleton(ISettingsSyncService, SettingsSynchroniser);