diff --git a/package.json b/package.json index 376d133..3bf3259 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "devstar", "displayName": "%displayName%", "description": "%description%", - "version": "0.3.9", + "version": "0.4.0", "keywords": [], "publisher": "mengning", "engines": { @@ -136,4 +136,4 @@ "extensionDependencies": [ "ms-vscode.cpptools" ] -} +} \ No newline at end of file diff --git a/src/devstar-api.ts b/src/devstar-api.ts index ba79d13..be15f35 100644 --- a/src/devstar-api.ts +++ b/src/devstar-api.ts @@ -43,31 +43,40 @@ export default class DevstarAPIHandler { } }); - // 处理非200响应状态码 + // 检查响应状态码 if (!response.ok) { - const text = await response.text(); // 先读取文本防止json解析失败 - if (response.status == 401) { - throw new Error('Token错误') + const text = await response.text(); // 读取文本内容以便调试 + console.error(`HTTP Error: ${response.status} - ${text}`); + if (response.status === 401) { + throw new Error('Token错误'); } else { - throw new Error(`HTTP Error: ${response.status} - ${text}`); + throw new Error(`HTTP Error: ${response.status}`); } } + // 检查 Content-Type 是否为 JSON + const contentType = response.headers.get('Content-Type'); + if (!contentType || !contentType.includes('application/json')) { + const text = await response.text(); // 读取文本内容以便调试 + console.error(`Unexpected Content-Type: ${contentType} - ${text}`); + throw new Error(`Unexpected Content-Type: ${contentType}`); + } + const responseData = await response.json(); - const data = responseData.data - if (data.username == undefined || data.username == "") { - throw new Error('Token对应用户不存在') - } else { - // 验证用户名匹配 - if (data.username !== username) { - throw new Error('Token与用户名不符'); - } + const data = responseData.data; + if (!data || !data.username) { + throw new Error('Token对应用户不存在'); + } + + // 验证用户名匹配 + if (data.username !== username) { + throw new Error('Token与用户名不符'); } return true; } catch (error) { - console.error(error) - return false + console.error(error); + return false; } } diff --git a/src/home.ts b/src/home.ts index 3ac017d..9b46333 100644 --- a/src/home.ts +++ b/src/home.ts @@ -110,7 +110,7 @@ export default class DSHome { await this.remoteContainer.firstOpenProject(data.host, data.hostname, data.port, data.username, data.path, this.context); break; case 'openRemoteFolder': - this.remoteContainer.openRemoteFolder(data.host, data.port, data.username, data.path); + this.remoteContainer.openRemoteFolder(data.host, data.port, data.username, data.path, this.context); break; case 'getDevstarDomain': panel.webview.postMessage({ command: 'getDevstarDomain', data: { devstarDomain: this.devstarDomain } }); diff --git a/src/main.ts b/src/main.ts index 2c040e1..26d288c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -4,7 +4,6 @@ import QuickAccessTreeProvider from './views/quick-access-tree'; import DSHome from './home'; import RemoteContainer, { openProjectWithoutLogging } from './remote-container'; import User from './user'; -import DevstarAPIHandler from './devstar-api'; import * as os from 'os'; import * as utils from './utils'; @@ -67,7 +66,40 @@ export class DevStarExtension { const path = params.get('path'); const accessToken = params.get('access_token'); const devstarUsername = params.get('devstar_username'); - const devstarDomain = params.get('devstar_domain'); + const rawDevstarDomain = params.get('devstar_domain'); + let devstarDomain = rawDevstarDomain; + if (rawDevstarDomain) { + try { + const url = new URL(rawDevstarDomain); + devstarDomain = `${url.protocol}//${url.hostname}`; + + // 从 rawDevstarDomain 的查询参数中提取 forwardPorts + const forwardPortsParam = url.searchParams.get('forwardPorts'); + if (forwardPortsParam) { + const ports = forwardPortsParam.split(',').map(port => parseInt(port, 10)).filter(port => !isNaN(port)); + console.log('解析到的 forwardPorts 参数:', ports); + context.globalState.update('forwardPorts', ports); + } else { + // 如果没有 forwardPorts 参数,清除 globalState 中的旧值 + console.log('未找到 forwardPorts 参数,清除旧的 forwardPorts 配置'); + context.globalState.update('forwardPorts', undefined); + } + } catch (error) { + console.error('Invalid devstar_domain URL:', error); + } + } + console.log('sanitized_devstar_domain:', devstarDomain); + + // 使用修正后的 devstar_domain + if (devstarDomain) { + this.user.setDevstarDomain(devstarDomain); + this.remoteContainer.setUser(this.user); + this.dsHome.setDevstarDomainAndHomePageURL(devstarDomain); + this.dsHome.setUser(this.user); + this.dsHome.setRemoteContainer(this.remoteContainer); + + context.globalState.update('devstarDomain', devstarDomain); + } if (host && hostname && port && username && path) { const containerHost = host; @@ -167,7 +199,7 @@ export class DevStarExtension { ); this.registerGlobalCommands(context); - + //防止进入HOME页面 // this.startDevStarHome(); } @@ -179,10 +211,10 @@ export class DevStarExtension { this.dsHome.toggle() ), vscode.commands.registerCommand('devstar.showPortMappings', () => { - // 这里需要根据当前活动连接获取hostname和port - // 简化实现:显示所有活动的端口映射 - this.remoteContainer.showPortMappingsInOutputChannel('current', 0); - }), + // 这里需要根据当前活动连接获取hostname和port + // 简化实现:显示所有活动的端口映射 + this.remoteContainer.showPortMappingsInOutputChannel('current', 0); + }), vscode.commands.registerCommand('devstar.clean', () => { // 先清除ssh key if (fs.existsSync(this.user.getUserPrivateKeyPath())) { diff --git a/src/remote-container.ts b/src/remote-container.ts index 9cf8cd7..0750468 100644 --- a/src/remote-container.ts +++ b/src/remote-container.ts @@ -77,7 +77,7 @@ export default class RemoteContainer { await this.firstConnect(host, hostname, username, port, path) .then((res) => { if (res === 'success') { - this.openRemoteFolder(host, port, username, path); + this.openRemoteFolder(host, port, username, path, context); } else { vscode.window.showErrorMessage('首次连接容器失败,请检查网络和容器状态。'); } @@ -96,7 +96,7 @@ export default class RemoteContainer { /** * local environment,第一次连接其他项目 */ - async firstConnect(host: string, hostname: string, username: string, port: number, projectPath?: string): Promise { + async firstConnect(host: string, hostname: string, _username: string, port: number, projectPath?: string): Promise { return new Promise(async (resolve, reject) => { const ssh = new NodeSSH(); @@ -190,37 +190,6 @@ export default class RemoteContainer { }); } - /** - * 从 devcontainer.json 中提取端口映射配置 - */ - private async getPortsConfigFromDevContainer(ssh: any, containerPath: string): Promise<{ - portsAttributes: any; - forwardPorts?: number[]; - otherPortsAttributes?: any; - }> { - try { - const findResult = await ssh.execCommand(`find ${containerPath} -name "devcontainer.json" -type f`); - - if (findResult.code === 0 && findResult.stdout.trim()) { - const devcontainerPath = findResult.stdout.trim().split('\n')[0]; - - const readResult = await ssh.execCommand(`cat ${devcontainerPath}`); - if (readResult.code === 0) { - const devcontainerConfig = JSON.parse(readResult.stdout); - - return { - portsAttributes: devcontainerConfig.portsAttributes || {}, - forwardPorts: devcontainerConfig.forwardPorts, - otherPortsAttributes: devcontainerConfig.otherPortsAttributes - }; - } - } - } catch (error) { - } - - return { portsAttributes: {} }; - } - /** * 查找可用的本地端口 - 优先使用相同端口 */ @@ -279,74 +248,6 @@ export default class RemoteContainer { }); } - /** - * 建立端口映射 - 根据 devcontainer.json 配置 - */ - private async setupPortForwarding(hostname: string, port: number, containerPath: string): Promise { - const ssh = new NodeSSH(); - const portMappings: Array<{ containerPort: number, localPort: number, label: string, source: string }> = []; - - try { - await ssh.connect({ - host: hostname, - username: 'root', - port: port, - privateKeyPath: this.user.getUserPrivateKeyPath(), - readyTimeout: 30000, - }); - - const portsConfig = await this.getPortsConfigFromDevContainer(ssh, containerPath); - - if (portsConfig.forwardPorts && portsConfig.forwardPorts.length > 0) { - for (const containerPort of portsConfig.forwardPorts) { - const localPort = await this.findAvailableLocalPort(containerPort); - await this.createSSHPortForward(hostname, port, containerPort, localPort); - - portMappings.push({ - containerPort, - localPort, - label: `Port ${containerPort}`, - source: 'forwardPorts' - }); - } - } - - for (const [containerPortStr, attributes] of Object.entries(portsConfig.portsAttributes)) { - const containerPort = parseInt(containerPortStr); - if (!isNaN(containerPort)) { - const alreadyMapped = portMappings.some(m => m.containerPort === containerPort); - if (!alreadyMapped) { - const localPort = await this.findAvailableLocalPort(containerPort); - await this.createSSHPortForward(hostname, port, containerPort, localPort); - - const label = (attributes as any).label || `Port ${containerPort}`; - portMappings.push({ - containerPort, - localPort, - label, - source: 'portsAttributes' - }); - } - } - } - - await ssh.dispose(); - - const mappingKey = `${hostname}:${port}`; - this.portMappings.set(mappingKey, portMappings); - - if (portMappings.length > 0) { - this.showPortMappingsSummary(portMappings); - - this.registerPortMappingsCommands(mappingKey, portMappings); - } - - } catch (error) { - await ssh.dispose(); - throw error; - } - } - /** * 创建 SSH 端口转发 */ @@ -369,10 +270,10 @@ export default class RemoteContainer { reject(error); }); - sshProcess.stdout.on('data', (data: Buffer) => { + sshProcess.stdout.on('data', (_data: Buffer) => { }); - sshProcess.stderr.on('data', (data: Buffer) => { + sshProcess.stderr.on('data', (_data: Buffer) => { }); if (!this.sshProcesses) { @@ -535,12 +436,13 @@ export default class RemoteContainer { /** * local env */ - async openRemoteFolder(host: string, port: number, username: string, path: string): Promise { + async openRemoteFolder(host: string, port: number, _username: string, path: string, context: vscode.ExtensionContext): Promise { try { const sshConfig = await this.getSSHConfig(host); if (sshConfig) { try { - await this.setupPortForwarding(sshConfig.hostname, port, path); + // 调用 setupPortForwardingFromGlobalState 方法 + await this.setupPortForwardingFromGlobalState(sshConfig.hostname, port, context); setTimeout(() => { this.showPortMappingsPanel(sshConfig.hostname, port); @@ -639,6 +541,47 @@ export default class RemoteContainer { this.portMappings.clear(); } } + + /** + * 从 globalState 获取 forwardPorts 并建立端口映射 + */ + public async setupPortForwardingFromGlobalState(hostname: string, port: number, context: vscode.ExtensionContext): Promise { + // 从 globalState 获取 forwardPorts 参数 + const forwardPorts: number[] | undefined = context.globalState.get('forwardPorts'); + + if (forwardPorts && forwardPorts.length > 0) { + const portMappings: Array<{ containerPort: number, localPort: number, label: string, source: string }> = []; + + for (const containerPort of forwardPorts) { + const localPort = await this.findAvailableLocalPort(containerPort); + try { + await this.createSSHPortForward(hostname, port, containerPort, localPort); + portMappings.push({ + containerPort, + localPort, + label: `Port ${containerPort}`, + source: 'globalState forwardPorts' + }); + } catch (error) { + console.error(`映射容器端口 ${containerPort} 到本地端口 ${localPort} 失败:`, error); + } + } + + const mappingKey = `${hostname}:${port}`; + this.portMappings.set(mappingKey, portMappings); + + if (portMappings.length > 0) { + this.showPortMappingsSummary(portMappings); + this.registerPortMappingsCommands(mappingKey, portMappings); + } + + // 使用完毕后立即清除 globalState 中的 forwardPorts,避免影响下一个项目 + console.log('端口映射完成,清除 globalState 中的 forwardPorts'); + context.globalState.update('forwardPorts', undefined); + } else { + console.log('未找到 forwardPorts 参数,跳过端口映射设置。'); + } + } } /** diff --git a/src/user.ts b/src/user.ts index bac7169..828c1aa 100644 --- a/src/user.ts +++ b/src/user.ts @@ -117,8 +117,8 @@ export default class User { } public async isLogged(): Promise { - const username: string|undefined = this.context.globalState.get(this.usernameKey) - const userToken: string|undefined = this.context.globalState.get(this.userTokenKey) + const username: string | undefined = this.context.globalState.get(this.usernameKey) + const userToken: string | undefined = this.context.globalState.get(this.userTokenKey) if ((username != undefined && username != '') && (userToken != undefined && userToken != '')) { const devstarAPIHandler = new DevstarAPIHandler(this.devstarDomain) @@ -172,7 +172,7 @@ export default class User { if (!this.isLogged()) { return ''; } else { - const username: string|undefined = this.context.globalState.get(this.usernameKey) + const username: string | undefined = this.context.globalState.get(this.usernameKey) // islogged为true,username不为空 return path.join(os.homedir(), '.ssh', `id_rsa_${username}_${this.devstarHostname}`) } @@ -182,7 +182,7 @@ export default class User { if (!this.isLogged()) { return ''; } else { - const username: string|undefined = this.context.globalState.get(this.usernameKey) + const username: string | undefined = this.context.globalState.get(this.usernameKey) // islogged为true,username不为空 return path.join(os.homedir(), '.ssh', `id_rsa_${username}_${this.devstarHostname}.pub`) } @@ -259,8 +259,55 @@ export default class User { this.updateLocalUserPrivateKeyPath(this.getUserPrivateKeyPath()) console.log(`Update local user private key path.`) } catch (error) { - const username: string|undefined = this.context.globalState.get(this.usernameKey) + const username: string | undefined = this.context.globalState.get(this.usernameKey) console.error(`Failed to write public/private key into the user(${username}) ssh public/key file: `, error); } } + + public async verifyToken(token: string, username: string): Promise { + try { + const response = await fetch(this.devstarDomain + `/api/devcontainer/user`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'token ' + token + } + }); + + // 检查响应状态码 + if (!response.ok) { + const text = await response.text(); // 读取文本内容以便调试 + console.error(`HTTP Error: ${response.status} - ${text}`); + if (response.status === 401) { + throw new Error('Token错误'); + } else { + throw new Error(`HTTP Error: ${response.status}`); + } + } + + // 检查 Content-Type 是否为 JSON + const contentType = response.headers.get('Content-Type'); + if (!contentType || !contentType.includes('application/json')) { + const text = await response.text(); // 读取文本内容以便调试 + console.error(`Unexpected Content-Type: ${contentType} - ${text}`); + throw new Error(`Unexpected Content-Type: ${contentType}`); + } + + const responseData = await response.json(); + const data = responseData.data; + if (!data || !data.username) { + throw new Error('Token对应用户不存在'); + } + + // 验证用户名匹配 + if (data.username !== username) { + throw new Error('Token与用户名不符'); + } + + return true; + } catch (error) { + console.error(error); + return false; + } + } } \ No newline at end of file