Skip to content

Commit 08c4ffa

Browse files
authored
recommend remote extension (microsoft#167419)
* recommend remote extension * some fixes
1 parent a127b73 commit 08c4ffa

File tree

3 files changed

+79
-3
lines changed

3 files changed

+79
-3
lines changed

src/vs/platform/remoteTunnel/common/remoteTunnel.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ export interface IRemoteTunnelService {
2525
readonly onDidChangeAccount: Event<IRemoteTunnelAccount | undefined>;
2626
updateAccount(account: IRemoteTunnelAccount | undefined): Promise<TunnelStatus>;
2727

28+
getHostName(): Promise<string | undefined>;
29+
2830
}
2931

3032
export type TunnelStatus = TunnelStates.Connected | TunnelStates.Disconnected | TunnelStates.Connecting | TunnelStates.Uninitialized;

src/vs/platform/remoteTunnel/electron-browser/remoteTunnelService.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ export class RemoteTunnelService extends Disposable implements IRemoteTunnelServ
171171
return;
172172
}
173173

174-
const hostName = this.getHostName();
174+
const hostName = this._getHostName();
175175
if (hostName) {
176176
this.setTunnelStatus(TunnelStates.connecting(localize({ key: 'remoteTunnelService.openTunnelWithName', comment: ['{0} is a host name'] }, 'Opening tunnel for {0}', hostName)));
177177
} else {
@@ -279,7 +279,11 @@ export class RemoteTunnelService extends Disposable implements IRemoteTunnelServ
279279
});
280280
}
281281

282-
private getHostName() {
282+
public async getHostName(): Promise<string | undefined> {
283+
return this._getHostName();
284+
}
285+
286+
private _getHostName(): string | undefined {
283287
let name = this.configurationService.getValue<string>(CONFIGURATION_KEY_HOST_NAME) || hostname();
284288
name = name.replace(/[^\w-]/g, '').substring(0, 20);
285289
return name || undefined;

src/vs/workbench/contrib/remoteTunnel/electron-sandbox/remoteTunnel.contribution.ts

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ export const REMOTE_TUNNEL_CONNECTION_STATE = new RawContextKey<CONTEXT_KEY_STAT
5151

5252
const SESSION_ID_STORAGE_KEY = 'remoteTunnelAccountPreference';
5353

54+
const REMOTE_TUNNEL_USED_STORAGE_KEY = 'remoteTunnelServiceUsed';
55+
const REMOTE_TUNNEL_EXTENSION_RECOMMENDED_KEY = 'remoteTunnelExtensionRecommended';
56+
5457
type ExistingSessionItem = { session: AuthenticationSession; providerId: string; label: string; description: string };
5558
type IAuthenticationProvider = { id: string; scopes: string[] };
5659
type AuthenticationProviderOption = IQuickPickItem & { provider: IAuthenticationProvider };
@@ -103,6 +106,7 @@ export class RemoteTunnelWorkbenchContribution extends Disposable implements IWo
103106
@ICommandService private commandService: ICommandService,
104107
@IWorkspaceContextService private workspaceContextService: IWorkspaceContextService,
105108
@IProgressService private progressService: IProgressService,
109+
@INotificationService private notificationService: INotificationService
106110
) {
107111
super();
108112

@@ -148,6 +152,8 @@ export class RemoteTunnelWorkbenchContribution extends Disposable implements IWo
148152
registerLogChannel(LOG_CHANNEL_ID, localize('remoteTunnelLog', "Remote Tunnel Service"), remoteTunnelServiceLogResource, fileService, logService);
149153

150154
this.initialize();
155+
156+
this.recommendRemoteExtensionIfNeeded();
151157
}
152158

153159
private get existingSessionId() {
@@ -163,6 +169,68 @@ export class RemoteTunnelWorkbenchContribution extends Disposable implements IWo
163169
}
164170
}
165171

172+
private async recommendRemoteExtensionIfNeeded() {
173+
const remoteExtension = this.serverConfiguration.extension;
174+
const shouldRecommend = async () => {
175+
if (this.storageService.getBoolean(REMOTE_TUNNEL_EXTENSION_RECOMMENDED_KEY, StorageScope.APPLICATION)) {
176+
return false;
177+
}
178+
if (await this.extensionService.getExtension(remoteExtension.extensionId)) {
179+
return false;
180+
}
181+
const usedOnHost = this.storageService.get(REMOTE_TUNNEL_USED_STORAGE_KEY, StorageScope.APPLICATION);
182+
if (!usedOnHost) {
183+
return false;
184+
}
185+
const currentHostName = await this.remoteTunnelService.getHostName();
186+
if (!currentHostName || currentHostName === usedOnHost) {
187+
return false;
188+
}
189+
return usedOnHost;
190+
};
191+
const recommed = async () => {
192+
const usedOnHost = await shouldRecommend();
193+
if (!usedOnHost) {
194+
return false;
195+
}
196+
this.notificationService.notify({
197+
severity: Severity.Info,
198+
message:
199+
localize(
200+
{
201+
key: 'recommend.remoteExtension',
202+
comment: ['{0} will be a host name, {1} will the link address to the web UI, {6} an extension name. [label](command:commandId) is a markdown link. Only translate the label, do not modify the format']
203+
},
204+
"'{0}' has turned on remote access. The {1} extension can be used to connect to it.",
205+
usedOnHost, remoteExtension.friendlyName
206+
),
207+
actions: {
208+
primary: [
209+
new Action('showExtension', localize('action.showExtension', "Show Extension"), undefined, true, () => {
210+
return this.commandService.executeCommand('workbench.extensions.action.showExtensionsWithIds', [remoteExtension.extensionId]);
211+
}),
212+
new Action('doNotShowAgain', localize('action.doNotShowAgain', "Do not show again"), undefined, true, () => {
213+
this.storageService.store(REMOTE_TUNNEL_EXTENSION_RECOMMENDED_KEY, true, StorageScope.APPLICATION, StorageTarget.USER);
214+
}),
215+
]
216+
}
217+
});
218+
return true;
219+
};
220+
if (await shouldRecommend()) {
221+
const storageListener = this.storageService.onDidChangeValue(async e => {
222+
if (e.key === REMOTE_TUNNEL_USED_STORAGE_KEY) {
223+
const success = await recommed();
224+
if (success) {
225+
storageListener.dispose();
226+
}
227+
228+
}
229+
});
230+
}
231+
}
232+
233+
166234
private async initialize(): Promise<void> {
167235
const status = await this.remoteTunnelService.getTunnelStatus();
168236
if (status.type === 'connected') {
@@ -418,6 +486,7 @@ export class RemoteTunnelWorkbenchContribution extends Disposable implements IWo
418486
const notificationService = accessor.get(INotificationService);
419487
const clipboardService = accessor.get(IClipboardService);
420488
const commandService = accessor.get(ICommandService);
489+
const storageService = accessor.get(IStorageService);
421490

422491
const connectionInfo = await that.startTunnel(false);
423492
if (connectionInfo) {
@@ -443,6 +512,7 @@ export class RemoteTunnelWorkbenchContribution extends Disposable implements IWo
443512
]
444513
}
445514
});
515+
storageService.store(REMOTE_TUNNEL_USED_STORAGE_KEY, connectionInfo.hostName, StorageScope.APPLICATION, StorageTarget.USER);
446516
} else {
447517
await notificationService.notify({
448518
severity: Severity.Info,
@@ -667,7 +737,7 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).regis
667737
[CONFIGURATION_KEY_HOST_NAME]: {
668738
description: localize('remoteTunnelAccess.machineName', "The name under which the remote tunnel access is registered. If not set, the host name is used."),
669739
type: 'string',
670-
scope: ConfigurationScope.APPLICATION,
740+
scope: ConfigurationScope.MACHINE,
671741
pattern: '^[\\w-]*$',
672742
patternErrorMessage: localize('remoteTunnelAccess.machineNameRegex', "The name can only consist of letters, numbers, underscore and minus."),
673743
maxLength: 20,

0 commit comments

Comments
 (0)