From d0481dca928b7f832eb4c3bfa5898e71f1bb017e Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 10 Sep 2019 11:06:49 +0200 Subject: [PATCH] web - move selfhost pieces out of workbench --- build/gulpfile.vscode.web.js | 3 +- src/vs/code/browser/workbench/web.main.ts | 210 ++++++++++++++++++ .../code/browser/workbench/workbench-dev.html | 65 ++++++ src/vs/code/browser/workbench/workbench.html | 38 +++- src/vs/code/browser/workbench/workbench.js | 31 --- src/vs/workbench/buildfile.web.js | 1 + .../credentials/browser/credentialsService.ts | 80 ++----- .../services/url/browser/urlService.ts | 127 +---------- 8 files changed, 343 insertions(+), 212 deletions(-) create mode 100644 src/vs/code/browser/workbench/web.main.ts create mode 100644 src/vs/code/browser/workbench/workbench-dev.html delete mode 100644 src/vs/code/browser/workbench/workbench.js diff --git a/build/gulpfile.vscode.web.js b/build/gulpfile.vscode.web.js index 8f2e9c9f5a2..d0cb6be52c7 100644 --- a/build/gulpfile.vscode.web.js +++ b/build/gulpfile.vscode.web.js @@ -34,7 +34,8 @@ const nodeModules = Object.keys(product.dependencies || {}) const vscodeWebResources = [ // Workbench - 'out-build/vs/{base,platform,editor,workbench}/**/*.{svg,png,html}', + 'out-build/vs/{base,platform,editor,workbench}/**/*.{svg,png}', + 'out-build/vs/code/browser/workbench/workbench.html', 'out-build/vs/base/browser/ui/octiconLabel/octicons/**', 'out-build/vs/**/markdown.css', diff --git a/src/vs/code/browser/workbench/web.main.ts b/src/vs/code/browser/workbench/web.main.ts new file mode 100644 index 00000000000..656bee0daa7 --- /dev/null +++ b/src/vs/code/browser/workbench/web.main.ts @@ -0,0 +1,210 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IWorkbenchConstructionOptions, create } from 'vs/workbench/workbench.web.api'; +import { IURLCallbackProvider } from 'vs/workbench/services/url/browser/urlService'; +import { Event, Emitter } from 'vs/base/common/event'; +import { URI, UriComponents } from 'vs/base/common/uri'; +import { generateUuid } from 'vs/base/common/uuid'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { streamToBuffer } from 'vs/base/common/buffer'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { request } from 'vs/base/parts/request/browser/request'; +import { ICredentialsProvider } from 'vs/workbench/services/credentials/browser/credentialsService'; + +export function main(): void { + const options: IWorkbenchConstructionOptions = JSON.parse(document.getElementById('vscode-workbench-web-configuration')!.getAttribute('data-settings')!); + options.urlCallbackProvider = new PollingURLCallbackProvider(); + options.credentialsProvider = new LocalStorageCredentialsProvider(); + + create(document.body, options); +} + +interface ICredential { + service: string; + account: string; + password: string; +} + +class LocalStorageCredentialsProvider implements ICredentialsProvider { + + static readonly CREDENTIALS_OPENED_KEY = 'credentials.provider'; + + private _credentials: ICredential[]; + private get credentials(): ICredential[] { + if (!this._credentials) { + try { + const serializedCredentials = window.localStorage.getItem(LocalStorageCredentialsProvider.CREDENTIALS_OPENED_KEY); + if (serializedCredentials) { + this._credentials = JSON.parse(serializedCredentials); + } + } catch (error) { + // ignore + } + + if (!Array.isArray(this._credentials)) { + this._credentials = []; + } + } + + return this._credentials; + } + + private save(): void { + window.localStorage.setItem(LocalStorageCredentialsProvider.CREDENTIALS_OPENED_KEY, JSON.stringify(this.credentials)); + } + + async getPassword(service: string, account: string): Promise { + return this.doGetPassword(service, account); + } + + private async doGetPassword(service: string, account?: string): Promise { + for (const credential of this.credentials) { + if (credential.service === service) { + if (typeof account !== 'string' || account === credential.account) { + return credential.password; + } + } + } + + return null; + } + + async setPassword(service: string, account: string, password: string): Promise { + this.deletePassword(service, account); + + this.credentials.push({ service, account, password }); + + this.save(); + } + + async deletePassword(service: string, account: string): Promise { + let found = false; + + this._credentials = this.credentials.filter(credential => { + if (credential.service === service && credential.account === account) { + found = true; + + return false; + } + + return true; + }); + + if (found) { + this.save(); + } + + return found; + } + + async findPassword(service: string): Promise { + return this.doGetPassword(service); + } + + async findCredentials(service: string): Promise> { + return this.credentials + .filter(credential => credential.service === service) + .map(({ account, password }) => ({ account, password })); + } +} + +class PollingURLCallbackProvider extends Disposable implements IURLCallbackProvider { + + static FETCH_INTERVAL = 500; // fetch every 500ms + static FETCH_TIMEOUT = 5 * 60 * 1000; // ...but stop after 5min + + static QUERY_KEYS = { + REQUEST_ID: 'vscode-requestId', + SCHEME: 'vscode-scheme', + AUTHORITY: 'vscode-authority', + PATH: 'vscode-path', + QUERY: 'vscode-query', + FRAGMENT: 'vscode-fragment' + }; + + private readonly _onCallback: Emitter = this._register(new Emitter()); + readonly onCallback: Event = this._onCallback.event; + + create(options?: Partial): URI { + const queryValues: Map = new Map(); + + const requestId = generateUuid(); + queryValues.set(PollingURLCallbackProvider.QUERY_KEYS.REQUEST_ID, requestId); + + const { scheme, authority, path, query, fragment } = options ? options : { scheme: undefined, authority: undefined, path: undefined, query: undefined, fragment: undefined }; + + if (scheme) { + queryValues.set(PollingURLCallbackProvider.QUERY_KEYS.SCHEME, scheme); + } + + if (authority) { + queryValues.set(PollingURLCallbackProvider.QUERY_KEYS.AUTHORITY, authority); + } + + if (path) { + queryValues.set(PollingURLCallbackProvider.QUERY_KEYS.PATH, path); + } + + if (query) { + queryValues.set(PollingURLCallbackProvider.QUERY_KEYS.QUERY, query); + } + + if (fragment) { + queryValues.set(PollingURLCallbackProvider.QUERY_KEYS.FRAGMENT, fragment); + } + + // Start to poll on the callback being fired + this.periodicFetchCallback(requestId, Date.now()); + + return this.doCreateUri('/callback', queryValues); + } + + private async periodicFetchCallback(requestId: string, startTime: number): Promise { + + // Ask server for callback results + const queryValues: Map = new Map(); + queryValues.set(PollingURLCallbackProvider.QUERY_KEYS.REQUEST_ID, requestId); + + const result = await request({ + url: this.doCreateUri('/fetch-callback', queryValues).toString(true) + }, CancellationToken.None); + + // Check for callback results + const content = await streamToBuffer(result.stream); + if (content.byteLength > 0) { + try { + this._onCallback.fire(URI.revive(JSON.parse(content.toString()))); + } catch (error) { + console.error(error); + } + + return; // done + } + + // Continue fetching unless we hit the timeout + if (Date.now() - startTime < PollingURLCallbackProvider.FETCH_TIMEOUT) { + setTimeout(() => this.periodicFetchCallback(requestId, startTime), PollingURLCallbackProvider.FETCH_INTERVAL); + } + } + + private doCreateUri(path: string, queryValues: Map): URI { + let query: string | undefined = undefined; + + if (queryValues) { + let index = 0; + queryValues.forEach((value, key) => { + if (!query) { + query = ''; + } + + const prefix = (index++ === 0) ? '' : '&'; + query += `${prefix}${key}=${encodeURIComponent(value)}`; + }); + } + + return URI.parse(window.location.href).with({ path, query }); + } +} diff --git a/src/vs/code/browser/workbench/workbench-dev.html b/src/vs/code/browser/workbench/workbench-dev.html new file mode 100644 index 00000000000..a7769b8cd64 --- /dev/null +++ b/src/vs/code/browser/workbench/workbench-dev.html @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/vs/code/browser/workbench/workbench.html b/src/vs/code/browser/workbench/workbench.html index 1d9a0b8308d..5fead4265ff 100644 --- a/src/vs/code/browser/workbench/workbench.html +++ b/src/vs/code/browser/workbench/workbench.html @@ -10,18 +10,19 @@ + manifest-src 'self'; + "> @@ -33,13 +34,40 @@ + + + + + + + - + + + diff --git a/src/vs/code/browser/workbench/workbench.js b/src/vs/code/browser/workbench/workbench.js deleted file mode 100644 index 2f09f53e43a..00000000000 --- a/src/vs/code/browser/workbench/workbench.js +++ /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. - *--------------------------------------------------------------------------------------------*/ - -//@ts-check -'use strict'; - -(function () { - - /** @type any */ - const amdLoader = require; - - amdLoader.config({ - baseUrl: `${window.location.origin}/static/out`, - paths: { - 'vscode-textmate': `${window.location.origin}/static/node_modules/vscode-textmate/release/main`, - 'onigasm-umd': `${window.location.origin}/static/node_modules/onigasm-umd/release/main`, - 'xterm': `${window.location.origin}/static/node_modules/xterm/lib/xterm.js`, - 'xterm-addon-search': `${window.location.origin}/static/node_modules/xterm-addon-search/lib/xterm-addon-search.js`, - 'xterm-addon-web-links': `${window.location.origin}/static/node_modules/xterm-addon-web-links/lib/xterm-addon-web-links.js`, - 'semver-umd': `${window.location.origin}/static/node_modules/semver-umd/lib/semver-umd.js`, - '@microsoft/applicationinsights-web': `${window.location.origin}/static/node_modules/@microsoft/applicationinsights-web/dist/applicationinsights-web.js`, - } - }); - - amdLoader(['vs/workbench/workbench.web.api'], function (api) { - const options = JSON.parse(document.getElementById('vscode-workbench-web-configuration').getAttribute('data-settings')); - api.create(document.body, options); - }); -})(); diff --git a/src/vs/workbench/buildfile.web.js b/src/vs/workbench/buildfile.web.js index 47a84533025..ac2dec472bd 100644 --- a/src/vs/workbench/buildfile.web.js +++ b/src/vs/workbench/buildfile.web.js @@ -20,5 +20,6 @@ function createModuleDescription(name, exclude) { exports.collectModules = function () { return [ createModuleDescription('vs/workbench/contrib/output/common/outputLinkComputer', ['vs/base/common/worker/simpleWorker', 'vs/editor/common/services/editorSimpleWorker']), + createModuleDescription('vs/code/browser/workbench/web.main', ['vs/workbench/workbench.web.api']), ]; }; diff --git a/src/vs/workbench/services/credentials/browser/credentialsService.ts b/src/vs/workbench/services/credentials/browser/credentialsService.ts index 3bc3637a2b6..d4b425e4adc 100644 --- a/src/vs/workbench/services/credentials/browser/credentialsService.ts +++ b/src/vs/workbench/services/credentials/browser/credentialsService.ts @@ -25,7 +25,7 @@ export class BrowserCredentialsService implements ICredentialsService { if (environmentService.options && environmentService.options.credentialsProvider) { this.credentialsProvider = environmentService.options.credentialsProvider; } else { - this.credentialsProvider = new LocalStorageCredentialsProvider(); + this.credentialsProvider = new InMemoryCredentialsProvider(); } } @@ -56,80 +56,44 @@ interface ICredential { password: string; } -class LocalStorageCredentialsProvider implements ICredentialsProvider { +class InMemoryCredentialsProvider implements ICredentialsProvider { - static readonly CREDENTIALS_OPENED_KEY = 'credentials.provider'; - - private _credentials: ICredential[] | undefined; - private get credentials(): ICredential[] { - if (!this._credentials) { - try { - const serializedCredentials = window.localStorage.getItem(LocalStorageCredentialsProvider.CREDENTIALS_OPENED_KEY); - if (serializedCredentials) { - this._credentials = JSON.parse(serializedCredentials); - } - } catch (error) { - // ignore - } - - if (!Array.isArray(this._credentials)) { - this._credentials = []; - } - } - - return this._credentials; - } - - private save(): void { - window.localStorage.setItem(LocalStorageCredentialsProvider.CREDENTIALS_OPENED_KEY, JSON.stringify(this.credentials)); - } + private credentials: ICredential[] = []; async getPassword(service: string, account: string): Promise { - return this.doGetPassword(service, account); - } + const credential = this.doFindPassword(service, account); - private async doGetPassword(service: string, account?: string): Promise { - for (const credential of this.credentials) { - if (credential.service === service) { - if (typeof account !== 'string' || account === credential.account) { - return credential.password; - } - } - } - - return null; + return credential ? credential.password : null; } async setPassword(service: string, account: string, password: string): Promise { this.deletePassword(service, account); - this.credentials.push({ service, account, password }); - - this.save(); } async deletePassword(service: string, account: string): Promise { - let found = false; - - this._credentials = this.credentials.filter(credential => { - if (credential.service === service && credential.account === account) { - found = true; - - return false; - } - - return true; - }); - - if (found) { - this.save(); + const credential = this.doFindPassword(service, account); + if (credential) { + this.credentials = this.credentials.splice(this.credentials.indexOf(credential), 1); } - return found; + return !!credential; } async findPassword(service: string): Promise { - return this.doGetPassword(service); + const credential = this.doFindPassword(service); + + return credential ? credential.password : null; + } + + private doFindPassword(service: string, account?: string): ICredential | null { + for (const credential of this.credentials) { + if (credential.service === service && (typeof account !== 'string' || credential.account === account)) { + return credential; + } + } + + return null; } async findCredentials(service: string): Promise> { diff --git a/src/vs/workbench/services/url/browser/urlService.ts b/src/vs/workbench/services/url/browser/urlService.ts index dfbca795c40..b073447fc26 100644 --- a/src/vs/workbench/services/url/browser/urlService.ts +++ b/src/vs/workbench/services/url/browser/urlService.ts @@ -5,17 +5,10 @@ import { IURLService } from 'vs/platform/url/common/url'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { AbstractURLService } from 'vs/platform/url/common/urlService'; -import { Event, Emitter } from 'vs/base/common/event'; +import { Event } from 'vs/base/common/event'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { IRequestService } from 'vs/platform/request/common/request'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { streamToBuffer } from 'vs/base/common/buffer'; -import { ILogService } from 'vs/platform/log/common/log'; -import { generateUuid } from 'vs/base/common/uuid'; export interface IURLCallbackProvider { @@ -47,130 +40,30 @@ export class BrowserURLService extends AbstractURLService { _serviceBrand: undefined; - private provider: IURLCallbackProvider; + private provider: IURLCallbackProvider | undefined; constructor( - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, - @IInstantiationService instantiationService: IInstantiationService + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService ) { super(); - this.provider = environmentService.options && environmentService.options.urlCallbackProvider ? environmentService.options.urlCallbackProvider : instantiationService.createInstance(SelfhostURLCallbackProvider); + this.provider = environmentService.options!.urlCallbackProvider; this.registerListeners(); } private registerListeners(): void { - this._register(this.provider.onCallback(uri => this.open(uri))); + if (this.provider) { + this._register(this.provider.onCallback(uri => this.open(uri))); + } } create(options?: Partial): URI { - return this.provider.create(options); - } -} - -class SelfhostURLCallbackProvider extends Disposable implements IURLCallbackProvider { - - static FETCH_INTERVAL = 500; // fetch every 500ms - static FETCH_TIMEOUT = 5 * 60 * 1000; // ...but stop after 5min - - static QUERY_KEYS = { - REQUEST_ID: 'vscode-requestId', - SCHEME: 'vscode-scheme', - AUTHORITY: 'vscode-authority', - PATH: 'vscode-path', - QUERY: 'vscode-query', - FRAGMENT: 'vscode-fragment' - }; - - private readonly _onCallback: Emitter = this._register(new Emitter()); - readonly onCallback: Event = this._onCallback.event; - - constructor( - @IRequestService private readonly requestService: IRequestService, - @ILogService private readonly logService: ILogService - ) { - super(); - } - - create(options?: Partial): URI { - const queryValues: Map = new Map(); - - const requestId = generateUuid(); - queryValues.set(SelfhostURLCallbackProvider.QUERY_KEYS.REQUEST_ID, requestId); - - const { scheme, authority, path, query, fragment } = options ? options : { scheme: undefined, authority: undefined, path: undefined, query: undefined, fragment: undefined }; - - if (scheme) { - queryValues.set(SelfhostURLCallbackProvider.QUERY_KEYS.SCHEME, scheme); + if (this.provider) { + return this.provider.create(options); } - if (authority) { - queryValues.set(SelfhostURLCallbackProvider.QUERY_KEYS.AUTHORITY, authority); - } - - if (path) { - queryValues.set(SelfhostURLCallbackProvider.QUERY_KEYS.PATH, path); - } - - if (query) { - queryValues.set(SelfhostURLCallbackProvider.QUERY_KEYS.QUERY, query); - } - - if (fragment) { - queryValues.set(SelfhostURLCallbackProvider.QUERY_KEYS.FRAGMENT, fragment); - } - - // Start to poll on the callback being fired - this.periodicFetchCallback(requestId, Date.now()); - - return this.doCreateUri('/callback', queryValues); - } - - private async periodicFetchCallback(requestId: string, startTime: number): Promise { - - // Ask server for callback results - const queryValues: Map = new Map(); - queryValues.set(SelfhostURLCallbackProvider.QUERY_KEYS.REQUEST_ID, requestId); - - const result = await this.requestService.request({ - url: this.doCreateUri('/fetch-callback', queryValues).toString(true) - }, CancellationToken.None); - - // Check for callback results - const content = await streamToBuffer(result.stream); - if (content.byteLength > 0) { - try { - this._onCallback.fire(URI.revive(JSON.parse(content.toString()))); - } catch (error) { - this.logService.error(error); - } - - return; // done - } - - // Continue fetching unless we hit the timeout - if (Date.now() - startTime < SelfhostURLCallbackProvider.FETCH_TIMEOUT) { - setTimeout(() => this.periodicFetchCallback(requestId, startTime), SelfhostURLCallbackProvider.FETCH_INTERVAL); - } - } - - private doCreateUri(path: string, queryValues: Map): URI { - let query: string | undefined = undefined; - - if (queryValues) { - let index = 0; - queryValues.forEach((value, key) => { - if (!query) { - query = ''; - } - - const prefix = (index++ === 0) ? '' : '&'; - query += `${prefix}${key}=${encodeURIComponent(value)}`; - }); - } - - return URI.parse(window.location.href).with({ path, query }); + return URI.parse('unsupported://'); } }