Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -133,8 +132,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.replaceAll(token, '*'.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;
Expand All @@ -155,13 +164,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;
Expand Down Expand Up @@ -206,32 +222,30 @@ 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() });
}

tunnelProcess.stdout!.on('data', data => {
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();
Expand All @@ -242,7 +256,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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -33,6 +33,10 @@ 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 { 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',
Expand All @@ -58,7 +62,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',
}

Expand All @@ -67,7 +71,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');
}

Expand All @@ -94,10 +98,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();

Expand Down Expand Up @@ -430,20 +435,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);
}
}

Expand Down Expand Up @@ -530,7 +543,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);
}
}));

Expand All @@ -556,8 +569,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: [{
Expand All @@ -568,9 +581,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);
}

}
Expand All @@ -593,6 +607,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();

Expand All @@ -605,7 +636,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');
}
Expand Down