diff --git a/releases/vscode-deephaven-latest.vsix b/releases/vscode-deephaven-latest.vsix index bf788128..3b072d3d 100644 Binary files a/releases/vscode-deephaven-latest.vsix and b/releases/vscode-deephaven-latest.vsix differ diff --git a/src/controllers/ExtensionController.ts b/src/controllers/ExtensionController.ts index 02b1cb65..52af43b5 100644 --- a/src/controllers/ExtensionController.ts +++ b/src/controllers/ExtensionController.ts @@ -78,6 +78,7 @@ import type { ICoreClientFactory, CoreUnauthenticatedClient, ConnectionState, + WorkerURL, } from '../types'; import { ServerConnectionTreeDragAndDropController } from './ServerConnectionTreeDragAndDropController'; import { ConnectionController } from './ConnectionController'; @@ -114,7 +115,8 @@ export class ExtensionController implements Disposable { 'Congratulations, your extension "vscode-deephaven" is now active!' ); - this._outputChannel?.appendLine('Deephaven extension activated'); + const version = context.extension.packageJSON.version; + this._outputChannel?.appendLine(`Deephaven extension ${version} activated`); } readonly _context: vscode.ExtensionContext; @@ -352,12 +354,31 @@ export class ExtensionController implements Disposable { url: URL ): Promise => { assertDefined(this._coreJsApiCache, 'coreJsApiCache'); - const dhc = await this._coreJsApiCache.get(url); - const client = new dhc.CoreClient(url.toString(), { + const workerInfo = await this._serverManager?.getWorkerInfo( + url as WorkerURL + ); + const envoyPrefix = workerInfo?.envoyPrefix; + const urlStr = String(workerInfo?.grpcUrl ?? url).replace(/\/$/, ''); + + const options: DhcType.ConnectOptions = { debug: false, // Set `debug` to true to see debug logs for gRPC transport transportFactory: NodeHttp2gRPCTransport.factory, - }) as CoreUnauthenticatedClient; + }; + + if (envoyPrefix != null) { + options.headers = { + // eslint-disable-next-line @typescript-eslint/naming-convention + 'envoy-prefix': envoyPrefix, + }; + } + + const dhc = await this._coreJsApiCache.get(url); + + const client = new dhc.CoreClient( + urlStr, + options + ) as CoreUnauthenticatedClient; // Attach a dispose method so that client caches can dispose of the client return Object.assign(client, { diff --git a/src/controllers/PanelController.ts b/src/controllers/PanelController.ts index e34d480a..74b39a57 100644 --- a/src/controllers/PanelController.ts +++ b/src/controllers/PanelController.ts @@ -5,6 +5,7 @@ import type { IServerManager, NonEmptyArray, VariableDefintion, + WorkerInfo, WorkerURL, } from '../types'; import { @@ -292,8 +293,8 @@ export class PanelController extends ControllerBase { const connection = this._serverManager.getConnection(serverUrl); assertDefined(connection, 'connection'); - const isWorkerUrl = Boolean( - await this._serverManager.getWorkerInfo(serverUrl as WorkerURL) + const workerInfo = await this._serverManager.getWorkerInfo( + serverUrl as WorkerURL ); for (const variable of variables) { @@ -315,7 +316,7 @@ export class PanelController extends ControllerBase { const iframeUrl = await getEmbedWidgetUrlForConnection( connection, title, - isWorkerUrl + workerInfo ); panel.webview.html = getPanelHtml(iframeUrl, title); @@ -347,7 +348,7 @@ export class PanelController extends ControllerBase { export async function getEmbedWidgetUrlForConnection( connection: ConnectionState, title: string, - isWorkerUrl: boolean + workerInfo?: WorkerInfo ): Promise { return getEmbedWidgetUrl({ serverUrl: connection.serverUrl, @@ -356,7 +357,8 @@ export async function getEmbedWidgetUrlForConnection( // For Core+ workers in DHE, we use `postMessage` apis for auth where DH // iframe communicates with parent (the extension) to get login credentials // from the DHE client. See `getPanelHtml` util for more details. - authProvider: isWorkerUrl ? 'parent' : undefined, + authProvider: workerInfo == null ? undefined : 'parent', + envoyPrefix: workerInfo?.envoyPrefix, psk: connection instanceof DhcService ? await connection.getPsk() : undefined, }); diff --git a/src/dh/dhc.ts b/src/dh/dhc.ts index 370d50c6..bf3b54a1 100644 --- a/src/dh/dhc.ts +++ b/src/dh/dhc.ts @@ -74,6 +74,7 @@ export async function getDhc( * @param title Widget title * @param themeKey Theme key * @param authProvider Optional auth provider + * @param envoyPrefix Optional envoy prefix for Core+ workers * @param psk Optional psk */ export function getEmbedWidgetUrl({ @@ -81,15 +82,17 @@ export function getEmbedWidgetUrl({ title, themeKey, authProvider, + envoyPrefix, psk, }: { serverUrl: URL; title: string; themeKey: string; authProvider?: 'parent'; + envoyPrefix?: string | null; psk?: string | null; }): URL { - const url = new URL('/iframe/widget', serverUrl); + const url = new URL('iframe/widget/', serverUrl); url.searchParams.set('name', title); url.searchParams.set('theme', themeKey); @@ -98,6 +101,10 @@ export function getEmbedWidgetUrl({ url.searchParams.set('authProvider', authProvider); } + if (envoyPrefix) { + url.searchParams.set('envoyPrefix', envoyPrefix); + } + if (psk) { url.searchParams.set('psk', psk); } diff --git a/src/dh/dhe.ts b/src/dh/dhe.ts index d254c1b7..5c48af80 100644 --- a/src/dh/dhe.ts +++ b/src/dh/dhe.ts @@ -10,7 +10,9 @@ import type { AuthenticatedClient as DheAuthenticatedClient } from '@deephaven-e import { hasStatusCode, loadModules } from '@deephaven/jsapi-nodejs'; import type { ConsoleType, + GrpcURL, IdeURL, + JsapiURL, QuerySerial, UniqueID, WorkerConfig, @@ -271,15 +273,22 @@ export async function getWorkerInfoFromQuery( return; } - const { grpcUrl, ideUrl, processInfoId, workerName } = queryInfo.designated; + const { envoyPrefix, grpcUrl, ideUrl, jsApiUrl, processInfoId, workerName } = + queryInfo.designated; + + const workerUrl = new URL(jsApiUrl) as WorkerURL; + workerUrl.pathname = workerUrl.pathname.replace(/jsapi\/dh-core.js$/, ''); return { tagId, serial: querySerial, - grpcUrl: new URL(grpcUrl) as WorkerURL, + envoyPrefix, + grpcUrl: new URL(grpcUrl) as GrpcURL, ideUrl: new URL(ideUrl) as IdeURL, + jsapiUrl: new URL(jsApiUrl) as JsapiURL, processInfoId, workerName, + workerUrl, }; } diff --git a/src/services/DheService.ts b/src/services/DheService.ts index dc5e805a..72b85295 100644 --- a/src/services/DheService.ts +++ b/src/services/DheService.ts @@ -2,7 +2,6 @@ import * as vscode from 'vscode'; import type { AuthenticatedClient as DheAuthenticatedClient } from '@deephaven-enterprise/auth-nodejs'; import type { EnterpriseDhType as DheType } from '@deephaven-enterprise/jsapi-types'; import { - WorkerURL, type ConsoleType, type IAsyncCacheService, type IConfigService, @@ -13,6 +12,7 @@ import { type UniqueID, type WorkerConfig, type WorkerInfo, + type WorkerURL, } from '../types'; import { URLMap } from './URLMap'; import { Logger } from '../util'; @@ -231,7 +231,7 @@ export class DheService implements IDheService { throw new Error('Failed to create worker.'); } - this._workerInfoMap.set(workerInfo.grpcUrl, workerInfo); + this._workerInfoMap.set(workerInfo.workerUrl, workerInfo); return workerInfo; }; diff --git a/src/services/ServerManager.ts b/src/services/ServerManager.ts index 1d4d8609..24ebbe0e 100644 --- a/src/services/ServerManager.ts +++ b/src/services/ServerManager.ts @@ -13,12 +13,12 @@ import type { WorkerInfo, IDheService, IAsyncCacheService, - WorkerURL, UniqueID, IToastService, CoreAuthenticatedClient, ISecretService, Psk, + WorkerURL, } from '../types'; import { getInitialServerStates, @@ -211,7 +211,7 @@ export class ServerManager implements IServerManager { // this indicates that the user cancelled the creation before it was ready. // In this case, dispose of the worker. if (!this._connectionMap.has(placeholderUrl)) { - dheService.deleteWorker(workerInfo.grpcUrl); + dheService.deleteWorker(workerInfo.workerUrl); this._onDidUpdate.fire(); return null; } @@ -228,11 +228,14 @@ export class ServerManager implements IServerManager { // Map the worker URL to the server URL to make things easier to dispose // later. - this._workerURLToServerURLMap.set(new URL(workerInfo.grpcUrl), serverUrl); + this._workerURLToServerURLMap.set( + new URL(workerInfo.workerUrl), + serverUrl + ); // Update the server URL to the worker url to be used below with core // connection creation. - serverUrl = new URL(workerInfo.grpcUrl); + serverUrl = new URL(workerInfo.workerUrl); } const connection = this._dhcServiceFactory.create(serverUrl, tagId); diff --git a/src/types/commonTypes.d.ts b/src/types/commonTypes.d.ts index 2b981c6c..bf5008e3 100644 --- a/src/types/commonTypes.d.ts +++ b/src/types/commonTypes.d.ts @@ -90,17 +90,22 @@ export interface ConnectionState { readonly tagId?: UniqueID; } -export type WorkerURL = Brand<'GrpcUrl', URL>; +export type GrpcURL = Brand<'GrpcURL', URL>; export type IdeURL = Brand<'IdeUrl', URL>; +export type JsapiURL = Brand<'JsapiURL', URL>; export type QuerySerial = Brand<'QuerySerial', string>; +export type WorkerURL = Brand<'WorkerURL', URL>; export interface WorkerInfo { tagId: UniqueID; - grpcUrl: WorkerURL; + envoyPrefix: string | null; + grpcUrl: GrpcURL; ideUrl: IdeURL; + jsapiUrl: JsapiURL; processInfoId: string | null; serial: QuerySerial; workerName: string | null; + workerUrl: WorkerURL; } export interface Disposable { diff --git a/src/types/serviceTypes.d.ts b/src/types/serviceTypes.d.ts index e55a6940..bdd1be11 100644 --- a/src/types/serviceTypes.d.ts +++ b/src/types/serviceTypes.d.ts @@ -8,19 +8,17 @@ import type { EventListenerT, ConnectionState, ServerState, - UnsubscribeEventListener, VariableChanges, VariableDefintion, VariableID, WorkerInfo, - WorkerURL, UniqueID, UserKeyPairs, UserLoginPreferences, CoreUnauthenticatedClient, Psk, CoreAuthenticatedClient, - NonEmptyArray, + WorkerURL, } from '../types/commonTypes'; import type { AuthenticatedClient as DheAuthenticatedClient,