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
2 changes: 2 additions & 0 deletions extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
"@eslint/js": "^9.27.0",
"@types/mocha": "^10.0.10",
"@types/node": "20.x",
"@types/node-forge": "^1.3.11",
"@types/sinon": "^17.0.4",
"@types/vscode": "^1.98.0",
"@typescript-eslint/eslint-plugin": "^8.28.0",
Expand All @@ -95,6 +96,7 @@
},
"dependencies": {
"@vscode/vsce": "^3.3.2",
"node-forge": "^1.3.1",
"sinon": "^20.0.0",
"ts-node": "^10.9.2",
"vscode-jsonrpc": "^8.2.1",
Expand Down
3 changes: 2 additions & 1 deletion extension/package.nls.cs.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@
"aspire-vscode.strings.aspireHostingSdkVersion": "Aspire Hosting SDK Version: {0}",
"aspire-vscode.strings.aspireCliVersion": "Aspire CLI Version: {0}",
"aspire-vscode.strings.requiredCapability": "Required Capability: {0}",
"aspire-vscode.strings.aspireTerminalName": "Aspire Terminal"
"aspire-vscode.strings.aspireTerminalName": "Aspire Terminal",
"aspire-vscode.strings.rpcServerError": "RPC Server error: {0}"
}
3 changes: 2 additions & 1 deletion extension/package.nls.de.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@
"aspire-vscode.strings.aspireHostingSdkVersion": "Aspire Hosting SDK Version: {0}",
"aspire-vscode.strings.aspireCliVersion": "Aspire CLI Version: {0}",
"aspire-vscode.strings.requiredCapability": "Required Capability: {0}",
"aspire-vscode.strings.aspireTerminalName": "Aspire Terminal"
"aspire-vscode.strings.aspireTerminalName": "Aspire Terminal",
"aspire-vscode.strings.rpcServerError": "RPC Server error: {0}"
}
3 changes: 2 additions & 1 deletion extension/package.nls.es.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@
"aspire-vscode.strings.aspireHostingSdkVersion": "Aspire Hosting SDK Version: {0}",
"aspire-vscode.strings.aspireCliVersion": "Aspire CLI Version: {0}",
"aspire-vscode.strings.requiredCapability": "Required Capability: {0}",
"aspire-vscode.strings.aspireTerminalName": "Aspire Terminal"
"aspire-vscode.strings.aspireTerminalName": "Aspire Terminal",
"aspire-vscode.strings.rpcServerError": "RPC Server error: {0}"
}
3 changes: 2 additions & 1 deletion extension/package.nls.fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@
"aspire-vscode.strings.aspireHostingSdkVersion": "Aspire Hosting SDK Version: {0}",
"aspire-vscode.strings.aspireCliVersion": "Aspire CLI Version: {0}",
"aspire-vscode.strings.requiredCapability": "Required Capability: {0}",
"aspire-vscode.strings.aspireTerminalName": "Aspire Terminal"
"aspire-vscode.strings.aspireTerminalName": "Aspire Terminal",
"aspire-vscode.strings.rpcServerError": "RPC Server error: {0}"
}
3 changes: 2 additions & 1 deletion extension/package.nls.it.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@
"aspire-vscode.strings.aspireHostingSdkVersion": "Aspire Hosting SDK Version: {0}",
"aspire-vscode.strings.aspireCliVersion": "Aspire CLI Version: {0}",
"aspire-vscode.strings.requiredCapability": "Required Capability: {0}",
"aspire-vscode.strings.aspireTerminalName": "Aspire Terminal"
"aspire-vscode.strings.aspireTerminalName": "Aspire Terminal",
"aspire-vscode.strings.rpcServerError": "RPC Server error: {0}"
}
3 changes: 2 additions & 1 deletion extension/package.nls.ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@
"aspire-vscode.strings.aspireHostingSdkVersion": "Aspire Hosting SDK Version: {0}",
"aspire-vscode.strings.aspireCliVersion": "Aspire CLI Version: {0}",
"aspire-vscode.strings.requiredCapability": "Required Capability: {0}",
"aspire-vscode.strings.aspireTerminalName": "Aspire Terminal"
"aspire-vscode.strings.aspireTerminalName": "Aspire Terminal",
"aspire-vscode.strings.rpcServerError": "RPC Server error: {0}"
}
3 changes: 2 additions & 1 deletion extension/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@
"aspire-vscode.strings.aspireHostingSdkVersion": "Aspire Hosting SDK Version: {0}",
"aspire-vscode.strings.aspireCliVersion": "Aspire CLI Version: {0}",
"aspire-vscode.strings.requiredCapability": "Required Capability: {0}",
"aspire-vscode.strings.aspireTerminalName": "Aspire Terminal"
"aspire-vscode.strings.aspireTerminalName": "Aspire Terminal",
"aspire-vscode.strings.rpcServerError": "RPC Server error: {0}"
}
3 changes: 2 additions & 1 deletion extension/package.nls.ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@
"aspire-vscode.strings.aspireHostingSdkVersion": "Aspire Hosting SDK Version: {0}",
"aspire-vscode.strings.aspireCliVersion": "Aspire CLI Version: {0}",
"aspire-vscode.strings.requiredCapability": "Required Capability: {0}",
"aspire-vscode.strings.aspireTerminalName": "Aspire Terminal"
"aspire-vscode.strings.aspireTerminalName": "Aspire Terminal",
"aspire-vscode.strings.rpcServerError": "RPC Server error: {0}"
}
3 changes: 2 additions & 1 deletion extension/package.nls.pl.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@
"aspire-vscode.strings.aspireHostingSdkVersion": "Aspire Hosting SDK Version: {0}",
"aspire-vscode.strings.aspireCliVersion": "Aspire CLI Version: {0}",
"aspire-vscode.strings.requiredCapability": "Required Capability: {0}",
"aspire-vscode.strings.aspireTerminalName": "Aspire Terminal"
"aspire-vscode.strings.aspireTerminalName": "Aspire Terminal",
"aspire-vscode.strings.rpcServerError": "RPC Server error: {0}"
}
3 changes: 2 additions & 1 deletion extension/package.nls.pt-br.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@
"aspire-vscode.strings.aspireHostingSdkVersion": "Aspire Hosting SDK Version: {0}",
"aspire-vscode.strings.aspireCliVersion": "Aspire CLI Version: {0}",
"aspire-vscode.strings.requiredCapability": "Required Capability: {0}",
"aspire-vscode.strings.aspireTerminalName": "Aspire Terminal"
"aspire-vscode.strings.aspireTerminalName": "Aspire Terminal",
"aspire-vscode.strings.rpcServerError": "RPC Server error: {0}"
}
3 changes: 2 additions & 1 deletion extension/package.nls.ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@
"aspire-vscode.strings.aspireHostingSdkVersion": "Aspire Hosting SDK Version: {0}",
"aspire-vscode.strings.aspireCliVersion": "Aspire CLI Version: {0}",
"aspire-vscode.strings.requiredCapability": "Required Capability: {0}",
"aspire-vscode.strings.aspireTerminalName": "Aspire Terminal"
"aspire-vscode.strings.aspireTerminalName": "Aspire Terminal",
"aspire-vscode.strings.rpcServerError": "RPC Server error: {0}"
}
3 changes: 2 additions & 1 deletion extension/package.nls.tr.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@
"aspire-vscode.strings.aspireHostingSdkVersion": "Aspire Hosting SDK Version: {0}",
"aspire-vscode.strings.aspireCliVersion": "Aspire CLI Version: {0}",
"aspire-vscode.strings.requiredCapability": "Required Capability: {0}",
"aspire-vscode.strings.aspireTerminalName": "Aspire Terminal"
"aspire-vscode.strings.aspireTerminalName": "Aspire Terminal",
"aspire-vscode.strings.rpcServerError": "RPC Server error: {0}"
}
3 changes: 2 additions & 1 deletion extension/package.nls.zh-cn.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@
"aspire-vscode.strings.aspireHostingSdkVersion": "Aspire Hosting SDK Version: {0}",
"aspire-vscode.strings.aspireCliVersion": "Aspire CLI Version: {0}",
"aspire-vscode.strings.requiredCapability": "Required Capability: {0}",
"aspire-vscode.strings.aspireTerminalName": "Aspire Terminal"
"aspire-vscode.strings.aspireTerminalName": "Aspire Terminal",
"aspire-vscode.strings.rpcServerError": "RPC Server error: {0}"
}
3 changes: 2 additions & 1 deletion extension/package.nls.zh-tw.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@
"aspire-vscode.strings.aspireHostingSdkVersion": "Aspire Hosting SDK Version: {0}",
"aspire-vscode.strings.aspireCliVersion": "Aspire CLI Version: {0}",
"aspire-vscode.strings.requiredCapability": "Required Capability: {0}",
"aspire-vscode.strings.aspireTerminalName": "Aspire Terminal"
"aspire-vscode.strings.aspireTerminalName": "Aspire Terminal",
"aspire-vscode.strings.rpcServerError": "RPC Server error: {0}"
}
5 changes: 0 additions & 5 deletions extension/src/commands/new.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import * as vscode from 'vscode';
import { getAspireTerminal } from '../utils/terminal';
import { isWorkspaceOpen } from '../utils/vsc';

export async function newCommand() {
if (!isWorkspaceOpen()) {
return;
}

const terminal = getAspireTerminal();

terminal.sendText(`aspire new`);
Expand Down
7 changes: 2 additions & 5 deletions extension/src/constants/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,12 @@ export const codespacesLink = localize('aspire-vscode.strings.codespacesLink', '
export const openAspireDashboard = localize('aspire-vscode.strings.openAspireDashboard', 'Open Aspire Dashboard');
export const noWorkspaceOpen = localize('aspire-vscode.strings.noWorkspaceOpen', 'No workspace is open. Please open a folder or workspace before running this command.');
export const failedToShowPromptEmpty = localize('aspire-vscode.strings.failedToShowPromptEmpty', 'Failed to show prompt, text was empty.');

// Activation and RPC Server Messages
export const activated = localize('aspire-vscode.strings.activated', 'Aspire Extension activated.');
export const rpcServerListening = (address: string) => localize('aspire-vscode.strings.listening', 'Aspire extension server listening on {0}', address);
export const rpcServerAddressError = localize('aspire-vscode.strings.addressError', 'Failed to get RPC server address. The extension may not function correctly.');

// Error Lines for Incompatible Version
export const rpcServerError = (err: any) => localize('aspire-vscode.strings.rpcServerError', 'RPC Server error: {0}', err);
export const incompatibleAppHostError = localize('aspire-vscode.strings.incompatibleAppHostError', 'The app host is not compatible. Consider upgrading the app host or Aspire CLI.');
export const aspireHostingSdkVersion = (version: string) => localize('aspire-vscode.strings.aspireHostingSdkVersion', 'Aspire Hosting SDK Version: {0}', version);
export const aspireCliVersion = (version: string) => localize('aspire-vscode.strings.aspireCliVersion', 'Aspire CLI Version: {0}', version);
export const requiredCapability = (capability: string) => localize('aspire-vscode.strings.requiredCapability', 'Required Capability: {0}', capability);
export const aspireTerminalName = localize('aspire-vscode.strings.aspireTerminalName', 'Aspire Terminal');
export const aspireTerminalName = localize('aspire-vscode.strings.aspireTerminalName', 'Aspire Terminal');
23 changes: 23 additions & 0 deletions extension/src/server/cert-util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import forge from 'node-forge';

export function generateSelfSignedCert(commonName: string = 'localhost') {
const pki = forge.pki;
const keys = pki.rsa.generateKeyPair(2048);
const cert = pki.createCertificate();
cert.publicKey = keys.publicKey;
cert.serialNumber = (Math.floor(Math.random() * 1e16)).toString();
cert.validity.notBefore = new Date();
cert.validity.notAfter = new Date();
cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1);

const attrs = [{ name: 'commonName', value: commonName }];
cert.setSubject(attrs);
cert.setIssuer(attrs);

cert.sign(keys.privateKey);

return {
key: pki.privateKeyToPem(keys.privateKey),
cert: pki.certificateToPem(cert)
};
}
13 changes: 12 additions & 1 deletion extension/src/server/interactionService.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { MessageConnection } from 'vscode-jsonrpc';
import * as vscode from 'vscode';
import { IOutputChannelWriter } from '../utils/vsc';
import { IOutputChannelWriter, isWorkspaceOpen } from '../utils/vsc';
import { yesLabel, noLabel, directUrl, codespacesUrl, directLink, codespacesLink, openAspireDashboard, failedToShowPromptEmpty, incompatibleAppHostError, aspireHostingSdkVersion, aspireCliVersion, requiredCapability } from '../constants/strings';
import { ICliRpcClient } from './rpcClient';
import { formatText } from '../utils/strings';
Expand All @@ -19,6 +19,7 @@ export interface IInteractionService {
displayDashboardUrls: (dashboardUrls: DashboardUrls) => Promise<void>;
displayLines: (lines: ConsoleLine[]) => void;
displayCancellationMessage: (message: string) => void;
openProject: (projectPath: string) => void;
}

type DashboardUrls = {
Expand Down Expand Up @@ -196,6 +197,15 @@ export class InteractionService implements IInteractionService {
vscode.window.showWarningMessage(formatText(message));
this._outputChannelWriter.appendLine(formatText(message));
}

openProject(projectPath: string) {
if (isWorkspaceOpen(false)) {
return;
}

const uri = vscode.Uri.file(projectPath);
vscode.commands.executeCommand('vscode.openFolder', uri, { forceNewWindow: false });
}
}

export function addInteractionServiceEndpoints(connection: MessageConnection, interactionService: IInteractionService, rpcClient: ICliRpcClient, withAuthentication: (callback: (...params: any[]) => any) => (...params: any[]) => any) {
Expand All @@ -212,4 +222,5 @@ export function addInteractionServiceEndpoints(connection: MessageConnection, in
connection.onRequest("displayDashboardUrls", withAuthentication(interactionService.displayDashboardUrls.bind(interactionService)));
connection.onRequest("displayLines", withAuthentication(interactionService.displayLines.bind(interactionService)));
connection.onRequest("displayCancellationMessage", withAuthentication(interactionService.displayCancellationMessage.bind(interactionService)));
connection.onRequest("openProject", withAuthentication(interactionService.openProject.bind(interactionService)));
}
20 changes: 15 additions & 5 deletions extension/src/server/rpcServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,25 @@ import * as net from 'net';
import * as vscode from 'vscode';
import { createMessageConnection, MessageConnection } from 'vscode-jsonrpc';
import { StreamMessageReader, StreamMessageWriter } from 'vscode-jsonrpc/node';
import { rpcServerAddressError, rpcServerListening } from '../constants/strings';
import { rpcServerAddressError, rpcServerListening, rpcServerError } from '../constants/strings';
import * as crypto from 'crypto';
import { addInteractionServiceEndpoints, IInteractionService } from './interactionService';
import { ICliRpcClient } from './rpcClient';
import { IOutputChannelWriter } from '../utils/vsc';
import * as tls from 'tls';
import { generateSelfSignedCert } from './cert-util';

export type RpcServerInformation = {
address: string;
token: string;
server: net.Server;
server: tls.Server;
dispose: () => void;
cert: string;
};

export function setupRpcServer(interactionService: (connection: MessageConnection) => IInteractionService, rpcClient: (connection: MessageConnection, token: string) => ICliRpcClient, outputChannelWriter: IOutputChannelWriter): Promise<RpcServerInformation> {
const token = generateToken();
const { key, cert } = generateSelfSignedCert();

function withAuthentication(callback: (...params: any[]) => any) {
return (...params: any[]) => {
Expand All @@ -33,7 +37,7 @@ export function setupRpcServer(interactionService: (connection: MessageConnectio
}

return new Promise<RpcServerInformation>((resolve, reject) => {
const rpcServer = net.createServer((socket) => {
const rpcServer = tls.createServer({ key, cert }, (socket) => {
const connection = createMessageConnection(
new StreamMessageReader(socket),
new StreamMessageWriter(socket)
Expand Down Expand Up @@ -61,7 +65,8 @@ export function setupRpcServer(interactionService: (connection: MessageConnectio
token: token,
server: rpcServer,
address: fullAddress,
dispose: () => disposeRpcServer(rpcServer)
dispose: () => disposeRpcServer(rpcServer),
cert: cert
});
}
else {
Expand All @@ -70,6 +75,11 @@ export function setupRpcServer(interactionService: (connection: MessageConnectio
reject(new Error(rpcServerAddressError));
}
});

rpcServer.on('error', (err) => {
outputChannelWriter.appendLine(rpcServerError(err));
reject(err);
});
});
}

Expand All @@ -80,4 +90,4 @@ function disposeRpcServer(rpcServer: net.Server) {
function generateToken(): string {
const key = crypto.randomBytes(16);
return key.toString('base64');
}
}
13 changes: 9 additions & 4 deletions extension/src/test/rpc/e2eServerTests.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as assert from 'assert';
import * as net from 'net';
import waitForExpect from 'wait-for-expect';
import * as vscode from 'vscode';
import * as tls from 'tls';

import { createMessageConnection } from 'vscode-jsonrpc';
import { StreamMessageReader, StreamMessageWriter } from 'vscode-jsonrpc/node';
Expand Down Expand Up @@ -41,9 +42,13 @@ suite('End-to-end RPC server auth tests', () => {

const rpcServerInfo = extension.exports.getRpcServerInfo() as RpcServerInformation;

// Connect as a client
const client = net.createConnection(rpcServerInfo.address.replace("localhost:", ""));
await new Promise<void>((resolve) => client.once('connect', resolve));
const port = Number(rpcServerInfo.address.replace('localhost:', ''));
const client = tls.connect({
port,
host: 'localhost',
rejectUnauthorized: false,
});
await new Promise<void>((resolve) => client.once('secureConnect', resolve));

const connection = createMessageConnection(
new StreamMessageReader(client),
Expand All @@ -53,4 +58,4 @@ suite('End-to-end RPC server auth tests', () => {
connection.listen();
return { connection, rpcServerInfo, client };
}
});
});
2 changes: 2 additions & 0 deletions extension/src/utils/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export function getAspireTerminal(): vscode.Terminal {
const env = {
...process.env,
ASPIRE_EXTENSION_ENDPOINT: rpcServerInfo.address,
ASPIRE_EXTENSION_TOKEN: rpcServerInfo.token,
ASPIRE_EXTENSION_CERT: Buffer.from(rpcServerInfo.cert, 'utf-8').toString('base64'),
ASPIRE_EXTENSION_PROMPT_ENABLED: 'true',
ASPIRE_LOCALE_OVERRIDE: vscode.env.language
};
Expand Down
4 changes: 2 additions & 2 deletions extension/src/utils/vsc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ class VSCOutputChannelWriter implements IOutputChannelWriter {

export const vscOutputChannelWriter: IOutputChannelWriter = new VSCOutputChannelWriter();

export function isWorkspaceOpen(): boolean {
export function isWorkspaceOpen(showErrorMessage: boolean = true): boolean {
const isOpen = !!vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0;
if (!isOpen) {
if (!isOpen && showErrorMessage) {
vscode.window.showErrorMessage(noWorkspaceOpen);
}

Expand Down
Loading
Loading