From 7cd26b1298572c793126a598d02d428949ffc63a Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 24 Oct 2022 20:58:21 +0200 Subject: [PATCH 1/3] polish --- .../electron-browser/remoteTunnelService.ts | 43 +++++++++----- .../remoteTunnel.contribution.ts | 56 +++++++++++++++---- 2 files changed, 73 insertions(+), 26 deletions(-) diff --git a/src/vs/platform/remoteTunnel/electron-browser/remoteTunnelService.ts b/src/vs/platform/remoteTunnel/electron-browser/remoteTunnelService.ts index 785888f061547..df6dac668fa7a 100644 --- a/src/vs/platform/remoteTunnel/electron-browser/remoteTunnelService.ts +++ b/src/vs/platform/remoteTunnel/electron-browser/remoteTunnelService.ts @@ -9,7 +9,6 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { Disposable } from 'vs/base/common/lifecycle'; import { ILogger, ILoggerService } from 'vs/platform/log/common/log'; -import { URI } from 'vs/base/common/uri'; import { dirname, join } from 'vs/base/common/path'; import { ChildProcess, spawn } from 'child_process'; import { IProductService } from 'vs/platform/product/common/productService'; @@ -19,6 +18,7 @@ import { ISharedProcessLifecycleService } from 'vs/platform/lifecycle/electron-b import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { localize } from 'vs/nls'; import { hostname, homedir } from 'os'; +import { escapeRegExpCharacters } from 'vs/base/common/strings'; type RemoteTunnelEnablementClassification = { owner: 'aeschli'; @@ -133,8 +133,18 @@ export class RemoteTunnelService extends Disposable implements IRemoteTunnelServ this.setTunnelStatus(TunnelStates.disconnected); return; } + const { token, authenticationProviderId } = this._account; + this.setTunnelStatus(TunnelStates.connecting(localize('remoteTunnelService.authorizing', 'Authorizing'))); - const loginProcess = this.runCodeTunneCommand('login', ['user', 'login', '--provider', this._account.authenticationProviderId, '--access-token', this._account.token]); + const onOutput = (a: string, isErr: boolean) => { + a = a.replace(new RegExp(escapeRegExpCharacters(token), 'g'), '*'.repeat(4)); + if (isErr) { + this._logger.error(a); + } else { + this._logger.info(a); + } + }; + const loginProcess = this.runCodeTunneCommand('login', ['user', 'login', '--provider', authenticationProviderId, '--access-token', token], onOutput); this._tunnelProcess = loginProcess; try { await loginProcess; @@ -155,13 +165,20 @@ export class RemoteTunnelService extends Disposable implements IRemoteTunnelServ } else { args.push('--random-name'); } - const serveCommand = this.runCodeTunneCommand('tunnel', args, (message: string) => { - const m = message.match(/^\s*Open this link in your browser (https:[^\s]*)+/); - if (m && m[1]) { - const linkUri = URI.parse(m[1]); - const pathMatch = linkUri.path.match(/\/+([^\/])+\/([^\/]+)\//); - const info: ConnectionInfo = pathMatch ? { link: m[1], domain: linkUri.authority, extensionId: pathMatch[1], hostName: pathMatch[2] } : { link: m[1], domain: linkUri.authority, extensionId: '', hostName: '' }; + const serveCommand = this.runCodeTunneCommand('tunnel', args, (message: string, isErr: boolean) => { + if (isErr) { + this._logger.error(message); + } else { + this._logger.info(message); + } + const m = message.match(/^\s*Open this link in your browser (https:\/\/([^\/]+)\/([^\/]+)\/([^\/]+))/); + if (m) { + const info: ConnectionInfo = { link: m[1], domain: m[2], extensionId: 'ms-vscode.remote-server', hostName: m[4] }; this.setTunnelStatus(TunnelStates.connected(info)); + } else if (message.match(/error refreshing token/)) { + serveCommand.cancel(); + this._onDidTokenFailedEmitter.fire(true); + this.setTunnelStatus(TunnelStates.disconnected); } }); this._tunnelProcess = serveCommand; @@ -206,12 +223,12 @@ export class RemoteTunnelService extends Disposable implements IRemoteTunnelServ if (process.env['VSCODE_DEV']) { onOutput('Compiling tunnel CLI from sources and run', false); - this._logger.info(`${logLabel} Spawning: cargo run -- tunnel ${commandArgs.join(' ')}`); + onOutput(`${logLabel} Spawning: cargo run -- tunnel ${commandArgs.join(' ')}`, false); tunnelProcess = spawn('cargo', ['run', '--', 'tunnel', ...commandArgs], { cwd: join(this.environmentService.appRoot, 'cli') }); } else { onOutput('Running tunnel CLI', false); const tunnelCommand = this.getTunnelCommandLocation(); - this._logger.info(`${logLabel} Spawning: ${tunnelCommand} tunnel ${commandArgs.join(' ')}`); + onOutput(`${logLabel} Spawning: ${tunnelCommand} tunnel ${commandArgs.join(' ')}`, false); tunnelProcess = spawn(tunnelCommand, ['tunnel', ...commandArgs], { cwd: homedir() }); } @@ -219,19 +236,17 @@ export class RemoteTunnelService extends Disposable implements IRemoteTunnelServ if (tunnelProcess) { const message = data.toString(); onOutput(message, false); - this._logger.info(`${logLabel} stdout (${tunnelProcess.pid}): + ${message}`); } }); tunnelProcess.stderr!.on('data', data => { if (tunnelProcess) { const message = data.toString(); onOutput(message, true); - this._logger.info(`${logLabel} stderr (${tunnelProcess.pid}): + ${message}`); } }); tunnelProcess.on('exit', e => { if (tunnelProcess) { - this._logger.info(`${logLabel} exit (${tunnelProcess.pid}): + ${e}`); + onOutput(`${logLabel} exit (${tunnelProcess.pid}): + ${e}`, false); tunnelProcess = undefined; if (e === 0) { resolve(); @@ -242,7 +257,7 @@ export class RemoteTunnelService extends Disposable implements IRemoteTunnelServ }); tunnelProcess.on('error', e => { if (tunnelProcess) { - this._logger.info(`${logLabel} error (${tunnelProcess.pid}): + ${e}`); + onOutput(`${logLabel} error (${tunnelProcess.pid}): + ${e}`, true); tunnelProcess = undefined; reject(); } diff --git a/src/vs/workbench/contrib/remoteTunnel/electron-sandbox/remoteTunnel.contribution.ts b/src/vs/workbench/contrib/remoteTunnel/electron-sandbox/remoteTunnel.contribution.ts index ae5a2fc13be2c..1ed0675ef7ea7 100644 --- a/src/vs/workbench/contrib/remoteTunnel/electron-sandbox/remoteTunnel.contribution.ts +++ b/src/vs/workbench/contrib/remoteTunnel/electron-sandbox/remoteTunnel.contribution.ts @@ -17,7 +17,7 @@ import { ILocalizedString } from 'vs/platform/action/common/action'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IStorageService, IStorageValueChangeEvent, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ILogger, ILoggerService, ILogService } from 'vs/platform/log/common/log'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IStringDictionary } from 'vs/base/common/collections'; import { IQuickInputService, IQuickPickItem, IQuickPickSeparator, QuickPickItem } from 'vs/platform/quickinput/common/quickInput'; @@ -33,6 +33,11 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { Action } from 'vs/base/common/actions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import * as Constants from 'vs/workbench/contrib/logs/common/logConstants'; +import { posix } from 'vs/base/common/path'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { Schemas } from 'vs/base/common/network'; +import { URI } from 'vs/base/common/uri'; +import { joinPath } from 'vs/base/common/resources'; export const REMOTE_TUNNEL_CATEGORY: ILocalizedString = { original: 'Remote Tunnels', @@ -58,7 +63,7 @@ enum RemoteTunnelCommandIds { manage = 'workbench.remoteTunnel.actions.manage', showLog = 'workbench.remoteTunnel.actions.showLog', configure = 'workbench.remoteTunnel.actions.configure', - openBrowser = 'workbench.remoteTunnel.actions.openBrowser', + copyToClipboard = 'workbench.remoteTunnel.actions.copyToClipboard', learnMore = 'workbench.remoteTunnel.actions.learnMore', } @@ -67,7 +72,7 @@ namespace RemoteTunnelCommandLabels { export const turnOff = localize('remoteTunnel.actions.turnOff', 'Turn off Remote Tunnel Access...'); export const showLog = localize('remoteTunnel.actions.showLog', 'Show Log'); export const configure = localize('remoteTunnel.actions.configure', 'Configure Machine Name...'); - export const openBrowser = localize('remoteTunnel.actions.openBrowser', 'Open in Browser'); + export const copyToClipboard = localize('remoteTunnel.actions.copyToClipboard', 'Copy Browser URI to Clipboard'); export const learnMore = localize('remoteTunnel.actions.learnMore', 'Get Started with VS Code Tunnels'); } @@ -94,10 +99,11 @@ export class RemoteTunnelWorkbenchContribution extends Disposable implements IWo @ILoggerService loggerService: ILoggerService, @ILogService logService: ILogService, @IQuickInputService private readonly quickInputService: IQuickInputService, - @IEnvironmentService environmentService: IEnvironmentService, + @INativeEnvironmentService private environmentService: INativeEnvironmentService, @IFileService fileService: IFileService, @IRemoteTunnelService private remoteTunnelService: IRemoteTunnelService, @ICommandService private commandService: ICommandService, + @IWorkspaceContextService private workspaceContextService: IWorkspaceContextService, ) { super(); @@ -430,20 +436,28 @@ export class RemoteTunnelWorkbenchContribution extends Disposable implements IWo } ); if (connectionInfo) { + const linkToOpen = that.getLinkToOpen(connectionInfo); await notificationService.notify({ severity: Severity.Info, message: localize('progress.turnOn.final', "Remote tunnel access is enabled for {0}. To access from a different machine, open [{1}]({2}) or use the Remote - Tunnels extension. To [configure](command:{3}), use the Account menu.", - connectionInfo.hostName, connectionInfo.domain, connectionInfo.link, RemoteTunnelCommandIds.manage), + connectionInfo.hostName, connectionInfo.domain, linkToOpen, RemoteTunnelCommandIds.manage), actions: { primary: [ - new Action('copyToClipboard', localize('action.copyToClipboard', "Copy Browser Link to Clipboard"), undefined, true, () => clipboardService.writeText(connectionInfo.link)), + new Action('copyToClipboard', localize('action.copyToClipboard', "Copy Browser Link to Clipboard"), undefined, true, () => clipboardService.writeText(linkToOpen)), new Action('showExtension', localize('action.showExtension', "Show Extension"), undefined, true, () => { return commandService.executeCommand('workbench.extensions.action.showExtensionsWithIds', ['ms-vscode.remote-server']); }) ] } }); + } else { + await notificationService.notify({ + severity: Severity.Info, + message: localize('progress.turnOn.failed', + "Unable to turn on the remote tunnel access. Check the Remote Tunnel log for details."), + }); + await commandService.executeCommand(RemoteTunnelCommandIds.showLog); } } @@ -530,7 +544,7 @@ export class RemoteTunnelWorkbenchContribution extends Disposable implements IWo async run(accessor: ServicesAccessor) { const outputService = accessor.get(IOutputService); - outputService.showChannel(Constants.remoteServerLog); + outputService.showChannel(Constants.remoteTunnelLogChannelId); } })); @@ -556,8 +570,8 @@ export class RemoteTunnelWorkbenchContribution extends Disposable implements IWo this._register(registerAction2(class extends Action2 { constructor() { super({ - id: RemoteTunnelCommandIds.openBrowser, - title: RemoteTunnelCommandLabels.openBrowser, + id: RemoteTunnelCommandIds.copyToClipboard, + title: RemoteTunnelCommandLabels.copyToClipboard, category: REMOTE_TUNNEL_CATEGORY, precondition: ContextKeyExpr.equals(REMOTE_TUNNEL_CONNECTION_STATE_KEY, 'connected'), menu: [{ @@ -568,9 +582,10 @@ export class RemoteTunnelWorkbenchContribution extends Disposable implements IWo } async run(accessor: ServicesAccessor) { - const openerService = accessor.get(IOpenerService); + const clipboardService = accessor.get(IClipboardService); if (that.connectionInfo) { - openerService.open(that.connectionInfo.link); + const linkToOpen = that.getLinkToOpen(that.connectionInfo); + clipboardService.writeText(linkToOpen); } } @@ -593,6 +608,23 @@ export class RemoteTunnelWorkbenchContribution extends Disposable implements IWo })); } + private getLinkToOpen(connectionInfo: ConnectionInfo): string { + const workspace = this.workspaceContextService.getWorkspace(); + const folders = workspace.folders; + let resource; + if (folders.length === 1) { + resource = folders[0].uri; + } else if (workspace.configuration) { + resource = workspace.configuration; + } + const link = URI.parse(connectionInfo.link); + if (resource?.scheme === Schemas.file) { + return joinPath(link, resource.path).toString(true); + } + return joinPath(link, this.environmentService.userHome.path).toString(true); + } + + private async showManageOptions() { const account = await this.getExistingSession(); @@ -605,7 +637,7 @@ export class RemoteTunnelWorkbenchContribution extends Disposable implements IWo items.push({ id: RemoteTunnelCommandIds.learnMore, label: RemoteTunnelCommandLabels.learnMore }); if (this.connectionInfo && account) { quickPick.title = localize('manage.title.on', 'Remote Machine Access enabled for {0}({1}) as {2}', account.label, account.description, this.connectionInfo.hostName); - items.push({ id: RemoteTunnelCommandIds.openBrowser, label: RemoteTunnelCommandLabels.openBrowser, description: this.connectionInfo.domain }); + items.push({ id: RemoteTunnelCommandIds.copyToClipboard, label: RemoteTunnelCommandLabels.copyToClipboard, description: this.connectionInfo.domain }); } else { quickPick.title = localize('manage.title.off', 'Remote Machine Access not enabled'); } From b33e2520b5a56d97112418714644af17dc680bf1 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 24 Oct 2022 21:04:08 +0200 Subject: [PATCH 2/3] fix unused import --- .../remoteTunnel/electron-sandbox/remoteTunnel.contribution.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/contrib/remoteTunnel/electron-sandbox/remoteTunnel.contribution.ts b/src/vs/workbench/contrib/remoteTunnel/electron-sandbox/remoteTunnel.contribution.ts index 1ed0675ef7ea7..699299feb5cf4 100644 --- a/src/vs/workbench/contrib/remoteTunnel/electron-sandbox/remoteTunnel.contribution.ts +++ b/src/vs/workbench/contrib/remoteTunnel/electron-sandbox/remoteTunnel.contribution.ts @@ -33,7 +33,6 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { Action } from 'vs/base/common/actions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import * as Constants from 'vs/workbench/contrib/logs/common/logConstants'; -import { posix } from 'vs/base/common/path'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; From 66a27deeef5b0ea8496382fd1c2f1d2341cdfd82 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 24 Oct 2022 21:07:38 +0200 Subject: [PATCH 3/3] use replaceAll --- .../remoteTunnel/electron-browser/remoteTunnelService.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/platform/remoteTunnel/electron-browser/remoteTunnelService.ts b/src/vs/platform/remoteTunnel/electron-browser/remoteTunnelService.ts index df6dac668fa7a..0d198ae9450ad 100644 --- a/src/vs/platform/remoteTunnel/electron-browser/remoteTunnelService.ts +++ b/src/vs/platform/remoteTunnel/electron-browser/remoteTunnelService.ts @@ -18,7 +18,6 @@ import { ISharedProcessLifecycleService } from 'vs/platform/lifecycle/electron-b import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { localize } from 'vs/nls'; import { hostname, homedir } from 'os'; -import { escapeRegExpCharacters } from 'vs/base/common/strings'; type RemoteTunnelEnablementClassification = { owner: 'aeschli'; @@ -137,7 +136,7 @@ export class RemoteTunnelService extends Disposable implements IRemoteTunnelServ this.setTunnelStatus(TunnelStates.connecting(localize('remoteTunnelService.authorizing', 'Authorizing'))); const onOutput = (a: string, isErr: boolean) => { - a = a.replace(new RegExp(escapeRegExpCharacters(token), 'g'), '*'.repeat(4)); + a = a.replaceAll(token, '*'.repeat(4)); if (isErr) { this._logger.error(a); } else {