Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
da42ec0
update error strings
adamint Jun 18, 2025
030a9a3
use token and gate all methods behind auth extension-side
adamint Jun 18, 2025
589ba2d
fix tests
adamint Jun 18, 2025
db5f3c3
add extension backchannel connection
adamint Jun 18, 2025
88a6bd9
fix syntax error, splat params
adamint Jun 18, 2025
48be713
get cli version from rpc target, call extension for all non-prompting…
adamint Jun 18, 2025
8184a5e
implement promptforselection to be able to show end to end command
adamint Jun 18, 2025
16746ed
update 'apphost' text in comments
adamint Jun 18, 2025
0262ab3
remove IncompatibleException
adamint Jun 18, 2025
348f7f5
fix e2e test
adamint Jun 18, 2025
50794db
use configuration instead of env variable
adamint Jun 18, 2025
c601d2e
Add WaitForConnectionAsync test
adamint Jun 18, 2025
86bd4b9
Catch ExtensionInputCanceledException when input is not provided, add…
adamint Jun 18, 2025
7dc02e0
Merge branch 'main' into dev/adamint/extension-interaction-service
adamint Jun 23, 2025
5334569
remove ExtensionInputCanceledException, replace with OperationCancele…
adamint Jun 23, 2025
8849f08
remove extension backchannel connector
adamint Jun 23, 2025
2582e8e
remove WaitForConnectionAsync, just connect on backchannel method inv…
adamint Jun 23, 2025
9ecc00e
Use generated certificate (bidirectional communication)
adamint Jun 23, 2025
d4d7136
remove comment
adamint Jun 23, 2025
44833f6
commit cert generator
adamint Jun 23, 2025
ab778f9
remove debugger.isattached wait
adamint Jun 23, 2025
6f26579
allow aspire new command outside a workspace
adamint Jun 23, 2025
b511c15
return TemplateResult from ApplyTemplateAsync to allow additional fields
adamint Jun 23, 2025
a6f868c
Add OpenNewProject method to interaction service, implement in extension
adamint Jun 23, 2025
8253679
rename RemoveFormatting to RemoveSpectreFormatting for clarity
adamint Jun 23, 2025
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
33 changes: 15 additions & 18 deletions extension/src/server/interactionService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export class InteractionService implements IInteractionService {
ignoreFocusOut: true
});

return selected || null;
return selected ?? null;
}

displayIncompatibleVersionError(requiredCapability: string, appHostHostingSdkVersion: string) {
Expand Down Expand Up @@ -196,21 +196,18 @@ export class InteractionService implements IInteractionService {
}
}

export function addInteractionServiceEndpoints(connection: MessageConnection, interactionService: IInteractionService, rpcClient: ICliRpcClient) {
connection.onRequest("showStatus", interactionService.showStatus.bind(interactionService));
connection.onRequest("promptForString", (promptText: string, defaultValue: string | null) =>
interactionService.promptForString(promptText, defaultValue, rpcClient));
connection.onRequest("confirm", interactionService.confirm.bind(interactionService));
connection.onRequest("promptForSelection", (promptText: string, choices: string[]) =>
interactionService.promptForSelection(promptText, choices)
);
connection.onRequest("displayIncompatibleVersionError", interactionService.displayIncompatibleVersionError.bind(interactionService));
connection.onRequest("displayError", interactionService.displayError.bind(interactionService));
connection.onRequest("displayMessage", interactionService.displayMessage.bind(interactionService));
connection.onRequest("displaySuccess", interactionService.displaySuccess.bind(interactionService));
connection.onRequest("displaySubtleMessage", interactionService.displaySubtleMessage.bind(interactionService));
connection.onRequest("displayEmptyLine", interactionService.displayEmptyLine.bind(interactionService));
connection.onRequest("displayDashboardUrls", interactionService.displayDashboardUrls.bind(interactionService));
connection.onRequest("displayLines", interactionService.displayLines.bind(interactionService));
connection.onRequest("displayCancellationMessage", interactionService.displayCancellationMessage.bind(interactionService));
export function addInteractionServiceEndpoints(connection: MessageConnection, interactionService: IInteractionService, rpcClient: ICliRpcClient, withAuthentication: (callback: (...params: any[]) => any) => (...params: any[]) => any) {
connection.onRequest("showStatus", withAuthentication(interactionService.showStatus.bind(interactionService)));
connection.onRequest("promptForString", withAuthentication(async (promptText: string, defaultValue: string | null) => interactionService.promptForString(promptText, defaultValue, rpcClient)));
connection.onRequest("confirm", withAuthentication(interactionService.confirm.bind(interactionService)));
connection.onRequest("promptForSelection", withAuthentication(interactionService.promptForSelection.bind(interactionService)));
connection.onRequest("displayIncompatibleVersionError", withAuthentication(interactionService.displayIncompatibleVersionError.bind(interactionService)));
connection.onRequest("displayError", withAuthentication(interactionService.displayError.bind(interactionService)));
connection.onRequest("displayMessage", withAuthentication(interactionService.displayMessage.bind(interactionService)));
connection.onRequest("displaySuccess", withAuthentication(interactionService.displaySuccess.bind(interactionService)));
connection.onRequest("displaySubtleMessage", withAuthentication( interactionService.displaySubtleMessage.bind(interactionService)));
connection.onRequest("displayEmptyLine", withAuthentication(interactionService.displayEmptyLine.bind(interactionService)));
connection.onRequest("displayDashboardUrls", withAuthentication(interactionService.displayDashboardUrls.bind(interactionService)));
connection.onRequest("displayLines", withAuthentication(interactionService.displayLines.bind(interactionService)));
connection.onRequest("displayCancellationMessage", withAuthentication(interactionService.displayCancellationMessage.bind(interactionService)));
}
19 changes: 12 additions & 7 deletions extension/src/server/rpcServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as net from 'net';
import * as vscode from 'vscode';
import { createMessageConnection, MessageConnection } from 'vscode-jsonrpc';
import { StreamMessageReader, StreamMessageWriter } from 'vscode-jsonrpc/node';
import { rpcServerListening, rpcServerAddressError } from '../constants/strings';
import { rpcServerAddressError } from '../constants/strings';
import * as crypto from 'crypto';
import { addInteractionServiceEndpoints, IInteractionService } from './interactionService';
import { ICliRpcClient } from './rpcClient';
Expand All @@ -18,12 +18,17 @@ export type RpcServerInformation = {
export function setupRpcServer(interactionService: (connection: MessageConnection) => IInteractionService, rpcClient: (connection: MessageConnection, token: string) => ICliRpcClient, outputChannelWriter: IOutputChannelWriter): Promise<RpcServerInformation> {
const token = generateToken();

function withAuthentication(callback: (params: any) => any) {
return (params: any) => {
if (!params || params.token !== token) {
function withAuthentication(callback: (...params: any[]) => any) {
return (...params: any[]) => {
if (!params || params[0] !== token) {
throw new Error('Invalid token provided');
}
return callback(params);

if (Array.isArray(params)) {
(params as any[]).shift();
}

return callback(...params);
};
}

Expand All @@ -42,15 +47,15 @@ export function setupRpcServer(interactionService: (connection: MessageConnectio
return ["baseline.v1"];
}));

addInteractionServiceEndpoints(connection, interactionService(connection), rpcClient(connection, token));
addInteractionServiceEndpoints(connection, interactionService(connection), rpcClient(connection, token), withAuthentication);

connection.listen();
});

rpcServer.listen(0, () => {
const addressInfo = rpcServer?.address();
if (typeof addressInfo === 'object' && addressInfo?.port) {
const fullAddress = (addressInfo.address === "::" ? "" : `${addressInfo.address}:`) + addressInfo.port;
const fullAddress = (addressInfo.address === "::" ? "localhost" : `${addressInfo.address}`) + ":" + addressInfo.port;
outputChannelWriter.appendLine(`Aspire extension server listening on: ${fullAddress}`);
resolve({
token: token,
Expand Down
2 changes: 1 addition & 1 deletion extension/src/test/rpc/e2eServerTests.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ 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);
const client = net.createConnection(rpcServerInfo.address.replace('localhost:', ''));
await new Promise<void>((resolve) => client.once('connect', resolve));

const connection = createMessageConnection(
Expand Down
1 change: 1 addition & 0 deletions extension/src/utils/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export function getAspireTerminal(): vscode.Terminal {
const env = {
...process.env,
ASPIRE_EXTENSION_ENDPOINT: rpcServerInfo.address,
ASPIRE_EXTENSION_TOKEN: rpcServerInfo.token,
ASPIRE_EXTENSION_PROMPT_ENABLED: 'true',
ASPIRE_LOCALE_OVERRIDE: vscode.env.language
};
Expand Down
5 changes: 1 addition & 4 deletions src/Aspire.Cli/Backchannel/AppHostIncompatibleException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,4 @@

namespace Aspire.Cli.Backchannel;

internal sealed class AppHostIncompatibleException(string message, string requiredCapability) : Exception(message)
{
public string RequiredCapability { get; } = requiredCapability;
}
internal sealed class AppHostIncompatibleException(string message, string requiredCapability) : IncompatibleException(message, requiredCapability);
Loading
Loading