ssh-terminal
Version:
SSH Terminal component based on xterm.js for multiple frontend frameworks
577 lines (470 loc) • 16.3 kB
Markdown
# Vue SSH Terminal
Vue SSH Terminal là một component dựa trên [xterm.js](https://github.com/xtermjs/xterm.js) để sử dụng trong nhiều frontend khác nhau, cho phép kết nối SSH thông qua WebSocket.
## Tính năng
- **🆔 Computing ID Authentication**: Kết nối thông qua LongVan Computing ID
- **🔒 Zero Password**: Không cần biết SSH password, server tự động lấy credentials
- **🌐 WebSocket Connection**: Kết nối SSH thông qua WebSocket an toàn
- **🎯 Zero Configuration**: Chỉ cần Computing ID và User Token
- **🔧 Multi-Framework**: Hỗ trợ Vue 2, Vue 3, và Web Component
- **🎨 Customizable**: Tùy chỉnh giao diện terminal (font, theme, cursor)
- **📱 Responsive**: Tự động resize terminal khi thay đổi kích thước cửa sổ
### 🔒 Tính Năng Bảo Mật Nâng Cao (v2.0+)
- **🔐 Mandatory RSA Encryption**: Bắt buộc mã hóa passwords - không hỗ trợ plaintext
- **🚫 Legacy Auth Disabled**: Hoàn toàn loại bỏ plaintext authentication
- **✅ Input Validation**: Xác thực nghiêm ngặt tất cả đầu vào
- **🔍 Secure Logging**: Không log thông tin nhạy cảm ra console
- **📏 Message Size Validation**: Kiểm tra kích thước message để tránh DoS
- **🛡️ Error Sanitization**: Thông báo lỗi không tiết lộ thông tin hệ thống
- **🔒 Credentials Protection**: Credentials không lộ trong DOM attributes
- **🧹 Input Sanitization**: Làm sạch dữ liệu đầu vào
- **⚡ Fail-Fast Security**: Ngắt kết nối ngay khi encryption fails
### 🚀 Universal Environment Support (v2.5+)
- **🌐 Production-Ready Compatibility**: Hoạt động hoàn hảo trên mọi HTTP và HTTPS environments
- **🔧 Advanced Dual Encryption**: Web Crypto API (HTTPS) + micro-rsa-dsa-dh (HTTP) với real RSA-OAEP
- **🎨 Professional Clean UI**: Giao diện production-ready, ẩn connection logs mặc định
- **📦 Zero-Dependency Deployment**: Tích hợp sẵn tất cả crypto libraries, không cần external CDN
- **⚡ Intelligent Detection**: Tự động chọn phương thức mã hóa tối ưu cho từng environment
- **🛡️ Enterprise Security**: Real encryption trong mọi môi trường, không có mock implementations
### 🚀 Tính Năng Khác
- **Production Bundle**: Web component ~372KB (bao gồm enterprise-grade crypto libraries)
- **Auto-reconnection**: Tự động kết nối lại khi mất connection với exponential backoff
- **Connection stability**: Heartbeat mechanism để maintain WebSocket connection
- **Consistent CSS approach**: Tất cả components sử dụng external CSS files
## Cài đặt
```bash
npm install ssh-terminal
```
> **⚠️ Lưu ý quan trọng**: Tất cả components đều yêu cầu import CSS file riêng để hiển thị đúng.
## 🚀 Quick Start
### Computing ID (Recommended)
Cách nhanh nhất để kết nối với LongVan Computing instances:
#### Vue 2
```vue
<template>
<SSHTerminal
:computing-id="computingId"
:user-token="userToken"
:websocket-url="websocketUrl"
/>
</template>
<script>
import { SSHTerminal } from 'ssh-terminal/vue2';
import 'ssh-terminal/dist/vue2/ssh-terminal-vue2.css';
export default {
components: {
SSHTerminal
},
data() {
return {
computingId: '20.5109.3964',
userToken: 'your-longvan-token',
websocketUrl: 'wss://ssh-proxy.dev.longvan.vn'
};
}
};
</script>
```
#### Vue 3
```vue
<template>
<SSHTerminal
:computing-id="computingId"
:user-token="userToken"
:websocket-url="websocketUrl"
/>
</template>
<script setup>
import { ref } from 'vue';
import { SSHTerminal } from 'ssh-terminal/vue3';
import 'ssh-terminal/dist/vue3/ssh-terminal-vue3.css';
const computingId = ref('20.5109.3964');
const userToken = ref('your-longvan-token');
const websocketUrl = ref('wss://ssh-proxy.dev.longvan.vn');
</script>
```
#### JavaScript thuần
```html
<html>
<head>
<script type="module">
import { SSHTerminalElement } from 'ssh-terminal';
if (!customElements.get('ssh-terminal')) {
customElements.define('ssh-terminal', SSHTerminalElement);
}
</script>
</head>
<body>
<ssh-terminal id="terminal"></ssh-terminal>
<script>
const terminal = document.getElementById('terminal');
// Cấu hình Computing ID
terminal.setConfig({
computingId: '20.5109.3964',
userToken: 'your-longvan-token',
wsUrl: 'wss://ssh-proxy.dev.longvan.vn'
});
// Lắng nghe sự kiện
terminal.addEventListener('ready', () => {
console.log('Connected to computing instance');
});
</script>
</body>
</html>
```
## Sử dụng
### Vue 3
```vue
<template>
<div class="terminal-container">
<!-- Computing ID Authentication -->
<SSHTerminal
:computing-id="computingId"
:user-token="userToken"
:websocket-url="websocketUrl"
:options="options"
@ready="onTerminalReady"
/>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { SSHTerminal } from 'ssh-terminal/vue3';
import 'ssh-terminal/dist/vue3/ssh-terminal-vue3.css';
// Computing ID authentication
const computingId = ref('20.5109.3964');
const userToken = ref('your-longvan-token');
const websocketUrl = ref('wss://ssh-proxy.dev.longvan.vn');
const options = ref({
fontSize: 14,
theme: {
background: '#1e1e1e',
foreground: '#f0f0f0'
}
});
function onTerminalReady(terminal) {
// Terminal connected to computing instance
console.log('Connected to computing:', computingId.value);
}
</script>
<style>
.terminal-container {
width: 100%;
height: 500px;
}
</style>
```
### Web Component
```bash
npm install ssh-terminal
```
```html
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SSH Terminal - Computing ID</title>
<script type="module">
import { SSHTerminalElement } from 'ssh-terminal';
if (!customElements.get('ssh-terminal')) {
customElements.define('ssh-terminal', SSHTerminalElement);
}
</script>
<style>
.terminal-container {
width: 800px;
height: 500px;
margin: 20px auto;
}
</style>
</head>
<body>
<div class="terminal-container">
<!-- Computing ID Authentication -->
<ssh-terminal id="terminal"></ssh-terminal>
</div>
<script>
// 🆔 Set Computing ID configuration
const terminal = document.getElementById('terminal');
terminal.setConfig({
computingId: '20.5109.3964',
userToken: 'your-longvan-token',
wsUrl: 'wss://ssh-proxy.dev.longvan.vn'
});
terminal.addEventListener('ready', (event) => {
console.log('Connected to computing instance');
});
</script>
</body>
</html>
```
#### Sử dụng trực tiếp từ CDN (không cần npm)
```html
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SSH Terminal Example</title>
<!-- Sử dụng từ unpkg CDN -->
<script src="https://unpkg.com/ssh-terminal@2.1.2/dist/web-component/ssh-terminal.umd.js"></script>
<!-- Hoặc sử dụng từ jsDelivr -->
<!--
<script src="https://cdn.jsdelivr.net/npm/ssh-terminal@2.1.2/dist/web-component/ssh-terminal.umd.js"></script>
-->
<!-- Lưu ý: Web component tự động inject CSS vào shadow DOM, không cần import CSS riêng -->
<!-- Các phần tử helper của xterm.js sẽ được giữ trong shadow DOM của web component -->
<style>
.terminal-container {
width: 800px;
height: 500px;
margin: 20px auto;
}
</style>
</head>
<body>
<div class="terminal-container">
<!-- ✅ Secure way (v2.0+) -->
<ssh-terminal id="terminal" ws-url="wss://ssh-proxy.dev.longvan.vn"></ssh-terminal>
</div>
<script>
// 🆔 Set Computing ID configuration
const terminal = document.getElementById('terminal');
terminal.setConfig({
computingId: '20.5109.3964',
userToken: 'your-longvan-token',
wsUrl: 'wss://ssh-proxy.dev.longvan.vn'
});
terminal.addEventListener('ready', (event) => {
console.log('Connected to computing instance');
});
</script>
</body>
</html>
```
#### Tải xuống và sử dụng trực tiếp
Bạn cũng có thể tải xuống file sau và sử dụng trực tiếp:
- `ssh-terminal.umd.js` (CSS được inject tự động)
```html
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SSH Terminal Example</title>
<script src="path/to/ssh-terminal.umd.js"></script>
<!-- CSS được inject tự động vào shadow DOM -->
<style>
.terminal-container {
width: 800px;
height: 500px;
margin: 20px auto;
}
</style>
</head>
<body>
<div class="terminal-container">
<!-- ✅ Secure way (v2.0+) -->
<ssh-terminal id="terminal"></ssh-terminal>
</div>
<script>
// 🆔 Set Computing ID configuration
const terminal = document.getElementById('terminal');
terminal.setConfig({
computingId: '20.5109.3964',
userToken: 'your-longvan-token',
wsUrl: 'wss://ssh-proxy.dev.longvan.vn'
});
terminal.addEventListener('ready', (event) => {
console.log('Connected to computing instance');
});
</script>
</body>
</html>
```
## 🔧 Framework Integration
### Nuxt 2
#### Plugin Setup
Tạo file `plugins/ssh-terminal.client.js`:
```javascript
import Vue from 'vue'
import { Vue2SSHTerminal } from 'ssh-terminal/vue2'
// Register component globally (CSS tự động inject)
Vue.component('SSHTerminal', Vue2SSHTerminal)
```
Đăng ký plugin trong `nuxt.config.js`:
```javascript
export default {
plugins: [
{ src: '~/plugins/ssh-terminal.client.js', mode: 'client' }
]
}
```
#### Sử dụng trong component
```vue
<template>
<div class="terminal-container">
<SSHTerminal
:computing-id="computingId"
:user-token="userToken"
:websocket-url="websocketUrl"
@ready="onTerminalReady"
@error="onTerminalError"
/>
</div>
</template>
<script>
export default {
data() {
return {
computingId: '20.5109.3964',
userToken: 'your-longvan-token',
websocketUrl: 'wss://ssh-proxy.dev.longvan.vn'
}
},
methods: {
onTerminalReady(terminal) {
console.log('✅ Terminal ready!')
},
onTerminalError(error) {
console.error('❌ Terminal error:', error)
}
}
}
</script>
<style scoped>
.terminal-container {
width: 100%;
height: 500px;
}
</style>
```
## 🆔 Computing ID Authentication
Kết nối SSH thông qua LongVan Computing ID - không cần biết SSH credentials.
### Flow hoạt động:
1. **Client gửi Computing ID + User Token** (encrypted)
2. **Server query LongVan GraphQL API** để lấy SSH credentials
3. **Automatic SSH connection** với credentials từ API
4. **Terminal ready** - zero password approach
### Ví dụ sử dụng:
```javascript
// Mặc định: Clean UI (không hiển thị connection logs)
terminal.setConfig({
computingId: '20.5109.3964',
userToken: 'your-longvan-token',
wsUrl: 'wss://ssh-proxy.dev.longvan.vn'
});
// Debug mode: Hiển thị connection logs
terminal.setConfig({
computingId: '20.5109.3964',
userToken: 'your-longvan-token',
wsUrl: 'wss://ssh-proxy.dev.longvan.vn'
}, {
showConnectionLogs: true
});
```
### Ưu điểm:
- ✅ **Zero password approach** - không cần biết SSH credentials
- ✅ **Tự động lấy thông tin từ LongVan GraphQL API**
- ✅ **Token-based authentication**
- ✅ **Secure encryption cho user token**
- ✅ **Seamless user experience**
## API
### Props (Vue Components)
| Prop | Kiểu dữ liệu | Bắt buộc | Validation | Mô tả |
|------|--------------|----------|------------|-------|
| computingId | String | Có | Valid computing ID format | LongVan Computing instance ID |
| userToken | String | Có | Valid token format | LongVan user authentication token |
| websocketUrl | String | Có | Valid WebSocket URL | URL của SSH Terminal server |
| options | Object | Không | - | Các tùy chọn cho terminal |
### Web Component Methods (v2.0+)
#### `setConfig(config)`
**Secure method** để set Computing ID configuration:
```javascript
terminal.setConfig({
computingId: '20.5109.3964',
userToken: 'your-longvan-token',
wsUrl: 'wss://ssh-proxy.dev.longvan.vn'
});
```
**Parameters:**
- `config.computingId` (String, required): LongVan Computing instance ID
- `config.userToken` (String, required): LongVan user authentication token (auto-encrypted with RSA)
- `config.wsUrl` (String, required): SSH Terminal server WebSocket URL
**Security Notes:**
- User token is automatically encrypted with RSA before transmission
- No plaintext credentials are ever sent over the network
- Server validates all inputs before processing
### Events
| Event | Tham số | Mô tả |
|-------|---------|-------|
| ready | terminal | Được gọi khi terminal đã sẵn sàng, trả về đối tượng terminal |
| error | { message, code } | Được gọi khi có lỗi xảy ra (authentication, connection, etc.) |
| close | { code, reason } | Được gọi khi connection bị đóng |
| computing-connected | { computingId, host, username } | Được gọi khi Computing ID authentication thành công (v2.0+) |
### Terminal Options
| Option | Kiểu dữ liệu | Mặc định | Mô tả |
|--------|--------------|----------|-------|
| fontSize | Number | 14 | Kích thước font |
| fontFamily | String | 'Menlo, Monaco, "Courier New", monospace' | Font family |
| theme | Object | { background: '#000000', foreground: '#ffffff', ... } | Theme cho terminal |
| cursorBlink | Boolean | true | Bật/tắt nhấp nháy con trỏ |
| cursorStyle | String | 'block' | Kiểu con trỏ ('block', 'underline', 'bar') |
| scrollback | Number | 1000 | Số dòng lưu trong bộ nhớ để cuộn lại |
| reconnection | Object | {...} | Cấu hình auto-reconnection |
| reconnection.enabled | Boolean | true | Bật/tắt auto-reconnection |
| reconnection.maxAttempts | Number | 5 | Số lần thử kết nối lại tối đa |
| reconnection.heartbeatInterval | Number | 30000 | Khoảng thời gian ping (ms) |
## 🐛 Troubleshooting
### Vue Version Compatibility Issues
**Error:** `TypeError: (0 , vue__WEBPACK_IMPORTED_MODULE_4__.openBlock) is not a function`
**Cause:** Using Vue 3 APIs in Vue 2 environment or vice versa.
**Solution:** Use the correct version-specific import:
```javascript
// ✅ For Vue 2 projects
import { SSHTerminal } from 'ssh-terminal/vue2';
import 'ssh-terminal/dist/vue2/ssh-terminal-vue2.css';
// ✅ For Vue 3 projects
import { SSHTerminal } from 'ssh-terminal/vue3';
import 'ssh-terminal/dist/vue3/ssh-terminal-vue3.css';
// ❌ Avoid generic import if you have version conflicts
import { Vue2SSHTerminal, Vue3SSHTerminal } from 'ssh-terminal';
```
### Container Element Required Error
**Error:** `Error: Container element is required`
**Cause:** Terminal container element is not available when `createTerminal` is called.
**Solution:** Ensure the container element exists in DOM:
```javascript
// ✅ Wait for element to be available
onMounted(() => {
const container = terminalContainer.value;
if (container) {
initTerminal();
}
});
// ✅ For Web Component, use setConfig() after element is connected
const terminal = document.getElementById('terminal');
if (terminal) {
terminal.setConfig({
computingId: '20.5109.3964',
userToken: 'your-longvan-token',
wsUrl: 'wss://ssh-proxy.dev.longvan.vn'
});
}
```
### CSS Not Loading
**Problem:** Terminal appears but styling is broken.
**Solution:** Make sure to import the CSS file:
```javascript
// For Vue 2
import 'ssh-terminal/dist/vue2/ssh-terminal-vue2.css';
// For Vue 3
import 'ssh-terminal/dist/vue3/ssh-terminal-vue3.css';
// For generic build
import 'ssh-terminal/dist/ssh-terminal.css';
```
## Đóng góp
Mọi đóng góp đều được chào đón! Vui lòng tạo issue hoặc pull request trên GitHub.
## Giấy phép
MIT