From 78c8df0904c7d7314b7c7b4c04c0ed2f57e992c2 Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Tue, 18 Feb 2025 07:53:52 +0530 Subject: [PATCH] Not wroking code --- package-lock.json | 33 - src/Explorer/Explorer.tsx | 215 +++--- src/Terminal/JupyterLabAppFactory.ts | 992 ++++++++++++++------------- src/Terminal/index.ts | 35 +- 4 files changed, 653 insertions(+), 622 deletions(-) diff --git a/package-lock.json b/package-lock.json index f03d00e7e..5c3aadb5e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14042,17 +14042,6 @@ "version": "1.12.0", "license": "MIT" }, - "node_modules/axios": { - "version": "1.7.9", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", - "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", - "dev": true, - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, "node_modules/babel-core": { "version": "7.0.0-bridge.0", "dev": true, @@ -31155,12 +31144,6 @@ "node": ">= 0.10" } }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true - }, "node_modules/prr": { "version": "1.0.1", "dev": true, @@ -46813,16 +46796,6 @@ "aws4": { "version": "1.12.0" }, - "axios": { - "version": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", - "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", - "dev": true, - "requires": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, "babel-core": { "version": "7.0.0-bridge.0", "dev": true, @@ -58547,12 +58520,6 @@ } } }, - "proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true - }, "prr": { "version": "1.0.1", "dev": true, diff --git a/src/Explorer/Explorer.tsx b/src/Explorer/Explorer.tsx index fdef1076e..e0b5b2c38 100644 --- a/src/Explorer/Explorer.tsx +++ b/src/Explorer/Explorer.tsx @@ -1,5 +1,6 @@ import * as msal from "@azure/msal-browser"; import { Link } from "@fluentui/react/lib/Link"; +import { Panel, Widget } from "@phosphor/widgets"; import { isPublicInternetAccessAllowed } from "Common/DatabaseAccountUtility"; import { Environment, getEnvironment } from "Common/EnvironmentUtility"; import { sendMessage } from "Common/MessageHandler"; @@ -17,18 +18,19 @@ import { useQueryCopilot } from "hooks/useQueryCopilot"; import * as ko from "knockout"; import React from "react"; import _ from "underscore"; +import { Terminal as XTerminal } from 'xterm'; import shallow from "zustand/shallow"; import { AuthType } from "../AuthType"; import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer"; import * as Constants from "../Common/Constants"; -import { Areas, ConnectionStatusType, HttpStatusCodes, Notebook, PoolIdType } from "../Common/Constants"; +import { ConnectionStatusType, Notebook, PoolIdType } from "../Common/Constants"; import { getErrorMessage, getErrorStack, handleError } from "../Common/ErrorHandlingUtils"; import * as Logger from "../Common/Logger"; import { QueriesClient } from "../Common/QueriesClient"; import { readCollection, readSampleCollection } from "../Common/dataAccess/readCollection"; import { readDatabases } from "../Common/dataAccess/readDatabases"; import * as DataModels from "../Contracts/DataModels"; -import { ContainerConnectionInfo, IPhoenixServiceInfo, IProvisionData, IResponse } from "../Contracts/DataModels"; +import { IPhoenixServiceInfo, IResponse } from "../Contracts/DataModels"; import * as ViewModels from "../Contracts/ViewModels"; import { GitHubOAuthService } from "../GitHub/GitHubOAuthService"; import { PhoenixClient } from "../Phoenix/PhoenixClient"; @@ -377,87 +379,87 @@ export default class Explorer { } public async allocateContainer(poolId: PoolIdType, mode?: string): Promise { - const shouldUseNotebookStates = poolId === PoolIdType.DefaultPoolId ? true : false; - const notebookServerInfo = shouldUseNotebookStates - ? useNotebook.getState().notebookServerInfo - : useQueryCopilot.getState().notebookServerInfo; + // const shouldUseNotebookStates = poolId === PoolIdType.DefaultPoolId ? true : false; + // const notebookServerInfo = shouldUseNotebookStates + // ? useNotebook.getState().notebookServerInfo + // : useQueryCopilot.getState().notebookServerInfo; - const isAllocating = shouldUseNotebookStates - ? useNotebook.getState().isAllocating - : useQueryCopilot.getState().isAllocatingContainer; - if ( - isAllocating === false && - (notebookServerInfo === undefined || - (notebookServerInfo && notebookServerInfo.notebookServerEndpoint === undefined)) - ) { - const connectionStatus: ContainerConnectionInfo = { - status: ConnectionStatusType.Connecting, - }; + // const isAllocating = shouldUseNotebookStates + // ? useNotebook.getState().isAllocating + // : useQueryCopilot.getState().isAllocatingContainer; + // if ( + // isAllocating === false && + // (notebookServerInfo === undefined || + // (notebookServerInfo && notebookServerInfo.notebookServerEndpoint === undefined)) + // ) { + // const connectionStatus: ContainerConnectionInfo = { + // status: ConnectionStatusType.Connecting, + // }; - shouldUseNotebookStates && useNotebook.getState().setConnectionInfo(connectionStatus); + // shouldUseNotebookStates && useNotebook.getState().setConnectionInfo(connectionStatus); - let connectionInfo; - let provisionData: IProvisionData; - try { - TelemetryProcessor.traceStart(Action.PhoenixConnection, { - dataExplorerArea: Areas.Notebook, - }); - if (shouldUseNotebookStates) { - useNotebook.getState().setIsAllocating(true); - provisionData = { - cosmosEndpoint: userContext?.databaseAccount?.properties?.documentEndpoint, - poolId: undefined, - }; - } else { - useQueryCopilot.getState().setIsAllocatingContainer(true); - provisionData = { - poolId: poolId, - databaseId: useTabs.getState().activeTab.collection.databaseId, - containerId: useTabs.getState().activeTab.collection.id(), - mode: mode, - }; - } - connectionInfo = await this.phoenixClient.allocateContainer(provisionData); - if (!connectionInfo?.data?.phoenixServiceUrl) { - throw new Error(`PhoenixServiceUrl is invalid!`); - } - await this.setNotebookInfo(shouldUseNotebookStates, connectionInfo, connectionStatus); - TelemetryProcessor.traceSuccess(Action.PhoenixConnection, { - dataExplorerArea: Areas.Notebook, - }); - } catch (error) { - TelemetryProcessor.traceFailure(Action.PhoenixConnection, { - dataExplorerArea: Areas.Notebook, - status: error.status, - error: getErrorMessage(error), - errorStack: getErrorStack(error), - }); - if (shouldUseNotebookStates) { - connectionStatus.status = ConnectionStatusType.Failed; - shouldUseNotebookStates - ? useNotebook.getState().resetContainerConnection(connectionStatus) - : useQueryCopilot.getState().resetContainerConnection(); - if (error?.status === HttpStatusCodes.Forbidden && error.message) { - useDialog.getState().showOkModalDialog("Connection Failed", `${error.message}`); - } else { - useDialog - .getState() - .showOkModalDialog( - "Connection Failed", - "We are unable to connect to the temporary workspace. Please try again in a few minutes. If the error persists, file a support ticket.", - ); - } - } - throw error; - } finally { - shouldUseNotebookStates - ? useNotebook.getState().setIsAllocating(false) - : useQueryCopilot.getState().setIsAllocatingContainer(false); - this.refreshCommandBarButtons(); - this.refreshNotebookList(); - this._isInitializingNotebooks = false; - } - } + // let connectionInfo; + // let provisionData: IProvisionData; + // try { + // TelemetryProcessor.traceStart(Action.PhoenixConnection, { + // dataExplorerArea: Areas.Notebook, + // }); + // if (shouldUseNotebookStates) { + // useNotebook.getState().setIsAllocating(true); + // provisionData = { + // cosmosEndpoint: userContext?.databaseAccount?.properties?.documentEndpoint, + // poolId: undefined, + // }; + // } else { + // useQueryCopilot.getState().setIsAllocatingContainer(true); + // provisionData = { + // poolId: poolId, + // databaseId: useTabs.getState().activeTab.collection.databaseId, + // containerId: useTabs.getState().activeTab.collection.id(), + // mode: mode, + // }; + // } + // // connectionInfo = await this.phoenixClient.allocateContainer(provisionData); + // // if (!connectionInfo?.data?.phoenixServiceUrl) { + // // throw new Error(`PhoenixServiceUrl is invalid!`); + // // } + // // await this.setNotebookInfo(shouldUseNotebookStates, connectionInfo, connectionStatus); + // // TelemetryProcessor.traceSuccess(Action.PhoenixConnection, { + // // dataExplorerArea: Areas.Notebook, + // // }); + // } catch (error) { + // TelemetryProcessor.traceFailure(Action.PhoenixConnection, { + // dataExplorerArea: Areas.Notebook, + // status: error.status, + // error: getErrorMessage(error), + // errorStack: getErrorStack(error), + // }); + // if (shouldUseNotebookStates) { + // connectionStatus.status = ConnectionStatusType.Failed; + // shouldUseNotebookStates + // ? useNotebook.getState().resetContainerConnection(connectionStatus) + // : useQueryCopilot.getState().resetContainerConnection(); + // if (error?.status === HttpStatusCodes.Forbidden && error.message) { + // useDialog.getState().showOkModalDialog("Connection Failed", `${error.message}`); + // } else { + // useDialog + // .getState() + // .showOkModalDialog( + // "Connection Failed", + // "We are unable to connect to the temporary workspace. Please try again in a few minutes. If the error persists, file a support ticket.", + // ); + // } + // } + // throw error; + // } finally { + // shouldUseNotebookStates + // ? useNotebook.getState().setIsAllocating(false) + // : useQueryCopilot.getState().setIsAllocatingContainer(false); + // this.refreshCommandBarButtons(); + // this.refreshNotebookList(); + // this._isInitializingNotebooks = false; + // } + // } } public async setNotebookInfo( @@ -907,19 +909,19 @@ export default class Explorer { public async openNotebookTerminal(kind: ViewModels.TerminalKind): Promise { if (useNotebook.getState().isPhoenixFeatures) { - await this.allocateContainer(PoolIdType.DefaultPoolId); - const notebookServerInfo = useNotebook.getState().notebookServerInfo; - if (notebookServerInfo && notebookServerInfo.notebookServerEndpoint !== undefined) { - this.connectToNotebookTerminal(kind); - } else { - useDialog - .getState() - .showOkModalDialog( - "Failed to connect", - "Failed to connect to temporary workspace. This could happen because of network issues. Please refresh the page and try again.", - ); - } - } else { + // //await this.allocateContainer(PoolIdType.DefaultPoolId); + // //const notebookServerInfo = useNotebook.getState().notebookServerInfo; + // // if (notebookServerInfo && notebookServerInfo.notebookServerEndpoint !== undefined) { + // this.connectToNotebookTerminal(kind); + // // } else { + // // useDialog + // // .getState() + // // .showOkModalDialog( + // // "Failed to connect", + // // "Failed to connect to temporary workspace. This could happen because of network issues. Please refresh the page and try again.", + // // ); + // // } + // } else { this.connectToNotebookTerminal(kind); } } @@ -972,6 +974,33 @@ export default class Explorer { }); useTabs.getState().activateNewTab(newTab); + + const xterminal = new XTerminal(); + + // Ensure newTab.node is set to a valid HTMLDivElement + if (!newTab.node || !(newTab.node instanceof HTMLDivElement)) { + newTab.node = document.createElement('div'); // Create a div if not already set + } + + // Ensure the container is an HTMLDivElement + const termContainer = newTab.node as unknown as HTMLDivElement; + termContainer.style.width = '100%'; + termContainer.style.height = '100%'; + + // Append the container to the tab system if necessary + document.body.appendChild(termContainer); // Optional: Add it to the DOM if needed + + // Attach xterm.js to the correct container + xterminal.open(termContainer); + + const termWidget = new Widget(); + termWidget.node.appendChild(termContainer); + + const panel = new Panel(); + panel.addWidget(termWidget); + + // Write to the terminal + xterminal.write('I am new shell'); } public async openGallery( diff --git a/src/Terminal/JupyterLabAppFactory.ts b/src/Terminal/JupyterLabAppFactory.ts index e50197e74..141eed17c 100644 --- a/src/Terminal/JupyterLabAppFactory.ts +++ b/src/Terminal/JupyterLabAppFactory.ts @@ -1,18 +1,14 @@ /** * JupyterLab applications based on jupyterLab components */ -import { ServerConnection, TerminalManager } from "@jupyterlab/services"; -import { IMessage, ITerminalConnection } from "@jupyterlab/services/lib/terminal/terminal"; -import { Terminal } from "@jupyterlab/terminal"; +import { ServerConnection } from "@jupyterlab/services"; import { Panel, Widget } from "@phosphor/widgets"; -//import { useMemo, useRef } from 'react'; +import { useRef } from 'react'; import { userContext } from "UserContext"; -//import { v4 as uuidv4 } from 'uuid'; -//import { Terminal as XTerminal } from 'xterm'; +import { v4 as uuidv4 } from 'uuid'; +import { Terminal as XTerminal } from 'xterm'; import { useAADAuth } from "../hooks/useAADAuth"; -const { armToken } = useAADAuth(); - export class JupyterLabAppFactory { private isShellStarted: boolean | undefined; private checkShellStarted: ((content: string | undefined) => void) | undefined; @@ -64,53 +60,66 @@ export class JupyterLabAppFactory { } } - public async createTerminalApp(serverSettings: ServerConnection.ISettings): Promise { - const configurationSettings: Partial = serverSettings; - (configurationSettings.appendToken as boolean) = false; - serverSettings = ServerConnection.makeSettings(configurationSettings); - const manager = new TerminalManager({ - serverSettings: serverSettings, + public async createTerminalApp(serverSettings: ServerConnection.ISettings): Promise { + // const configurationSettings: Partial = serverSettings; + // (configurationSettings.appendToken as boolean) = false; + // serverSettings = ServerConnection.makeSettings(configurationSettings); + // const manager = new TerminalManager({ + // serverSettings: serverSettings, + // }); + // const session = await manager.startNew(); + // session.messageReceived.connect(async (_, message: IMessage) => { + // const content = message.content && message.content[0]?.toString(); + + // if (this.checkShellStarted && message.type == "stdout") { + // //Close the terminal tab once the shell closed messages are received + // if (!this.isShellStarted) { + // this.checkShellStarted(content); + // } else if (this.isShellExited(content)) { + // this.onShellExited(this.restartShell); + // } + // } + // }, this); + + // let internalSend = session.send; + // session.send = (message: IMessage) => { + // message?.content?.push(serverSettings?.token); + // internalSend.call(session, message); + // }; + // const term = new Terminal(session, { theme: "dark", shutdownOnClose: true }); + + // if (!term) { + // console.error("Failed starting terminal"); + // return undefined; + // } + + // term.title.closable = false; + // term.addClass("terminalWidget"); + + // let panel = new Panel(); + // panel.addWidget(term as any); + // panel.id = "main"; + + // // Attach the widget to the dom. + // Widget.attach(panel, document.body); + + // // Switch focus to the terminal + // term.activate(); + + const xterminal = new XTerminal({ + + convertEol: true }); - const session = await manager.startNew(); - session.messageReceived.connect(async (_, message: IMessage) => { - const content = message.content && message.content[0]?.toString(); - - if (this.checkShellStarted && message.type == "stdout") { - //Close the terminal tab once the shell closed messages are received - if (!this.isShellStarted) { - this.checkShellStarted(content); - } else if (this.isShellExited(content)) { - this.onShellExited(this.restartShell); - } - } - }, this); - - let internalSend = session.send; - session.send = (message: IMessage) => { - message?.content?.push(serverSettings?.token); - internalSend.call(session, message); - }; - const term = new Terminal(session, { theme: "dark", shutdownOnClose: true }); - - if (!term) { - console.error("Failed starting terminal"); - return undefined; - } - - term.title.closable = false; - term.addClass("terminalWidget"); + let panel = new Panel(); - panel.addWidget(term as any); - panel.id = "main"; + panel.addWidget(xterminal as any); + panel.id = "xMain"; - // Attach the widget to the dom. Widget.attach(panel, document.body); + xterminal.write('I am new shell'); - // Switch focus to the terminal - term.activate(); - - // this.startCloudShellterminal(panel); + this.startCloudShellterminal(xterminal); // Handle resize events. window.addEventListener("resize", () => { @@ -122,508 +131,503 @@ export class JupyterLabAppFactory { panel.dispose(); }); - return session; + return xterminal; } - // public async startCloudShellterminal(panel: Panel) - // { - // const intervalsToClearRef = useRef([]); + public async startCloudShellterminal(xterminal: XTerminal) + { + const intervalsToClearRef = useRef([]); - // const xterminal = useMemo(() => new XTerminal({ - // convertEol: true - // }), []); + const allowedParentFrameAuthorities = ["localhost:3000", "portal.azure.com", "portal.azure.us", "rc.portal.azure.com", "ms.portal.azure.com", "canary.portal.azure.com", "canary-ms.portal.azure.com", "docs.microsoft.com", "review.docs.microsoft.com", "ppe.docs.microsoft.com", "ux.console.azure.us", "admin-local.teams.microsoft.net", "admin-ignite.microsoft.com", "wusportalprv.office.com", "portal-sdf.office.com", "ncuportalprv.office.com", "admin.microsoft.com", "portal.microsoft.com", "portal.office.com", "admin.microsoft365.com", "admin-sdf.exchange.microsoft.com", "admin.exchange.microsoft.com", "cloudconsole-ux-prod-usnatwest.appservice.eaglex.ic.gov", "cloudconsole-ux-prod-usnateast.appservice.eaglex.ic.gov", "portal.azure.eaglex.ic.gov", "cloudconsole-ux-prod-ussecwest.appservice.microsoft.scloud", "cloudconsole-ux-prod-usseceast.appservice.microsoft.scloud", "portal.azure.microsoft.scloud", "admin-local.teams.microsoft.net", "admin-dev.teams.microsoft.net", "admin-int.teams.microsoft.net", "admin.teams.microsoft.com", "preview.portal.azure.com", "learn.microsoft.com", "review.learn.microsoft.com", "ppe.learn.microsoft.com", "dev.learn.microsoft.com"]; + const trustedParentOrigin = getTrustedParentOrigin(); + let trustedAuthority = (trustedParentOrigin.split("//")[1] || "").toLowerCase(); + const isTrustedOrigin = allowedParentFrameAuthorities.some(origin => origin === trustedAuthority); - // panel.addWidget(xterminal as any); - // panel.id = "xMain"; + if (!isTrustedOrigin) { + const errorMessage = "The origin '" + trustedParentOrigin + "' is not trusted."; + xterminal.writeln(''); + xterminal.writeln('errorMessage'); + throw new Error(errorMessage); + } - // xterminal.write('I am new shell'); + trustedAuthority = (trustedParentOrigin.indexOf("https") === 0 ? "https://" : "http://") + trustedAuthority; - // const allowedParentFrameAuthorities = ["localhost:3000", "portal.azure.com", "portal.azure.us", "rc.portal.azure.com", "ms.portal.azure.com", "canary.portal.azure.com", "canary-ms.portal.azure.com", "docs.microsoft.com", "review.docs.microsoft.com", "ppe.docs.microsoft.com", "ux.console.azure.us", "admin-local.teams.microsoft.net", "admin-ignite.microsoft.com", "wusportalprv.office.com", "portal-sdf.office.com", "ncuportalprv.office.com", "admin.microsoft.com", "portal.microsoft.com", "portal.office.com", "admin.microsoft365.com", "admin-sdf.exchange.microsoft.com", "admin.exchange.microsoft.com", "cloudconsole-ux-prod-usnatwest.appservice.eaglex.ic.gov", "cloudconsole-ux-prod-usnateast.appservice.eaglex.ic.gov", "portal.azure.eaglex.ic.gov", "cloudconsole-ux-prod-ussecwest.appservice.microsoft.scloud", "cloudconsole-ux-prod-usseceast.appservice.microsoft.scloud", "portal.azure.microsoft.scloud", "admin-local.teams.microsoft.net", "admin-dev.teams.microsoft.net", "admin-int.teams.microsoft.net", "admin.teams.microsoft.com", "preview.portal.azure.com", "learn.microsoft.com", "review.learn.microsoft.com", "ppe.learn.microsoft.com", "dev.learn.microsoft.com"]; - // const trustedParentOrigin = getTrustedParentOrigin(); - // let trustedAuthority = (trustedParentOrigin.split("//")[1] || "").toLowerCase(); - // const isTrustedOrigin = allowedParentFrameAuthorities.some(origin => origin === trustedAuthority); + let aadAuth = useAADAuth(); + let authorizationToken = aadAuth.armToken; + const tokenInterval = setInterval(async () => { + authorizationToken = aadAuth.armToken; + }, 1000 * 60 * 10); - // if (!isTrustedOrigin) { - // const errorMessage = "The origin '" + trustedParentOrigin + "' is not trusted."; - // xterminal.writeln(''); - // xterminal.writeln('errorMessage'); - // throw new Error(errorMessage); - // } + const intervalsToClear = intervalsToClearRef.current ?? []; + intervalsToClear.push(tokenInterval); - // trustedAuthority = (trustedParentOrigin.indexOf("https") === 0 ? "https://" : "http://") + trustedAuthority; + // validate that the subscription id is registered in the Cloudshell namespace + try { + const response: any = await verifyCloudshellProviderRegistration(userContext.subscriptionId, authorizationToken); + if (response.registrationState !== "Registered") { + await registerCloudShellProvider(userContext.subscriptionId, authorizationToken); + } + } catch (err) { + xterminal.writeln(''); + xterminal.writeln('Unable to verify cloudshell provider registration.'); + intervalsToClear.forEach((val) => window.clearInterval(+val)); + throw err; + } - // let authorizationToken = armToken; - // const tokenInterval = setInterval(async () => { - // authorizationToken = armToken; - // }, 1000 * 60 * 10); + const region = await getUserRegion(authorizationToken, userContext.subscriptionId).then((res) => { + const reqId = (res.headers as any).get("x-ms-request-id"); + const location = reqId?.split(":")?.[0]?.toLowerCase() ?? ""; + const validRegions = new Set(["westus", "southcentralus", "eastus", "northeurope", "westeurope", "centralindia", "southeastasia", "westcentralus", "eastus2euap", "centraluseuap"]); + if (validRegions.has(location)) { + return location; + } + if (location === "centralus") { + return "centraluseuap"; + } + if (location === "eastus2") { + return "eastus2euap"; + } + return "westus"; + }).catch((err) => { + xterminal.writeln(''); + xterminal.writeln('Unable to get user region.'); + return "westus"; + }); - // const intervalsToClear = intervalsToClearRef.current ?? []; - // intervalsToClear.push(tokenInterval); + try { + // do not use the subscription from the preferred settings use the one from the context + await putEphemeralUserSettings(userContext.subscriptionId, region, authorizationToken); + } catch (err) { + xterminal.writeln(''); + xterminal.writeln('Unable to update user settings to ephemeral session.'); + intervalsToClear.forEach(val => window.clearInterval(+val)); + throw err; + } - // // validate that the subscription id is registered in the Cloudshell namespace - // try { - // const response: any = await verifyCloudshellProviderRegistration(userContext.subscriptionId, authorizationToken); - // if (response.registrationState !== "Registered") { - // await registerCloudShellProvider(userContext.subscriptionId, authorizationToken); - // } - // } catch (err) { - // xterminal.writeln(''); - // xterminal.writeln('Unable to verify cloudshell provider registration.'); - // intervalsToClear.forEach((val) => window.clearInterval(+val)); - // throw err; - // } + // verify user settings after they have been updated to ephemeral + try { + const userSettings = await getUserSettings(authorizationToken); + const isValidUserSettings = this.validateUserSettings(userSettings); + if (!isValidUserSettings) { + throw new Error("Invalid user settings detected for ephemeral session."); + } + } catch (err) { + xterminal.writeln(''); + xterminal.writeln('Unable to verify user settings for ephemeral session.'); + //xterminal.forEach((val) => window.clearInterval(+val)); + throw err; + } - // const region = await getUserRegion(authorizationToken, userContext.subscriptionId).then((res) => { - // const reqId = (res.headers as any).get("x-ms-request-id"); - // const location = reqId?.split(":")?.[0]?.toLowerCase() ?? ""; - // const validRegions = new Set(["westus", "southcentralus", "eastus", "northeurope", "westeurope", "centralindia", "southeastasia", "westcentralus", "eastus2euap", "centraluseuap"]); - // if (validRegions.has(location)) { - // return location; - // } - // if (location === "centralus") { - // return "centraluseuap"; - // } - // if (location === "eastus2") { - // return "eastus2euap"; - // } - // return "westus"; - // }).catch((err) => { - // xterminal.writeln(''); - // xterminal.writeln('Unable to get user region.'); - // return "westus"; - // }); + // trigger callback to provision console internal + let provisionConsoleResponse; + try { + provisionConsoleResponse = await provisionConsole(userContext.subscriptionId, authorizationToken, region); + // statusPaneUpdateCommands.setTerminalUri(provisionConsoleResponse.properties.uri); + } catch (err) { + xterminal.writeln(''); + xterminal.writeln('Unable to provision console.'); + intervalsToClear.forEach((val) => window.clearInterval(+val)); + throw err; + } - // try { - // // do not use the subscription from the preferred settings use the one from the context - // await putEphemeralUserSettings(userContext.subscriptionId, region, authorizationToken); - // } catch (err) { - // xterminal.writeln(''); - // xterminal.writeln('Unable to update user settings to ephemeral session.'); - // intervalsToClear.forEach(val => window.clearInterval(+val)); - // throw err; - // } + if (provisionConsoleResponse.properties.provisioningState !== "Succeeded") { + intervalsToClear.forEach((val) => window.clearInterval(+val)); + xterminal.writeln("Failed to provision console."); + throw new Error("Failed to provision console."); + } - // // verify user settings after they have been updated to ephemeral - // try { - // const userSettings = await getUserSettings(authorizationToken); - // const isValidUserSettings = this.validateUserSettings(userSettings); - // if (!isValidUserSettings) { - // throw new Error("Invalid user settings detected for ephemeral session."); - // } - // } catch (err) { - // xterminal.writeln(''); - // xterminal.writeln('Unable to verify user settings for ephemeral session.'); - // //xterminal.forEach((val) => window.clearInterval(+val)); - // throw err; - // } + xterminal.writeln("Connecting to cloudshell..."); + xterminal.writeln("Please wait..."); + // connect the terminal + let connectTerminalResponse; + try { + connectTerminalResponse = await connectTerminal(provisionConsoleResponse.properties.uri, authorizationToken, { rows: xterminal.rows, cols: xterminal.cols }); + } catch (err) { + xterminal.writeln(''); + xterminal.writeln('Unable to connect terminal.'); + intervalsToClear.forEach((val) => window.clearInterval(+val)); + throw err; + } - // // trigger callback to provision console internal - // let provisionConsoleResponse; - // try { - // provisionConsoleResponse = await provisionConsole(userContext.subscriptionId, authorizationToken, region); - // // statusPaneUpdateCommands.setTerminalUri(provisionConsoleResponse.properties.uri); - // } catch (err) { - // xterminal.writeln(''); - // xterminal.writeln('Unable to provision console.'); - // intervalsToClear.forEach((val) => window.clearInterval(+val)); - // throw err; - // } - - // if (provisionConsoleResponse.properties.provisioningState !== "Succeeded") { - // intervalsToClear.forEach((val) => window.clearInterval(+val)); - // xterminal.writeln("Failed to provision console."); - // throw new Error("Failed to provision console."); - // } - - // xterminal.writeln("Connecting to cloudshell..."); - // xterminal.writeln("Please wait..."); - // // connect the terminal - // let connectTerminalResponse; - // try { - // connectTerminalResponse = await connectTerminal(provisionConsoleResponse.properties.uri, authorizationToken, { rows: xterminal.rows, cols: xterminal.cols }); - // } catch (err) { - // xterminal.writeln(''); - // xterminal.writeln('Unable to connect terminal.'); - // intervalsToClear.forEach((val) => window.clearInterval(+val)); - // throw err; - // } - - // const targetUri = provisionConsoleResponse.properties.uri + `/terminals?cols=${xterminal.cols}&rows=${xterminal.rows}&version=2019-01-01&shell=bash`; - // const termId = connectTerminalResponse.id; - // //statusPaneUpdateCommands.setTermId(termId); + const targetUri = provisionConsoleResponse.properties.uri + `/terminals?cols=${xterminal.cols}&rows=${xterminal.rows}&version=2019-01-01&shell=bash`; + const termId = connectTerminalResponse.id; + //statusPaneUpdateCommands.setTermId(termId); - // let socketUri = connectTerminalResponse.socketUri.replace(":443/", ""); - // const targetUriBody = targetUri.replace('https://', '').split('?')[0]; - // if (socketUri.indexOf(targetUriBody) === -1) { - // socketUri = 'wss://' + targetUriBody + '/' + termId; - // } - // if (targetUriBody.includes('servicebus')) { - // const targetUriBodyArr = targetUriBody.split('/'); - // socketUri = 'wss://' + targetUriBodyArr[0] + '/$hc/' + targetUriBodyArr[1] + '/terminals/' + termId; - // } + let socketUri = connectTerminalResponse.socketUri.replace(":443/", ""); + const targetUriBody = targetUri.replace('https://', '').split('?')[0]; + if (socketUri.indexOf(targetUriBody) === -1) { + socketUri = 'wss://' + targetUriBody + '/' + termId; + } + if (targetUriBody.includes('servicebus')) { + const targetUriBodyArr = targetUriBody.split('/'); + socketUri = 'wss://' + targetUriBodyArr[0] + '/$hc/' + targetUriBodyArr[1] + '/terminals/' + termId; + } - // // provision appropriate first party permissions to cloudshell instance - // await postTokens(provisionConsoleResponse.properties.uri, authorizationToken).catch((err) => { - // xterminal.writeln('Unable to provision first party permissions to cloudshell instance.'); - // intervalsToClear.forEach((val) => window.clearInterval(+val)); - // throw err; - // }); + // provision appropriate first party permissions to cloudshell instance + await postTokens(provisionConsoleResponse.properties.uri, authorizationToken).catch((err) => { + xterminal.writeln('Unable to provision first party permissions to cloudshell instance.'); + intervalsToClear.forEach((val) => window.clearInterval(+val)); + throw err; + }); - // const socket = new WebSocket(socketUri); + const socket = new WebSocket(socketUri); - // configureSocket(socket, socketUri, xterminal, intervalsToClear, 0); + configureSocket(socket, socketUri, xterminal, intervalsToClear, 0); - // // authorize the session - // try { - // const authorizeResponse = await authorizeSession(provisionConsoleResponse.properties.uri, authorizationToken); - // const cookieToken = authorizeResponse.token; - // const a = document.createElement("img"); - // a.src = targetUri + "?token=" + encodeURIComponent(cookieToken); - // } catch (err) { - // xterminal.writeln('Unable to authroize the session'); - // intervalsToClear.forEach((val) => window.clearInterval(+val)); - // socket.close(); - // throw err; - // } - // } + // authorize the session + try { + const authorizeResponse = await authorizeSession(provisionConsoleResponse.properties.uri, authorizationToken); + const cookieToken = authorizeResponse.token; + const a = document.createElement("img"); + a.src = targetUri + "?token=" + encodeURIComponent(cookieToken); + } catch (err) { + xterminal.writeln('Unable to authroize the session'); + intervalsToClear.forEach((val) => window.clearInterval(+val)); + socket.close(); + throw err; + } + } - // public validateUserSettings(userSettings: Settings) { - // if (userSettings.sessionType !== SessionType.Ephemeral && userSettings.osType !== OsType.Linux) { - // return false; - // } else { - // return true; - // } - // } + public validateUserSettings(userSettings: Settings) { + if (userSettings.sessionType !== SessionType.Ephemeral && userSettings.osType !== OsType.Linux) { + return false; + } else { + return true; + } + } } -// export const enum OsType { -// Linux = "linux", -// Windows = "windows" -// } +export const enum OsType { + Linux = "linux", + Windows = "windows" +} -// export const enum ShellType { -// Bash = "bash", -// PowerShellCore = "pwsh" -// } +export const enum ShellType { + Bash = "bash", + PowerShellCore = "pwsh" +} -// export const enum NetworkType { -// Default = "Default", -// Isolated = "Isolated" -// } +export const enum NetworkType { + Default = "Default", + Isolated = "Isolated" +} -// export const enum SessionType { -// Mounted = "Mounted", -// Ephemeral = "Ephemeral" -// } +export const enum SessionType { + Mounted = "Mounted", + Ephemeral = "Ephemeral" +} -// // https://stackoverflow.com/q/38598280 (Is it possible to wrap a function and retain its types?) -// export const trackedApiCall = , U>(apiCall: (...args: T) => Promise, name: string) => { -// return async (...args: T): Promise => { -// const startTime = Date.now(); -// const result = await apiCall(...args); -// const endTime = Date.now(); -// return result; -// }; -// }; +// https://stackoverflow.com/q/38598280 (Is it possible to wrap a function and retain its types?) +export const trackedApiCall = , U>(apiCall: (...args: T) => Promise, name: string) => { + return async (...args: T): Promise => { + const startTime = Date.now(); + const result = await apiCall(...args); + const endTime = Date.now(); + return result; + }; +}; -// export const getUserRegion = trackedApiCall(async (authToken: string, subscriptionId: string) => { -// const locale = getLocale(); -// const locationUri = getArmUri(`/subscriptions/${subscriptionId}/locations?api-version=2022-12-01`).toString(); -// return await fetch(locationUri, { -// method: "get", -// headers: { -// 'Accept': 'application/json', -// 'Content-Type': 'application/json', -// 'Authorization': authToken, -// 'Accept-Language': locale, -// 'x-ms-correlation-request-id': uuidv4(), -// } -// }); -// }, "getUserRegion"); +export const getUserRegion = trackedApiCall(async (authToken: string, subscriptionId: string) => { + const locale = getLocale(); + const locationUri = getArmUri(`/subscriptions/${subscriptionId}/locations?api-version=2022-12-01`).toString(); + return await fetch(locationUri, { + method: "get", + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'Authorization': authToken, + 'Accept-Language': locale, + 'x-ms-correlation-request-id': uuidv4(), + } + }); +}, "getUserRegion"); -// export type Settings = { -// location: string; -// sessionType: SessionType; -// osType: OsType; -// }; +export type Settings = { + location: string; + sessionType: SessionType; + osType: OsType; +}; -// export const getUserSettings = trackedApiCall(async (authToken: string): Promise => { -// // figure out how to set the Accept-Language dynamically -// const armUri = getArmUri(`/providers/Microsoft.Portal/userSettings/cloudconsole?api-version=2023-02-01-preview`).toString();; -// const locale = getLocale(); -// const resp = await fetch(armUri, { -// method: "get", -// headers: { -// 'Accept': 'application/json', -// 'Content-Type': 'application/json', -// 'Authorization': authToken, -// 'Accept-Language': locale, -// 'x-ms-correlation-request-id': uuidv4(), -// } -// }); +export const getUserSettings = trackedApiCall(async (authToken: string): Promise => { + // figure out how to set the Accept-Language dynamically + const armUri = getArmUri(`/providers/Microsoft.Portal/userSettings/cloudconsole?api-version=2023-02-01-preview`).toString();; + const locale = getLocale(); + const resp = await fetch(armUri, { + method: "get", + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'Authorization': authToken, + 'Accept-Language': locale, + 'x-ms-correlation-request-id': uuidv4(), + } + }); -// const json = await resp?.json() as any; -// return { -// location: json?.properties?.preferredLocation, -// sessionType: json?.properties?.sessionType, -// osType: json?.properties?.preferredOsType -// }; -// }, "getUserSettings"); + const json = await resp?.json() as any; + return { + location: json?.properties?.preferredLocation, + sessionType: json?.properties?.sessionType, + osType: json?.properties?.preferredOsType + }; +}, "getUserSettings"); -// export const verifyCloudshellProviderRegistration = async(subscriptionId: string, authToken: string) => { -// const targetUri = getArmUri(`/subscriptions/${subscriptionId}/providers/Microsoft.CloudShell?api-version=2022-12-01`).toString(); -// const locale = getLocale(); -// return await fetch(targetUri, { -// method: "get", -// headers: { -// 'Accept': 'application/json', -// 'Content-Type': 'application/json', -// 'Authorization': authToken, -// 'Accept-Language': locale, -// } -// }); -// }; +export const verifyCloudshellProviderRegistration = async(subscriptionId: string, authToken: string) => { + const targetUri = getArmUri(`/subscriptions/${subscriptionId}/providers/Microsoft.CloudShell?api-version=2022-12-01`).toString(); + const locale = getLocale(); + return await fetch(targetUri, { + method: "get", + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'Authorization': authToken, + 'Accept-Language': locale, + } + }); +}; -// export const registerCloudShellProvider = async (subscriptionId: string, authToken: string) => { -// const targetUri = getArmUri(`/subscriptions/${subscriptionId}/providers/Microsoft.CloudShell/register?api-version=2022-12-01`).toString(); -// return await fetch(targetUri, { -// method: "post", -// headers: { -// 'Content-Length': "0", -// 'Content-Type': 'application/json', -// 'Authorization': authToken -// } -// }); -// }; +export const registerCloudShellProvider = async (subscriptionId: string, authToken: string) => { + const targetUri = getArmUri(`/subscriptions/${subscriptionId}/providers/Microsoft.CloudShell/register?api-version=2022-12-01`).toString(); + return await fetch(targetUri, { + method: "post", + headers: { + 'Content-Length': "0", + 'Content-Type': 'application/json', + 'Authorization': authToken + } + }); +}; -// // TODO: update accept language header -// export const putEphemeralUserSettings = trackedApiCall(async (userSubscriptionId: string, userRegion: string, authorizationToken: string) => { -// const ephemeralSettings = { -// properties: { -// preferredOsType: OsType.Linux, -// preferredShellType: ShellType.Bash, -// preferredLocation: userRegion, -// networkType: NetworkType.Default, -// sessionType: SessionType.Ephemeral, -// userSubscription: userSubscriptionId, -// } -// }; +// TODO: update accept language header +export const putEphemeralUserSettings = trackedApiCall(async (userSubscriptionId: string, userRegion: string, authorizationToken: string) => { + const ephemeralSettings = { + properties: { + preferredOsType: OsType.Linux, + preferredShellType: ShellType.Bash, + preferredLocation: userRegion, + networkType: NetworkType.Default, + sessionType: SessionType.Ephemeral, + userSubscription: userSubscriptionId, + } + }; -// const armUri = getArmUri("/providers/Microsoft.Portal/userSettings/cloudconsole?api-version=2023-02-01-preview").toString(); -// await fetch(armUri, { -// method: "put", -// body: JSON.stringify(ephemeralSettings), -// headers: { -// 'Accept': 'application/json', -// 'Content-Type': 'application/json', -// 'Authorization': authorizationToken, -// 'Accept-Language': getLocale(), -// } -// }); -// }, "putEphemeralUserSettings"); + const armUri = getArmUri("/providers/Microsoft.Portal/userSettings/cloudconsole?api-version=2023-02-01-preview").toString(); + await fetch(armUri, { + method: "put", + body: JSON.stringify(ephemeralSettings), + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'Authorization': authorizationToken, + 'Accept-Language': getLocale(), + } + }); +}, "putEphemeralUserSettings"); -// type provisionConsoleResponse = { -// properties: { -// osType: OsType; -// provisioningState: string; -// uri: string; -// }; -// }; +type provisionConsoleResponse = { + properties: { + osType: OsType; + provisioningState: string; + uri: string; + }; +}; -// export const provisionConsole = trackedApiCall(async (subscriptionId: string, authorizationToken: string, location: string): Promise => { -// const armUri = getArmUri(`providers/Microsoft.Portal/consoles/default?api-version=2023-02-01-preview&feature.azureconsole.sessiontype=mounted&feature.azureconsole.usersubscription=${subscriptionId}`).toString(); +export const provisionConsole = trackedApiCall(async (subscriptionId: string, authorizationToken: string, location: string): Promise => { + const armUri = getArmUri(`providers/Microsoft.Portal/consoles/default?api-version=2023-02-01-preview&feature.azureconsole.sessiontype=mounted&feature.azureconsole.usersubscription=${subscriptionId}`).toString(); -// const data = { -// properties: { -// osType: OsType.Linux -// } -// }; -// const resp = await fetch(armUri, { -// method: "put", -// headers: { -// 'Accept': 'application/json', -// 'Content-Type': 'application/json', -// 'Authorization': authorizationToken, -// 'x-ms-console-preferred-location': location, -// 'x-ms-correlation-request-id': uuidv4(), -// 'Accept-Language': getLocale() -// }, -// body: JSON.stringify(data) -// }); -// return resp.json(); -// }, "provisionConsole"); + const data = { + properties: { + osType: OsType.Linux + } + }; + const resp = await fetch(armUri, { + method: "put", + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'Authorization': authorizationToken, + 'x-ms-console-preferred-location': location, + 'x-ms-correlation-request-id': uuidv4(), + 'Accept-Language': getLocale() + }, + body: JSON.stringify(data) + }); + return resp.json(); +}, "provisionConsole"); -// export type ConnectTerminalResponse = { -// id: string; -// idleTimeout: string; -// rootDirectory: string; -// socketUri: string; -// tokenUpdated: boolean; -// }; +export type ConnectTerminalResponse = { + id: string; + idleTimeout: string; + rootDirectory: string; + socketUri: string; + tokenUpdated: boolean; +}; -// export const postTokens = trackedApiCall(async (consoleUri: string, authorizationToken: string) => { -// const targetUri = consoleUri + '/accessToken'; -// await fetch(targetUri, { -// method: "post", -// headers: { -// 'Accept': 'application/json', -// 'Authorization': authorizationToken, -// 'x-ms-client-request-id': uuidv4(), -// 'Accept-Language': getLocale() -// }, -// body: JSON.stringify({ armToken }) -// }); -// }, "postTokens"); +export const postTokens = trackedApiCall(async (consoleUri: string, authorizationToken: string) => { + const targetUri = consoleUri + '/accessToken'; + let aadAuth = useAADAuth(); + let token = aadAuth.armToken; -// export const connectTerminal = trackedApiCall(async (consoleUri: string, authorizationToken: string, size: { rows: number, cols: number }): Promise => { -// const targetUri = consoleUri + `/terminals?cols=${size.cols}&rows=${size.rows}&version=2019-01-01&shell=bash`; -// const resp = await fetch(targetUri, { -// method: "post", -// headers: { -// 'Accept': 'application/json', -// 'Content-Type': 'application/json', -// 'Content-Length': '2', -// 'Authorization': authorizationToken, -// 'x-ms-client-request-id': uuidv4(), -// 'Accept-Language': getLocale(), -// }, -// body: "{}" // empty body is necessary -// }); -// return resp.json(); -// }, "connectTerminal"); + await fetch(targetUri, { + method: "post", + headers: { + 'Accept': 'application/json', + 'Authorization': authorizationToken, + 'x-ms-client-request-id': uuidv4(), + 'Accept-Language': getLocale() + }, + body: JSON.stringify({ token }) + }); +}, "postTokens"); -// export type Authroization = { -// token: string; -// }; +export const connectTerminal = trackedApiCall(async (consoleUri: string, authorizationToken: string, size: { rows: number, cols: number }): Promise => { + const targetUri = consoleUri + `/terminals?cols=${size.cols}&rows=${size.rows}&version=2019-01-01&shell=bash`; + const resp = await fetch(targetUri, { + method: "post", + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'Content-Length': '2', + 'Authorization': authorizationToken, + 'x-ms-client-request-id': uuidv4(), + 'Accept-Language': getLocale(), + }, + body: "{}" // empty body is necessary + }); + return resp.json(); +}, "connectTerminal"); -// export const authorizeSession = trackedApiCall(async (consoleUri: string, accessToken: string): Promise => { -// const targetUri = consoleUri + "/authorize"; -// const resp = await fetch(targetUri, { -// method: "post", -// headers: { -// 'Accept': 'application/json', -// 'Authorization': accessToken, -// 'Accept-Language': getLocale(), -// "Content-Type": 'application/json' -// } -// }); -// return resp.json(); -// }, "authorizeSession"); +export type Authroization = { + token: string; +}; + +export const authorizeSession = trackedApiCall(async (consoleUri: string, accessToken: string): Promise => { + const targetUri = consoleUri + "/authorize"; + const resp = await fetch(targetUri, { + method: "post", + headers: { + 'Accept': 'application/json', + 'Authorization': accessToken, + 'Accept-Language': getLocale(), + "Content-Type": 'application/json' + } + }); + return resp.json(); +}, "authorizeSession"); -// export const getArmUri = (origin: string): (relativePath: string) => string => { -// let originNoTrailingSlash = origin; -// if (origin.endsWith("/")) { -// originNoTrailingSlash = originNoTrailingSlash.slice(0, originNoTrailingSlash.length - 1); -// } else { -// origin += "/"; -// } +export const getArmUri = (origin: string): (relativePath: string) => string => { + let originNoTrailingSlash = origin; + if (origin.endsWith("/")) { + originNoTrailingSlash = originNoTrailingSlash.slice(0, originNoTrailingSlash.length - 1); + } else { + origin += "/"; + } -// return (relativePath: string) => { -// if (!relativePath) { -// throw new Error(`relativePath is required: ${relativePath}`); -// } + return (relativePath: string) => { + if (!relativePath) { + throw new Error(`relativePath is required: ${relativePath}`); + } -// return `${relativePath.charAt(0) === "/" ? originNoTrailingSlash : origin}${relativePath}`; -// }; -// } + return `${relativePath.charAt(0) === "/" ? originNoTrailingSlash : origin}${relativePath}`; + }; +} -// export const getLocale = () => { -// const langLocale = navigator.language; -// return (langLocale && langLocale.length === 2 ? langLocale[1] : 'en-us'); -// }; +export const getLocale = () => { + const langLocale = navigator.language; + return (langLocale && langLocale.length === 2 ? langLocale[1] : 'en-us'); +}; -// export const getTrustedParentOrigin = () => { -// const searchParams = new URLSearchParams(window.location.search); -// return searchParams.get("trustedAuthority") || ''; -// } +export const getTrustedParentOrigin = () => { + const searchParams = new URLSearchParams(window.location.search); + return searchParams.get("trustedAuthority") || ''; +} -// let keepAliveID: NodeJS.Timeout = null; -// let pingCount = 0; +let keepAliveID: NodeJS.Timeout = null; +let pingCount = 0; -// export const configureSocket = (socket: WebSocket, uri: string, terminal: XTerminal, intervals: NodeJS.Timer[], socketRetryCount: number) => { -// let jsonData = ''; +export const configureSocket = (socket: WebSocket, uri: string, terminal: XTerminal, intervals: NodeJS.Timer[], socketRetryCount: number) => { + let jsonData = ''; -// socket.onopen = () => { -// const initializeCommand = -// `rm -rf ie.log && rm -rf ie && rm -rf scenarios/ && \n` + -// `echo Welcome to this quick start shell. This Cloud Shell terminal will be used to execute commands as part of the scenario. Follow the instructions on the left to get started\n`; + socket.onopen = () => { + const initializeCommand = + `rm -rf ie.log && rm -rf ie && rm -rf scenarios/ && \n` + + `echo Welcome to this quick start shell. This Cloud Shell terminal will be used to execute commands as part of the scenario. Follow the instructions on the left to get started\n`; -// socket.send(initializeCommand); + socket.send(initializeCommand); -// const keepSocketAlive = (socket: WebSocket) => { -// if (socket.readyState === WebSocket.OPEN) { -// if ((pingCount / 60) >= 20) { -// socket.close(); -// } else { -// socket.send(''); -// pingCount++; -// keepAliveID = setTimeout(() => keepSocketAlive(socket), 1000); -// } -// } -// }; -// keepSocketAlive(socket); -// }; + const keepSocketAlive = (socket: WebSocket) => { + if (socket.readyState === WebSocket.OPEN) { + if ((pingCount / 60) >= 20) { + socket.close(); + } else { + socket.send(''); + pingCount++; + keepAliveID = setTimeout(() => keepSocketAlive(socket), 1000); + } + } + }; + keepSocketAlive(socket); + }; -// socket.onclose = () => { -// if (keepAliveID) { -// clearTimeout(keepAliveID); -// pingCount = 0; -// } -// intervals.forEach((val) => { -// window.clearInterval(+val); -// }); + socket.onclose = () => { + if (keepAliveID) { + clearTimeout(keepAliveID); + pingCount = 0; + } + intervals.forEach((val) => { + window.clearInterval(+val); + }); -// terminal.writeln("Session terminated. Please refresh the page to start a new session."); -// }; + terminal.writeln("Session terminated. Please refresh the page to start a new session."); + }; -// socket.onerror = () => { -// terminal.writeln("terminal reconnected"); -// if (socketRetryCount < 10 && socket.readyState !== WebSocket.CLOSED) { -// configureSocket(socket, uri, terminal, intervals, socketRetryCount + 1); -// } else { -// // log an error indicating socket connection failed -// terminal.writeln("Socket connection closed"); -// // close the socket -// socket.close(); -// } -// }; + socket.onerror = () => { + terminal.writeln("terminal reconnected"); + if (socketRetryCount < 10 && socket.readyState !== WebSocket.CLOSED) { + configureSocket(socket, uri, terminal, intervals, socketRetryCount + 1); + } else { + // log an error indicating socket connection failed + terminal.writeln("Socket connection closed"); + // close the socket + socket.close(); + } + }; -// socket.onmessage = (event: MessageEvent) => { -// // if we are sending and receiving messages the terminal is not idle set ping count to 0 -// pingCount = 0; + socket.onmessage = (event: MessageEvent) => { + // if we are sending and receiving messages the terminal is not idle set ping count to 0 + pingCount = 0; -// // check if we are dealing with array buffer or string -// let eventData = ''; -// if (typeof event.data === "object") { -// try { -// const enc = new TextDecoder("utf-8"); -// eventData = enc.decode(event.data as any); -// } catch (e) { -// // not array buffer -// } -// } -// if (typeof event.data === 'string') { -// eventData = event.data; -// } + // check if we are dealing with array buffer or string + let eventData = ''; + if (typeof event.data === "object") { + try { + const enc = new TextDecoder("utf-8"); + eventData = enc.decode(event.data as any); + } catch (e) { + // not array buffer + } + } + if (typeof event.data === 'string') { + eventData = event.data; + } -// // process as one line or process as multiline -// if (eventData.includes("ie_us") && eventData.includes("ie_ue")) { -// // process as one line -// const statusData = eventData.split('ie_us')[1].split('ie_ue')[0]; -// console.log(statusData); -// } else if (eventData.includes("ie_us")) { -// // check for start -// jsonData += eventData.split('ie_us')[1]; -// } else if (eventData.includes("ie_ue")) { -// // check for end and process the command -// jsonData += eventData.split('ie_ue')[0]; -// console.log(jsonData); -// jsonData = ''; -// } else if (jsonData.length > 0) { -// // check if the line is all data then just concatenate -// jsonData += eventData; -// } -// }; -// return socket; -// }; + // process as one line or process as multiline + if (eventData.includes("ie_us") && eventData.includes("ie_ue")) { + // process as one line + const statusData = eventData.split('ie_us')[1].split('ie_ue')[0]; + console.log(statusData); + } else if (eventData.includes("ie_us")) { + // check for start + jsonData += eventData.split('ie_us')[1]; + } else if (eventData.includes("ie_ue")) { + // check for end and process the command + jsonData += eventData.split('ie_ue')[0]; + console.log(jsonData); + jsonData = ''; + } else if (jsonData.length > 0) { + // check if the line is all data then just concatenate + jsonData += eventData; + } + }; + return socket; +}; diff --git a/src/Terminal/index.ts b/src/Terminal/index.ts index dda1400ed..9bc549f11 100644 --- a/src/Terminal/index.ts +++ b/src/Terminal/index.ts @@ -3,6 +3,7 @@ import { IMessage, ITerminalConnection } from "@jupyterlab/services/lib/terminal import "@jupyterlab/terminal/style/index.css"; import { MessageTypes } from "Contracts/ExplorerContracts"; import postRobot from "post-robot"; +import { Terminal as XTerminal } from 'xterm'; import { HttpHeaders } from "../Common/Constants"; import { Action } from "../Shared/Telemetry/TelemetryConstants"; import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor"; @@ -11,7 +12,7 @@ import { JupyterLabAppFactory } from "./JupyterLabAppFactory"; import { TerminalProps } from "./TerminalProps"; import "./index.css"; -let session: ITerminalConnection | undefined; +let session: ITerminalConnection | undefined | XTerminal; const createServerSettings = (props: TerminalProps): ServerConnection.ISettings => { let body: BodyInit | undefined; @@ -62,7 +63,37 @@ const initTerminal = async (props: TerminalProps): Promise => { const serverSettings = createServerSettings(props); - createTerminalApp(props, serverSettings); + //createTerminalApp(props, serverSettings); + + + const xterminal = new XTerminal(); + + // Ensure newTab.node is set to a valid HTMLDivElement + if (!newTab.node || !(newTab.node instanceof HTMLDivElement)) { + newTab.node = document.createElement('div'); // Create a div if not already set + } + + // Ensure the container is an HTMLDivElement + const termContainer = newTab.node as unknown as HTMLDivElement; + termContainer.style.width = '100%'; + termContainer.style.height = '100%'; + + // Append the container to the tab system if necessary + document.body.appendChild(termContainer); // Optional: Add it to the DOM if needed + + // Attach xterm.js to the correct container + xterminal.open(termContainer); + + const termWidget = new Widget(); + termWidget.node.appendChild(termContainer); + + const panel = new Panel(); + panel.addWidget(termWidget); + + // Write to the terminal + xterminal.write('I am new shell'); + + session = xterminal; }; const createTerminalApp = async (props: TerminalProps, serverSettings: ServerConnection.ISettings) => {