feat: 核心功能升级与安全加固
- 🛡️ 安全(Security): - 彻底修复 IPv6 及保留地址的 SSRF 绕过漏洞。 - 新增基于 IP 的连接频率限制 (10次/分钟) 防范暴力破解。 - 在 localStorage 中通过 Web Crypto (AES-GCM) 对记住的凭证进行加密存储。 - 连接时静默计算并输出服务器 Host Key 指纹 (SHA-256),兼顾安全与易用性。 - ✨ 功能(Feature): - 支持基于 Ed25519 的 SSH 纯文本私钥直接登录。 - 前端终端集成 zmodem.js,支持在终端中运行 rz/sz 实现浏览器与服务器文件的直接拖拽与无缝传输。 - 支持 WebSocket 会话漫游 (Roaming),网络短暂断开可借助 Durable Object 在 15 秒内静默重连恢复会话,免去重新输入密码的繁琐。 - 🎨 体验与界面(UX/UI): - 前端渲染引擎升级为 WebGL (@xterm/addon-webgl),大幅提升海量文本滚动的帧率与流畅度。 - 新增内置极客主题下拉框 (Cyberpunk / Glacier / Gruvbox) 及右键复制粘贴支持。
This commit is contained in:
1 parent
5538c58f97
commit
ff7b65dcd1
13 files changed
+809
-103
No files matched your search
+6
-1
@@ -73,7 +73,12 @@
|
||||
<h1 class="text-2xl font-bold text-primary-container tracking-tighter cursor-pointer">CloudSSH</h1>
|
||||
</div>
|
||||
<div class="flex items-center gap-4 text-primary-container">
|
||||
<button id="disconnect-btn" class="hover:opacity-80 transition-opacity cursor-pointer p-1 flex items-center justify-center">
|
||||
<select id="theme-selector" class="bg-surface-variant text-on-surface-variant text-xs py-1 px-2 border-none outline-none cursor-pointer rounded">
|
||||
<option value="cyberpunk">Cyberpunk</option>
|
||||
<option value="glacier">Glacier</option>
|
||||
<option value="gruvbox">Gruvbox</option>
|
||||
</select>
|
||||
<button id="disconnect-btn" class="hover:opacity-80 transition-opacity cursor-pointer p-1 flex items-center justify-center" title="Disconnect">
|
||||
<span class="material-symbols-outlined" style="font-variation-settings: 'FILL' 0;">power_settings_new</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
Generated
+30
-1
@@ -10,7 +10,9 @@
|
||||
"dependencies": {
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
"@xterm/addon-web-links": "^0.11.0",
|
||||
"@xterm/xterm": "^5.5.0"
|
||||
"@xterm/addon-webgl": "^0.19.0",
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
"zmodem.js": "^0.1.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.7.0",
|
||||
@@ -848,12 +850,30 @@
|
||||
"@xterm/xterm": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-webgl": {
|
||||
"version": "0.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0.tgz",
|
||||
"integrity": "sha512-b3fMOsyLVuCeNJWxolACEUED0vm7qC0cy4wRvf3oURSzDTYVQiGPhTnhWZwIHdvC48Y+oLhvYXnY4XDXPoJo6A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@xterm/xterm": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz",
|
||||
"integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/crc-32": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
|
||||
"integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"crc32": "bin/crc32.njs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.25.12",
|
||||
"dev": true,
|
||||
@@ -1155,6 +1175,15 @@
|
||||
"@rollup/rollup-win32-x64-msvc": "4.62.0",
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/zmodem.js": {
|
||||
"version": "0.1.10",
|
||||
"resolved": "https://registry.npmjs.org/zmodem.js/-/zmodem.js-0.1.10.tgz",
|
||||
"integrity": "sha512-Z1DWngunZ/j3BmIzSJpFZVNV73iHkj89rxXX4IciJdU9ga3nZ7rJ5LkfjV/QDsKhc7bazDWTTJCLJ+iRXD82dw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"crc-32": "^1.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,9 +8,11 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
"@xterm/addon-web-links": "^0.11.0"
|
||||
"@xterm/addon-web-links": "^0.11.0",
|
||||
"@xterm/addon-webgl": "^0.19.0",
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
"zmodem.js": "^0.1.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.7.0",
|
||||
|
||||
+138
-8
@@ -1,11 +1,53 @@
|
||||
import { SSHTerminal } from './terminal';
|
||||
|
||||
// --- Credential encryption helpers ---
|
||||
async function deriveKey(salt: Uint8Array): Promise<CryptoKey> {
|
||||
const raw = new TextEncoder().encode(window.location.origin + ':cloudssh');
|
||||
const baseKey = await crypto.subtle.importKey('raw', raw, 'PBKDF2', false, ['deriveKey']);
|
||||
return crypto.subtle.deriveKey(
|
||||
{ name: 'PBKDF2', salt: salt as any, iterations: 100000, hash: 'SHA-256' },
|
||||
baseKey,
|
||||
{ name: 'AES-GCM', length: 256 },
|
||||
false,
|
||||
['encrypt', 'decrypt']
|
||||
);
|
||||
}
|
||||
|
||||
async function encryptCredentials(data: object): Promise<string> {
|
||||
const salt = crypto.getRandomValues(new Uint8Array(16));
|
||||
const iv = crypto.getRandomValues(new Uint8Array(12));
|
||||
const key = await deriveKey(salt);
|
||||
const encoded = new TextEncoder().encode(JSON.stringify(data));
|
||||
const encrypted = new Uint8Array(await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, encoded));
|
||||
// Format: base64(salt + iv + ciphertext)
|
||||
const combined = new Uint8Array(salt.length + iv.length + encrypted.length);
|
||||
combined.set(salt, 0);
|
||||
combined.set(iv, salt.length);
|
||||
combined.set(encrypted, salt.length + iv.length);
|
||||
return btoa(String.fromCharCode(...combined));
|
||||
}
|
||||
|
||||
async function decryptCredentials(stored: string): Promise<{ host: string; port: string; username: string; password: string } | null> {
|
||||
try {
|
||||
const raw = Uint8Array.from(atob(stored), c => c.charCodeAt(0));
|
||||
const salt = raw.slice(0, 16);
|
||||
const iv = raw.slice(16, 28);
|
||||
const data = raw.slice(28);
|
||||
const key = await deriveKey(salt);
|
||||
const decrypted = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, data);
|
||||
return JSON.parse(new TextDecoder().decode(decrypted));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export class ConnectionForm {
|
||||
private terminal: SSHTerminal;
|
||||
|
||||
constructor(terminal: SSHTerminal) {
|
||||
this.terminal = terminal;
|
||||
this.render();
|
||||
this.loadSavedCredentials();
|
||||
}
|
||||
|
||||
private render(): void {
|
||||
@@ -37,13 +79,26 @@ export class ConnectionForm {
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-bold tracking-[0.1em] text-[#bbccb0] mb-2">AUTH_KEY</label>
|
||||
<div class="flex items-center">
|
||||
<span class="material-symbols-outlined text-[#bbccb0] mr-2" style="font-size: 16px;">key</span>
|
||||
<input id="password" class="terminal-input text-[13px]" placeholder="••••••••" type="password" required>
|
||||
<label class="block text-xs font-bold tracking-[0.1em] text-[#bbccb0] mb-2">AUTH_METHOD</label>
|
||||
<div class="flex gap-2 mb-3">
|
||||
<button type="button" id="auth-tab-password" class="auth-tab px-3 py-1 text-[11px] font-bold tracking-[0.1em] border border-[#4af626] text-[#4af626] bg-transparent cursor-pointer transition-all" style="background:#4af626;color:#0a0a0a;">PASSWORD</button>
|
||||
<button type="button" id="auth-tab-key" class="auth-tab px-3 py-1 text-[11px] font-bold tracking-[0.1em] border border-[#3c4b36] text-[#bbccb0] bg-transparent cursor-pointer transition-all">PRIVATE_KEY</button>
|
||||
</div>
|
||||
<div id="auth-password-section">
|
||||
<div class="flex items-center">
|
||||
<span class="material-symbols-outlined text-[#bbccb0] mr-2" style="font-size: 16px;">key</span>
|
||||
<input id="password" class="terminal-input text-[13px]" placeholder="••••••••" type="password">
|
||||
</div>
|
||||
</div>
|
||||
<div id="auth-key-section" style="display:none;">
|
||||
<textarea id="private-key" class="terminal-input text-[11px] w-full" rows="5" placeholder="-----BEGIN OPENSSH PRIVATE KEY----- ...粘贴 Ed25519 私钥内容... -----END OPENSSH PRIVATE KEY-----" style="resize:vertical;border:1px solid #3c4b36;padding:8px;"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pt-6">
|
||||
<div class="flex items-center gap-2 mt-2">
|
||||
<input type="checkbox" id="remember-me" class="accent-[#4af626] w-4 h-4 cursor-pointer">
|
||||
<label for="remember-me" class="text-xs text-[#bbccb0] cursor-pointer select-none">REMEMBER_CONNECTION</label>
|
||||
</div>
|
||||
<div class="pt-4">
|
||||
<button id="connect-btn" class="cyber-button w-full py-3 px-4 text-xs font-bold tracking-[0.1em] uppercase flex items-center justify-center gap-2 bg-[#4af626] text-[#022100]" type="button">
|
||||
<span class="material-symbols-outlined" style="font-size: 18px;">power_settings_new</span>
|
||||
Execute_Connection
|
||||
@@ -64,6 +119,60 @@ export class ConnectionForm {
|
||||
document.getElementById('connection-form')!.addEventListener('keypress', (e) => {
|
||||
if (e.key === 'Enter') this.handleConnect();
|
||||
});
|
||||
|
||||
// Auth method tab switching
|
||||
document.getElementById('auth-tab-password')!.addEventListener('click', () => {
|
||||
this.setAuthMode('password');
|
||||
});
|
||||
document.getElementById('auth-tab-key')!.addEventListener('click', () => {
|
||||
this.setAuthMode('key');
|
||||
});
|
||||
}
|
||||
|
||||
private authMode: 'password' | 'key' = 'password';
|
||||
|
||||
private setAuthMode(mode: 'password' | 'key'): void {
|
||||
this.authMode = mode;
|
||||
const pwTab = document.getElementById('auth-tab-password')!;
|
||||
const keyTab = document.getElementById('auth-tab-key')!;
|
||||
const pwSection = document.getElementById('auth-password-section')!;
|
||||
const keySection = document.getElementById('auth-key-section')!;
|
||||
|
||||
if (mode === 'password') {
|
||||
pwTab.style.background = '#4af626'; pwTab.style.color = '#0a0a0a';
|
||||
pwTab.style.borderColor = '#4af626';
|
||||
keyTab.style.background = 'transparent'; keyTab.style.color = '#bbccb0';
|
||||
keyTab.style.borderColor = '#3c4b36';
|
||||
pwSection.style.display = ''; keySection.style.display = 'none';
|
||||
} else {
|
||||
keyTab.style.background = '#4af626'; keyTab.style.color = '#0a0a0a';
|
||||
keyTab.style.borderColor = '#4af626';
|
||||
pwTab.style.background = 'transparent'; pwTab.style.color = '#bbccb0';
|
||||
pwTab.style.borderColor = '#3c4b36';
|
||||
keySection.style.display = ''; pwSection.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
private async loadSavedCredentials(): Promise<void> {
|
||||
const stored = localStorage.getItem('cloudssh_cred');
|
||||
if (!stored) return;
|
||||
const cred = await decryptCredentials(stored);
|
||||
if (!cred) {
|
||||
localStorage.removeItem('cloudssh_cred');
|
||||
return;
|
||||
}
|
||||
(document.getElementById('host') as HTMLInputElement).value = cred.host || '';
|
||||
(document.getElementById('port') as HTMLInputElement).value = cred.port || '22';
|
||||
(document.getElementById('username') as HTMLInputElement).value = cred.username || '';
|
||||
(document.getElementById('password') as HTMLInputElement).value = cred.password || '';
|
||||
(document.getElementById('private-key') as HTMLTextAreaElement).value = (cred as any).privateKey || '';
|
||||
(document.getElementById('remember-me') as HTMLInputElement).checked = true;
|
||||
|
||||
if ((cred as any).authMethod === 'key') {
|
||||
this.setAuthMode('key');
|
||||
} else {
|
||||
this.setAuthMode('password');
|
||||
}
|
||||
}
|
||||
|
||||
private async handleConnect(): Promise<void> {
|
||||
@@ -73,12 +182,32 @@ export class ConnectionForm {
|
||||
);
|
||||
const username = (document.getElementById('username') as HTMLInputElement).value;
|
||||
const password = (document.getElementById('password') as HTMLInputElement).value;
|
||||
const privateKey = (document.getElementById('private-key') as HTMLTextAreaElement).value;
|
||||
const remember = (document.getElementById('remember-me') as HTMLInputElement).checked;
|
||||
|
||||
if (!host || !username || !password) {
|
||||
alert('请填写所有必填字段');
|
||||
if (!host || !username) {
|
||||
alert('请填写主机名和用户名');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.authMode === 'password' && !password) {
|
||||
alert('请输入密码');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.authMode === 'key' && !privateKey) {
|
||||
alert('请粘贴私钥内容');
|
||||
return;
|
||||
}
|
||||
|
||||
// Save or clear credentials
|
||||
if (remember) {
|
||||
const encrypted = await encryptCredentials({ host, port: port.toString(), username, password, privateKey, authMethod: this.authMode === 'key' ? 'publickey' : 'password' });
|
||||
localStorage.setItem('cloudssh_cred', encrypted);
|
||||
} else {
|
||||
localStorage.removeItem('cloudssh_cred');
|
||||
}
|
||||
|
||||
const authSection = document.getElementById('auth-section')!;
|
||||
const termSection = document.getElementById('terminal-section')!;
|
||||
|
||||
@@ -93,7 +222,7 @@ export class ConnectionForm {
|
||||
this.terminal.mount();
|
||||
|
||||
try {
|
||||
await this.terminal.connect({ host, port, username, password });
|
||||
await this.terminal.connect({ host, port, username, password, authMethod: this.authMode === 'key' ? 'publickey' : 'password', privateKey });
|
||||
} catch (error) {
|
||||
termSection.classList.add('hidden');
|
||||
termSection.classList.remove('flex');
|
||||
@@ -102,3 +231,4 @@ export class ConnectionForm {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,11 @@ document.getElementById('disconnect-btn')?.addEventListener('click', () => {
|
||||
document.getElementById('status-text')!.innerHTML = '<span class="w-2 h-2 bg-[#353534] inline-block"></span> STATUS: OFFLINE';
|
||||
});
|
||||
|
||||
document.getElementById('theme-selector')?.addEventListener('change', (e) => {
|
||||
const theme = (e.target as HTMLSelectElement).value as any;
|
||||
terminal.setTheme(theme);
|
||||
});
|
||||
|
||||
// Set current year for copyright
|
||||
const copyrightYearSpan = document.getElementById('copyright-year');
|
||||
if (copyrightYearSpan) {
|
||||
|
||||
+148
-43
@@ -1,20 +1,54 @@
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
import { FitAddon } from '@xterm/addon-fit';
|
||||
import { WebLinksAddon } from '@xterm/addon-web-links';
|
||||
import { WebglAddon } from '@xterm/addon-webgl';
|
||||
import { ZmodemHandler } from './zmodem-handler';
|
||||
import '@xterm/xterm/css/xterm.css';
|
||||
|
||||
export interface SSHConnectionConfig {
|
||||
host: string;
|
||||
port: number;
|
||||
username: string;
|
||||
password: string;
|
||||
password?: string;
|
||||
authMethod?: 'password' | 'publickey';
|
||||
privateKey?: string;
|
||||
}
|
||||
|
||||
export const THEMES = {
|
||||
cyberpunk: {
|
||||
background: '#0a0a0a',
|
||||
foreground: '#4af626',
|
||||
cursor: '#14d1ff',
|
||||
cursorAccent: '#0a0a0a',
|
||||
selectionBackground: '#273747',
|
||||
},
|
||||
glacier: {
|
||||
background: '#0a192f',
|
||||
foreground: '#64ffda',
|
||||
cursor: '#e6f1ff',
|
||||
cursorAccent: '#0a192f',
|
||||
selectionBackground: '#112240',
|
||||
},
|
||||
gruvbox: {
|
||||
background: '#282828',
|
||||
foreground: '#ebdbb2',
|
||||
cursor: '#d3869b',
|
||||
cursorAccent: '#282828',
|
||||
selectionBackground: '#504945',
|
||||
}
|
||||
};
|
||||
|
||||
export class SSHTerminal {
|
||||
private terminal: Terminal;
|
||||
private fitAddon: FitAddon;
|
||||
private webglAddon!: WebglAddon;
|
||||
private ws: WebSocket | null = null;
|
||||
private container: HTMLElement;
|
||||
private config: SSHConnectionConfig | null = null;
|
||||
private roamId: string | null = null;
|
||||
private reconnecting: boolean = false;
|
||||
private reconnectAttempts: number = 0;
|
||||
private maxReconnectAttempts: number = 5;
|
||||
|
||||
constructor(containerId: string) {
|
||||
this.container = document.getElementById(containerId)!;
|
||||
@@ -24,29 +58,7 @@ export class SSHTerminal {
|
||||
cursorStyle: 'block',
|
||||
fontSize: 14,
|
||||
fontFamily: '"JetBrains Mono", "Fira Code", "Cascadia Code", monospace',
|
||||
theme: {
|
||||
background: '#0a0a0a',
|
||||
foreground: '#4af626',
|
||||
cursor: '#14d1ff',
|
||||
cursorAccent: '#0a0a0a',
|
||||
selectionBackground: '#273747',
|
||||
black: '#01060e',
|
||||
red: '#ea6c73',
|
||||
green: '#91b362',
|
||||
yellow: '#f9af4f',
|
||||
blue: '#53bdfa',
|
||||
magenta: '#fae994',
|
||||
cyan: '#90e1c6',
|
||||
white: '#c7c7c7',
|
||||
brightBlack: '#686868',
|
||||
brightRed: '#f07178',
|
||||
brightGreen: '#c2d94c',
|
||||
brightYellow: '#ffb454',
|
||||
brightBlue: '#59c2ff',
|
||||
brightMagenta: '#ffee99',
|
||||
brightCyan: '#95e6cb',
|
||||
brightWhite: '#ffffff',
|
||||
},
|
||||
theme: THEMES.cyberpunk,
|
||||
allowProposedApi: true,
|
||||
scrollback: 10000,
|
||||
});
|
||||
@@ -56,50 +68,143 @@ export class SSHTerminal {
|
||||
this.terminal.loadAddon(new WebLinksAddon());
|
||||
|
||||
window.addEventListener('resize', () => this.fit());
|
||||
|
||||
// Right-click paste support
|
||||
this.container.addEventListener('contextmenu', async (e) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
const text = await navigator.clipboard.readText();
|
||||
if (text && this.ws?.readyState === WebSocket.OPEN) {
|
||||
this.ws.send(text);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to read clipboard', err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setTheme(themeName: keyof typeof THEMES): void {
|
||||
this.terminal.options.theme = THEMES[themeName];
|
||||
}
|
||||
|
||||
mount(): void {
|
||||
this.terminal.open(this.container);
|
||||
|
||||
// Load WebGL addon after terminal is opened
|
||||
try {
|
||||
this.webglAddon = new WebglAddon();
|
||||
this.webglAddon.onContextLoss(e => {
|
||||
console.warn('WebGL context lost', e);
|
||||
this.webglAddon.dispose();
|
||||
});
|
||||
this.terminal.loadAddon(this.webglAddon);
|
||||
} catch (e) {
|
||||
console.warn('WebGL addon failed to load, falling back to canvas/dom', e);
|
||||
}
|
||||
|
||||
this.fit();
|
||||
|
||||
this.terminal.writeln('\x1b[1;33m╔══════════════════════════════════╗\x1b[0m');
|
||||
this.terminal.writeln('\x1b[1;33m║ CloudSSH Web Terminal ║\x1b[0m');
|
||||
this.terminal.writeln('\x1b[1;33m║ Powered by Cloudflare ║\x1b[0m');
|
||||
this.terminal.writeln('\x1b[1;33m║ Connecting to CloudSSH ║\x1b[0m');
|
||||
this.terminal.writeln('\x1b[1;33m╚══════════════════════════════════╝\x1b[0m');
|
||||
this.terminal.writeln('');
|
||||
}
|
||||
|
||||
async connect(config: SSHConnectionConfig): Promise<void> {
|
||||
this.terminal.writeln(
|
||||
`\x1b[1;33m[*] Connecting to ${config.username}@${config.host}:${config.port}...\x1b[0m`
|
||||
);
|
||||
private attemptReconnect(): void {
|
||||
if (this.reconnectAttempts >= this.maxReconnectAttempts || !this.roamId || !this.config) {
|
||||
this.terminal.writeln('\x1b[31m[!] 连接已断开,无法恢复。\x1b[0m');
|
||||
document.getElementById('status-text')!.innerHTML = '<span class="w-2 h-2 bg-[#ea6c73] inline-block"></span> STATUS: DISCONNECTED';
|
||||
return;
|
||||
}
|
||||
|
||||
const proto = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const wsUrl = `${proto}//${window.location.host}/api/ssh`;
|
||||
this.reconnecting = true;
|
||||
this.reconnectAttempts++;
|
||||
this.terminal.writeln(`\x1b[33m[*] 正在尝试恢复连接 (${this.reconnectAttempts}/${this.maxReconnectAttempts})...\x1b[0m`);
|
||||
|
||||
// Slight delay before reconnecting
|
||||
setTimeout(() => {
|
||||
this.connect(this.config!, this.roamId!);
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
async connect(config: SSHConnectionConfig, withRoamId?: string): Promise<void> {
|
||||
if (!withRoamId) {
|
||||
this.config = config;
|
||||
this.reconnectAttempts = 0;
|
||||
this.roamId = null;
|
||||
}
|
||||
|
||||
const wsUrl = new URL(window.location.href);
|
||||
wsUrl.protocol = wsUrl.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
wsUrl.pathname = '/api/ssh';
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.ws = new WebSocket(wsUrl);
|
||||
this.ws = new WebSocket(wsUrl.toString());
|
||||
|
||||
this.ws.onopen = () => {
|
||||
this.terminal.writeln('\x1b[32m[+] WebSocket connected, sending credentials...\x1b[0m');
|
||||
this.ws?.send(
|
||||
JSON.stringify({
|
||||
host: config.host,
|
||||
port: config.port,
|
||||
username: config.username,
|
||||
password: config.password,
|
||||
})
|
||||
);
|
||||
if (!withRoamId) {
|
||||
this.terminal.writeln('\x1b[32m[+] WebSocket connected, sending credentials...\x1b[0m');
|
||||
}
|
||||
|
||||
const payload = withRoamId
|
||||
? { roamId: withRoamId }
|
||||
: {
|
||||
host: config.host,
|
||||
port: config.port,
|
||||
username: config.username,
|
||||
password: config.password,
|
||||
authMethod: config.authMethod,
|
||||
privateKey: config.privateKey,
|
||||
};
|
||||
|
||||
this.ws?.send(JSON.stringify(payload));
|
||||
|
||||
if (withRoamId) {
|
||||
this.reconnectAttempts = 0;
|
||||
this.reconnecting = false;
|
||||
}
|
||||
|
||||
resolve();
|
||||
};
|
||||
|
||||
this.ws.onerror = () => {
|
||||
if (!this.reconnecting) {
|
||||
reject(new Error('WebSocket connection failed'));
|
||||
}
|
||||
};
|
||||
|
||||
this.ws.onclose = () => {
|
||||
if (!this.reconnecting && this.roamId) {
|
||||
this.attemptReconnect();
|
||||
} else if (!this.reconnecting) {
|
||||
this.terminal.writeln('\x1b[31m[-] 连接已关闭\x1b[0m');
|
||||
document.getElementById('status-text')!.innerHTML = '<span class="w-2 h-2 bg-[#353534] inline-block"></span> STATUS: OFFLINE';
|
||||
}
|
||||
};
|
||||
|
||||
// Zmodem support
|
||||
const zmodemHandler = new ZmodemHandler(
|
||||
(data) => this.terminal.write(data),
|
||||
(data) => {
|
||||
if (this.ws?.readyState === WebSocket.OPEN) {
|
||||
this.ws.send(data);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
this.ws.onmessage = (event) => {
|
||||
if (typeof event.data === 'string') {
|
||||
try {
|
||||
const msg = JSON.parse(event.data);
|
||||
switch (msg.type) {
|
||||
case 'roamId':
|
||||
this.roamId = msg.roamId;
|
||||
break;
|
||||
case 'status':
|
||||
this.terminal.writeln(`\x1b[32m[*] ${msg.message}\x1b[0m`);
|
||||
if (msg.message === '认证成功' || msg.message === '会话已恢复') {
|
||||
document.getElementById('status-text')!.innerHTML = '<span class="w-2 h-2 bg-[#4af626] inline-block animate-pulse"></span> STATUS: ONLINE';
|
||||
}
|
||||
break;
|
||||
case 'error':
|
||||
this.terminal.writeln(`\x1b[31m[!] ${msg.message}\x1b[0m`);
|
||||
@@ -111,7 +216,7 @@ export class SSHTerminal {
|
||||
} else {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
this.terminal.write(new Uint8Array(reader.result as ArrayBuffer));
|
||||
zmodemHandler.consume(reader.result as ArrayBuffer);
|
||||
};
|
||||
reader.readAsArrayBuffer(event.data);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
// @ts-ignore
|
||||
import * as Zmodem from 'zmodem.js/src/zmodem_browser';
|
||||
|
||||
export class ZmodemHandler {
|
||||
private sentry: any;
|
||||
private session: any | null = null;
|
||||
private onTerminalWrite: (data: Uint8Array) => void;
|
||||
private onWsSend: (data: Uint8Array) => void;
|
||||
|
||||
constructor(
|
||||
onTerminalWrite: (data: Uint8Array) => void,
|
||||
onWsSend: (data: Uint8Array) => void
|
||||
) {
|
||||
this.onTerminalWrite = onTerminalWrite;
|
||||
this.onWsSend = onWsSend;
|
||||
this.init();
|
||||
}
|
||||
|
||||
private init() {
|
||||
this.sentry = new Zmodem.Sentry({
|
||||
to_terminal: (octets: Array<number>) => {
|
||||
this.onTerminalWrite(new Uint8Array(octets));
|
||||
},
|
||||
sender: (octets: Array<number>) => {
|
||||
this.onWsSend(new Uint8Array(octets));
|
||||
},
|
||||
on_detect: (detection: any) => {
|
||||
const zsession = detection.confirm();
|
||||
this.session = zsession;
|
||||
|
||||
if (zsession.type === 'receive') {
|
||||
this.handleReceive(zsession);
|
||||
} else {
|
||||
this.handleSend(zsession);
|
||||
}
|
||||
},
|
||||
on_retract: () => {
|
||||
this.session = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public consume(data: ArrayBuffer) {
|
||||
this.sentry.consume(data);
|
||||
}
|
||||
|
||||
private handleReceive(zsession: any) {
|
||||
zsession.on('offer', (xfer: any) => {
|
||||
const name = xfer.get_details().name;
|
||||
const size = xfer.get_details().size;
|
||||
|
||||
const accept = confirm("接受来自服务器的文件下载?\n" + name + " (" + size + " bytes)");
|
||||
if (!accept) {
|
||||
xfer.skip();
|
||||
return;
|
||||
}
|
||||
|
||||
const packets: Array<Uint8Array> = [];
|
||||
xfer.on('input', (payload: Uint8Array) => {
|
||||
packets.push(payload);
|
||||
});
|
||||
|
||||
xfer.accept().then(() => {
|
||||
Zmodem.Browser.save_to_disk(packets, name);
|
||||
});
|
||||
});
|
||||
|
||||
zsession.start();
|
||||
}
|
||||
|
||||
private handleSend(zsession: any) {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.multiple = true;
|
||||
input.style.display = 'none';
|
||||
|
||||
input.onchange = (e) => {
|
||||
const files = (e.target as HTMLInputElement).files;
|
||||
if (files && files.length > 0) {
|
||||
Zmodem.Browser.send_files(zsession, files, {
|
||||
on_offer_response: (file: any, xfer: any) => {
|
||||
if (!xfer) console.warn('Server rejected file:', file.name);
|
||||
},
|
||||
on_progress: (file: any, xfer: any, chunk: any) => {
|
||||
// Can implement progress bar here
|
||||
},
|
||||
on_file_complete: (file: any) => {
|
||||
console.log('Finished uploading:', file.name);
|
||||
}
|
||||
}).then(() => {
|
||||
zsession.close();
|
||||
});
|
||||
} else {
|
||||
zsession.close();
|
||||
}
|
||||
};
|
||||
|
||||
document.body.appendChild(input);
|
||||
input.click();
|
||||
document.body.removeChild(input);
|
||||
}
|
||||
}
|
||||
+131
@@ -18,6 +18,137 @@ export class SSHAuth {
|
||||
return concat(...parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a public key auth request for Ed25519 keys (RFC 4252 §7).
|
||||
* The signature covers: session_id_string || SSH_MSG_USERAUTH_REQUEST || user || service || "publickey" || TRUE || "ssh-ed25519" || pubkey_blob
|
||||
*/
|
||||
static async buildPublicKeyAuthRequest(
|
||||
username: string,
|
||||
privateKeyPEM: string,
|
||||
sessionID: Uint8Array
|
||||
): Promise<Uint8Array> {
|
||||
const { signingKey, publicKeyBlob } = await this.parseEd25519PrivateKey(privateKeyPEM);
|
||||
|
||||
// Build the request body (without signature first)
|
||||
const requestBody = concat(
|
||||
new Uint8Array([SSH_MSG_USERAUTH_REQUEST]),
|
||||
encodeString(username),
|
||||
encodeString('ssh-connection'),
|
||||
encodeString('publickey'),
|
||||
new Uint8Array([0x01]), // TRUE = has signature
|
||||
encodeString('ssh-ed25519'),
|
||||
encodeString(publicKeyBlob),
|
||||
);
|
||||
|
||||
// Data to sign: session_id_string || request_body
|
||||
const dataToSign = concat(encodeString(sessionID), requestBody);
|
||||
|
||||
// Sign with Ed25519
|
||||
const rawSignature = new Uint8Array(await crypto.subtle.sign('Ed25519', signingKey, dataToSign));
|
||||
|
||||
// SSH signature blob: string "ssh-ed25519" || string signature
|
||||
const signatureBlob = concat(
|
||||
encodeString('ssh-ed25519'),
|
||||
encodeString(rawSignature),
|
||||
);
|
||||
|
||||
// Full auth packet: requestBody || string signature_blob
|
||||
return concat(requestBody, encodeString(signatureBlob));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an OpenSSH Ed25519 PEM private key.
|
||||
* Supports OPENSSH PRIVATE KEY format.
|
||||
*/
|
||||
private static async parseEd25519PrivateKey(pem: string): Promise<{ signingKey: CryptoKey; publicKeyBlob: Uint8Array }> {
|
||||
const lines = pem.trim().split('\n');
|
||||
const b64 = lines.filter(l => !l.startsWith('-----')).join('');
|
||||
const raw = Uint8Array.from(atob(b64), c => c.charCodeAt(0));
|
||||
|
||||
// Parse OpenSSH format: "openssh-key-v1\0" magic
|
||||
const magic = 'openssh-key-v1\0';
|
||||
const magicBytes = new TextEncoder().encode(magic);
|
||||
for (let i = 0; i < magicBytes.length; i++) {
|
||||
if (raw[i] !== magicBytes[i]) {
|
||||
throw new Error('不支持的私钥格式,仅支持 OpenSSH Ed25519 密钥');
|
||||
}
|
||||
}
|
||||
let offset = magicBytes.length;
|
||||
|
||||
// ciphername
|
||||
const cipherLen = readUint32(raw, offset); offset += 4;
|
||||
const cipher = new TextDecoder().decode(raw.slice(offset, offset + cipherLen)); offset += cipherLen;
|
||||
if (cipher !== 'none') throw new Error('不支持加密的私钥,请使用 ssh-keygen -p 移除密码');
|
||||
|
||||
// kdfname
|
||||
const kdfLen = readUint32(raw, offset); offset += 4; offset += kdfLen;
|
||||
// kdfoptions
|
||||
const kdfOptLen = readUint32(raw, offset); offset += 4; offset += kdfOptLen;
|
||||
// number of keys
|
||||
const numKeys = readUint32(raw, offset); offset += 4;
|
||||
if (numKeys !== 1) throw new Error('仅支持单密钥文件');
|
||||
|
||||
// public key section
|
||||
const pubSecLen = readUint32(raw, offset); offset += 4; offset += pubSecLen;
|
||||
|
||||
// private key section
|
||||
const privSecLen = readUint32(raw, offset); offset += 4;
|
||||
const privSection = raw.slice(offset, offset + privSecLen);
|
||||
|
||||
// Parse private section: checkint1, checkint2, keytype, pubkey, privkey, comment
|
||||
let po = 0;
|
||||
po += 4; // checkint1
|
||||
po += 4; // checkint2
|
||||
|
||||
// key type
|
||||
const ktLen = readUint32(privSection, po); po += 4;
|
||||
const keyType = new TextDecoder().decode(privSection.slice(po, po + ktLen)); po += ktLen;
|
||||
if (keyType !== 'ssh-ed25519') throw new Error(`不支持的密钥类型: ${keyType},仅支持 ssh-ed25519`);
|
||||
|
||||
// public key (32 bytes)
|
||||
const pubKeyLen = readUint32(privSection, po); po += 4;
|
||||
const pubKeyRaw = privSection.slice(po, po + pubKeyLen); po += pubKeyLen;
|
||||
|
||||
// private key (64 bytes = 32 bytes seed + 32 bytes pubkey)
|
||||
const privKeyLen = readUint32(privSection, po); po += 4;
|
||||
const privKeyRaw = privSection.slice(po, po + privKeyLen);
|
||||
// Ed25519 seed is first 32 bytes
|
||||
const seed = privKeyRaw.slice(0, 32);
|
||||
|
||||
// Import as PKCS8 for Web Crypto (build PKCS8 wrapper for Ed25519 seed)
|
||||
const pkcs8 = this.buildEd25519PKCS8(seed);
|
||||
const signingKey = await crypto.subtle.importKey(
|
||||
'pkcs8', pkcs8, { name: 'Ed25519' }, false, ['sign']
|
||||
);
|
||||
|
||||
// Build SSH public key blob: string "ssh-ed25519" || string pubkey
|
||||
const publicKeyBlob = concat(
|
||||
encodeString('ssh-ed25519'),
|
||||
encodeString(pubKeyRaw),
|
||||
);
|
||||
|
||||
return { signingKey, publicKeyBlob };
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap a 32-byte Ed25519 seed into PKCS8 DER format for Web Crypto import.
|
||||
*/
|
||||
private static buildEd25519PKCS8(seed: Uint8Array): Uint8Array {
|
||||
// PKCS8 structure for Ed25519:
|
||||
// SEQUENCE {
|
||||
// INTEGER 0 (version)
|
||||
// SEQUENCE { OID 1.3.101.112 (Ed25519) }
|
||||
// OCTET STRING { OCTET STRING { seed } }
|
||||
// }
|
||||
const oid = new Uint8Array([0x06, 0x03, 0x2b, 0x65, 0x70]); // OID 1.3.101.112
|
||||
const seedOctet = new Uint8Array([0x04, seed.length, ...seed]);
|
||||
const innerOctet = new Uint8Array([0x04, seedOctet.length, ...seedOctet]);
|
||||
const algoSeq = new Uint8Array([0x30, oid.length, ...oid]);
|
||||
const version = new Uint8Array([0x02, 0x01, 0x00]);
|
||||
const totalLen = version.length + algoSeq.length + innerOctet.length;
|
||||
return new Uint8Array([0x30, totalLen, ...version, ...algoSeq, ...innerOctet]);
|
||||
}
|
||||
|
||||
static handleResponse(payload: Uint8Array): AuthResult {
|
||||
const msgType = payload[0];
|
||||
|
||||
|
||||
@@ -44,6 +44,8 @@ export interface SSHConnectionConfig {
|
||||
port: number;
|
||||
username: string;
|
||||
password: string;
|
||||
authMethod?: 'password' | 'publickey';
|
||||
privateKey?: string;
|
||||
}
|
||||
|
||||
export interface Env {
|
||||
|
||||
@@ -1,10 +1,45 @@
|
||||
import { Env, SSHConnectionConfig } from '../types';
|
||||
import { SSHSession } from './ssh-session';
|
||||
|
||||
/**
|
||||
* SSRF 防护:检测目标主机是否为内网、保留或特殊地址。
|
||||
* 覆盖 IPv4 私有段、IPv6 回环/链路本地/私有段、IPv4-mapped IPv6 等。
|
||||
*/
|
||||
function isBlockedHost(host: string): boolean {
|
||||
const h = host.toLowerCase().trim();
|
||||
|
||||
// 特殊主机名
|
||||
if (h === 'localhost' || h === 'ip6-localhost' || h === 'ip6-loopback') return true;
|
||||
|
||||
// IPv4 私有 / 保留地址
|
||||
if (/^(127\.|10\.|0\.|192\.168\.|169\.254\.)/.test(h)) return true;
|
||||
if (/^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(h)) return true;
|
||||
|
||||
// 移除 IPv6 方括号 (e.g. [::1])
|
||||
const v6 = h.replace(/^\[|\]$/g, '');
|
||||
|
||||
// IPv6 回环
|
||||
if (v6 === '::1' || v6 === '0:0:0:0:0:0:0:1') return true;
|
||||
// IPv6 未指定地址
|
||||
if (v6 === '::' || v6 === '0:0:0:0:0:0:0:0') return true;
|
||||
// IPv6 链路本地 (fe80::/10)
|
||||
if (/^fe[89ab]/i.test(v6)) return true;
|
||||
// IPv6 唯一本地 (fc00::/7)
|
||||
if (/^f[cd]/i.test(v6)) return true;
|
||||
// IPv4-mapped IPv6 (::ffff:127.0.0.1 等)
|
||||
const v4mapped = v6.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/i);
|
||||
if (v4mapped) return isBlockedHost(v4mapped[1]);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export class SSHSessionDO {
|
||||
private state: DurableObjectState;
|
||||
private env: Env;
|
||||
private sessions: Map<WebSocket, SSHSession> = new Map();
|
||||
private activeSessionsByRoamId: Map<string, SSHSession> = new Map();
|
||||
private roamTimeouts: Map<string, ReturnType<typeof setTimeout>> = new Map();
|
||||
private _pendingTimeouts: Map<WebSocket, ReturnType<typeof setTimeout>> = new Map();
|
||||
|
||||
constructor(state: DurableObjectState, env: Env) {
|
||||
this.state = state;
|
||||
@@ -42,27 +77,15 @@ export class SSHSessionDO {
|
||||
} as any);
|
||||
}
|
||||
|
||||
private _pendingTimeouts: Map<WebSocket, ReturnType<typeof setTimeout>> = new Map();
|
||||
|
||||
// Hibernation API: called when a WebSocket message is received
|
||||
// Hibernation API: called when a WebSocket receives a message
|
||||
async webSocketMessage(ws: WebSocket, message: string | ArrayBuffer): Promise<void> {
|
||||
const session = this.sessions.get(ws);
|
||||
|
||||
if (session) {
|
||||
// Session already established, forward input
|
||||
try {
|
||||
if (typeof message === 'string') {
|
||||
await session.handleWebSocketMessage(message);
|
||||
} else {
|
||||
await session.handleWebSocketMessage(message);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[WS] Input error:', e instanceof Error ? e.message : String(e));
|
||||
}
|
||||
await session.handleWebSocketMessage(message);
|
||||
return;
|
||||
}
|
||||
|
||||
// No session yet — this should be the credentials message
|
||||
// This is the first message (credentials or roam reconnect)
|
||||
const timeout = this._pendingTimeouts.get(ws);
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
@@ -70,15 +93,31 @@ export class SSHSessionDO {
|
||||
}
|
||||
|
||||
try {
|
||||
const config = JSON.parse(message as string) as SSHConnectionConfig;
|
||||
const config = JSON.parse(message as string);
|
||||
|
||||
if (!config.host || !config.username || !config.password) {
|
||||
if (config.roamId) {
|
||||
// Attempt to resume session
|
||||
const existingSession = this.activeSessionsByRoamId.get(config.roamId);
|
||||
if (existingSession) {
|
||||
const roamTimeout = this.roamTimeouts.get(config.roamId);
|
||||
if (roamTimeout) {
|
||||
clearTimeout(roamTimeout);
|
||||
this.roamTimeouts.delete(config.roamId);
|
||||
}
|
||||
this.sessions.set(ws, existingSession);
|
||||
existingSession.updateWebSocket(ws);
|
||||
return;
|
||||
}
|
||||
// If roam session not found, fall back to new connection if config provided
|
||||
}
|
||||
|
||||
if (!config.host || !config.username || (!config.password && !config.privateKey)) {
|
||||
ws.send(JSON.stringify({ type: 'error', message: 'Missing credentials' }));
|
||||
ws.close(1011, 'Invalid credentials');
|
||||
return;
|
||||
}
|
||||
|
||||
await this.initSSHSession(ws, config);
|
||||
await this.initSSHSession(ws, config as SSHConnectionConfig);
|
||||
} catch (e) {
|
||||
ws.send(JSON.stringify({ type: 'error', message: 'Invalid credentials format' }));
|
||||
ws.close(1011, 'Invalid format');
|
||||
@@ -89,8 +128,15 @@ export class SSHSessionDO {
|
||||
async webSocketClose(ws: WebSocket, code: number, reason: string, wasClean: boolean): Promise<void> {
|
||||
const session = this.sessions.get(ws);
|
||||
if (session) {
|
||||
session.close();
|
||||
this.sessions.delete(ws);
|
||||
// Wait 15s for roam reconnect before closing session
|
||||
const roamId = session.roamId;
|
||||
const timeout = setTimeout(() => {
|
||||
session.close();
|
||||
this.activeSessionsByRoamId.delete(roamId);
|
||||
this.roamTimeouts.delete(roamId);
|
||||
}, 15000);
|
||||
this.roamTimeouts.set(roamId, timeout);
|
||||
}
|
||||
const timeout = this._pendingTimeouts.get(ws);
|
||||
if (timeout) {
|
||||
@@ -101,11 +147,7 @@ export class SSHSessionDO {
|
||||
|
||||
// Hibernation API: called when a WebSocket error occurs
|
||||
async webSocketError(ws: WebSocket, error: unknown): Promise<void> {
|
||||
const session = this.sessions.get(ws);
|
||||
if (session) {
|
||||
session.close();
|
||||
this.sessions.delete(ws);
|
||||
}
|
||||
await this.webSocketClose(ws, 1011, 'Error', false);
|
||||
}
|
||||
|
||||
private async initSSHSession(
|
||||
@@ -113,16 +155,15 @@ export class SSHSessionDO {
|
||||
config: SSHConnectionConfig
|
||||
): Promise<void> {
|
||||
try {
|
||||
// --- SSRF Protection ---
|
||||
if (isBlockedHost(config.host)) {
|
||||
throw new Error('禁止连接内网或保留地址 (SSRF 防护)');
|
||||
}
|
||||
const BLOCKED_PORTS = [80, 443, 25, 465, 587, 3306, 6379, 27017, 11211];
|
||||
if (BLOCKED_PORTS.includes(config.port)) {
|
||||
throw new Error(`端口 ${config.port} 存在安全风险,已被禁止连接`);
|
||||
}
|
||||
|
||||
const isPrivateIP = /^(127\.|10\.|172\.(1[6-9]|2[0-9]|3[0-1])\.|192\.168\.)/.test(config.host);
|
||||
const isLocalhost = config.host.toLowerCase() === 'localhost';
|
||||
if (isPrivateIP || isLocalhost) {
|
||||
throw new Error('禁止连接内网或本地回环地址 (SSRF 防护)');
|
||||
}
|
||||
const { connect } = await import('cloudflare:sockets');
|
||||
const socket = connect({ hostname: config.host, port: config.port });
|
||||
|
||||
@@ -130,6 +171,7 @@ export class SSHSessionDO {
|
||||
|
||||
const session = new SSHSession(ws, socket, config);
|
||||
this.sessions.set(ws, session);
|
||||
this.activeSessionsByRoamId.set(session.roamId, session);
|
||||
|
||||
await session.startHandshake();
|
||||
|
||||
|
||||
+107
-14
@@ -39,15 +39,90 @@ export const HTML = `<!DOCTYPE html>
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script type="module">var De=Object.defineProperty;var xe=(Q,K,J)=>K in Q?De(Q,K,{enumerable:!0,configurable:!0,writable:!0,value:J}):Q[K]=J;var ne=(Q,K,J)=>xe(Q,typeof K!="symbol"?K+"":K,J);(function(){const K=document.createElement("link").relList;if(K&&K.supports&&K.supports("modulepreload"))return;for(const j of document.querySelectorAll('link[rel="modulepreload"]'))G(j);new MutationObserver(j=>{for(const V of j)if(V.type==="childList")for(const A of V.addedNodes)A.tagName==="LINK"&&A.rel==="modulepreload"&&G(A)}).observe(document,{childList:!0,subtree:!0});function J(j){const V={};return j.integrity&&(V.integrity=j.integrity),j.referrerPolicy&&(V.referrerPolicy=j.referrerPolicy),j.crossOrigin==="use-credentials"?V.credentials="include":j.crossOrigin==="anonymous"?V.credentials="omit":V.credentials="same-origin",V}function G(j){if(j.ep)return;j.ep=!0;const V=J(j);fetch(j.href,V)}})();var pe={exports:{}},Se;function Re(){return Se||(Se=1,(function(Q,K){(function(J,G){Q.exports=G()})(globalThis,(()=>(()=>{var J={4567:function(A,r,o){var l=this&&this.__decorate||function(e,t,a,f){var v,p=arguments.length,c=p<3?t:f===null?f=Object.getOwnPropertyDescriptor(t,a):f;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")c=Reflect.decorate(e,t,a,f);else for(var m=e.length-1;m>=0;m--)(v=e[m])&&(c=(p<3?v(c):p>3?v(t,a,c):v(t,a))||c);return p>3&&c&&Object.defineProperty(t,a,c),c},u=this&&this.__param||function(e,t){return function(a,f){t(a,f,e)}};Object.defineProperty(r,"__esModule",{value:!0}),r.AccessibilityManager=void 0;const n=o(9042),d=o(9924),_=o(844),g=o(4725),h=o(2585),i=o(3656);let s=r.AccessibilityManager=class extends _.Disposable{constructor(e,t,a,f){super(),this._terminal=e,this._coreBrowserService=a,this._renderService=f,this._rowColumns=new WeakMap,this._liveRegionLineCount=0,this._charsToConsume=[],this._charsToAnnounce="",this._accessibilityContainer=this._coreBrowserService.mainDocument.createElement("div"),this._accessibilityContainer.classList.add("xterm-accessibility"),this._rowContainer=this._coreBrowserService.mainDocument.createElement("div"),this._rowContainer.setAttribute("role","list"),this._rowContainer.classList.add("xterm-accessibility-tree"),this._rowElements=[];for(let v=0;v<this._terminal.rows;v++)this._rowElements[v]=this._createAccessibilityTreeNode(),this._rowContainer.appendChild(this._rowElements[v]);if(this._topBoundaryFocusListener=v=>this._handleBoundaryFocus(v,0),this._bottomBoundaryFocusListener=v=>this._handleBoundaryFocus(v,1),this._rowElements[0].addEventListener("focus",this._topBoundaryFocusListener),this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._refreshRowsDimensions(),this._accessibilityContainer.appendChild(this._rowContainer),this._liveRegion=this._coreBrowserService.mainDocument.createElement("div"),this._liveRegion.classList.add("live-region"),this._liveRegion.setAttribute("aria-live","assertive"),this._accessibilityContainer.appendChild(this._liveRegion),this._liveRegionDebouncer=this.register(new d.TimeBasedDebouncer(this._renderRows.bind(this))),!this._terminal.element)throw new Error("Cannot enable accessibility before Terminal.open");this._terminal.element.insertAdjacentElement("afterbegin",this._accessibilityContainer),this.register(this._terminal.onResize((v=>this._handleResize(v.rows)))),this.register(this._terminal.onRender((v=>this._refreshRows(v.start,v.end)))),this.register(this._terminal.onScroll((()=>this._refreshRows()))),this.register(this._terminal.onA11yChar((v=>this._handleChar(v)))),this.register(this._terminal.onLineFeed((()=>this._handleChar(\`
|
||||
\`)))),this.register(this._terminal.onA11yTab((v=>this._handleTab(v)))),this.register(this._terminal.onKey((v=>this._handleKey(v.key)))),this.register(this._terminal.onBlur((()=>this._clearLiveRegion()))),this.register(this._renderService.onDimensionsChange((()=>this._refreshRowsDimensions()))),this.register((0,i.addDisposableDomListener)(document,"selectionchange",(()=>this._handleSelectionChange()))),this.register(this._coreBrowserService.onDprChange((()=>this._refreshRowsDimensions()))),this._refreshRows(),this.register((0,_.toDisposable)((()=>{this._accessibilityContainer.remove(),this._rowElements.length=0})))}_handleTab(e){for(let t=0;t<e;t++)this._handleChar(" ")}_handleChar(e){this._liveRegionLineCount<21&&(this._charsToConsume.length>0?this._charsToConsume.shift()!==e&&(this._charsToAnnounce+=e):this._charsToAnnounce+=e,e===\`
|
||||
\`&&(this._liveRegionLineCount++,this._liveRegionLineCount===21&&(this._liveRegion.textContent+=n.tooMuchOutput)))}_clearLiveRegion(){this._liveRegion.textContent="",this._liveRegionLineCount=0}_handleKey(e){this._clearLiveRegion(),new RegExp("\\\\p{Control}","u").test(e)||this._charsToConsume.push(e)}_refreshRows(e,t){this._liveRegionDebouncer.refresh(e,t,this._terminal.rows)}_renderRows(e,t){const a=this._terminal.buffer,f=a.lines.length.toString();for(let v=e;v<=t;v++){const p=a.lines.get(a.ydisp+v),c=[],m=(p==null?void 0:p.translateToString(!0,void 0,void 0,c))||"",y=(a.ydisp+v+1).toString(),k=this._rowElements[v];k&&(m.length===0?(k.innerText=" ",this._rowColumns.set(k,[0,1])):(k.textContent=m,this._rowColumns.set(k,c)),k.setAttribute("aria-posinset",y),k.setAttribute("aria-setsize",f))}this._announceCharacters()}_announceCharacters(){this._charsToAnnounce.length!==0&&(this._liveRegion.textContent+=this._charsToAnnounce,this._charsToAnnounce="")}_handleBoundaryFocus(e,t){const a=e.target,f=this._rowElements[t===0?1:this._rowElements.length-2];if(a.getAttribute("aria-posinset")===(t===0?"1":\`\${this._terminal.buffer.lines.length}\`)||e.relatedTarget!==f)return;let v,p;if(t===0?(v=a,p=this._rowElements.pop(),this._rowContainer.removeChild(p)):(v=this._rowElements.shift(),p=a,this._rowContainer.removeChild(v)),v.removeEventListener("focus",this._topBoundaryFocusListener),p.removeEventListener("focus",this._bottomBoundaryFocusListener),t===0){const c=this._createAccessibilityTreeNode();this._rowElements.unshift(c),this._rowContainer.insertAdjacentElement("afterbegin",c)}else{const c=this._createAccessibilityTreeNode();this._rowElements.push(c),this._rowContainer.appendChild(c)}this._rowElements[0].addEventListener("focus",this._topBoundaryFocusListener),this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._terminal.scrollLines(t===0?-1:1),this._rowElements[t===0?1:this._rowElements.length-2].focus(),e.preventDefault(),e.stopImmediatePropagation()}_handleSelectionChange(){var m;if(this._rowElements.length===0)return;const e=document.getSelection();if(!e)return;if(e.isCollapsed)return void(this._rowContainer.contains(e.anchorNode)&&this._terminal.clearSelection());if(!e.anchorNode||!e.focusNode)return void console.error("anchorNode and/or focusNode are null");let t={node:e.anchorNode,offset:e.anchorOffset},a={node:e.focusNode,offset:e.focusOffset};if((t.node.compareDocumentPosition(a.node)&Node.DOCUMENT_POSITION_PRECEDING||t.node===a.node&&t.offset>a.offset)&&([t,a]=[a,t]),t.node.compareDocumentPosition(this._rowElements[0])&(Node.DOCUMENT_POSITION_CONTAINED_BY|Node.DOCUMENT_POSITION_FOLLOWING)&&(t={node:this._rowElements[0].childNodes[0],offset:0}),!this._rowContainer.contains(t.node))return;const f=this._rowElements.slice(-1)[0];if(a.node.compareDocumentPosition(f)&(Node.DOCUMENT_POSITION_CONTAINED_BY|Node.DOCUMENT_POSITION_PRECEDING)&&(a={node:f,offset:((m=f.textContent)==null?void 0:m.length)??0}),!this._rowContainer.contains(a.node))return;const v=({node:y,offset:k})=>{const L=y instanceof Text?y.parentNode:y;let w=parseInt(L==null?void 0:L.getAttribute("aria-posinset"),10)-1;if(isNaN(w))return console.warn("row is invalid. Race condition?"),null;const R=this._rowColumns.get(L);if(!R)return console.warn("columns is null. Race condition?"),null;let T=k<R.length?R[k]:R.slice(-1)[0]+1;return T>=this._terminal.cols&&(++w,T=0),{row:w,column:T}},p=v(t),c=v(a);if(p&&c){if(p.row>c.row||p.row===c.row&&p.column>=c.column)throw new Error("invalid range");this._terminal.select(p.column,p.row,(c.row-p.row)*this._terminal.cols-p.column+c.column)}}_handleResize(e){this._rowElements[this._rowElements.length-1].removeEventListener("focus",this._bottomBoundaryFocusListener);for(let t=this._rowContainer.children.length;t<this._terminal.rows;t++)this._rowElements[t]=this._createAccessibilityTreeNode(),this._rowContainer.appendChild(this._rowElements[t]);for(;this._rowElements.length>e;)this._rowContainer.removeChild(this._rowElements.pop());this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._refreshRowsDimensions()}_createAccessibilityTreeNode(){const e=this._coreBrowserService.mainDocument.createElement("div");return e.setAttribute("role","listitem"),e.tabIndex=-1,this._refreshRowDimensions(e),e}_refreshRowsDimensions(){if(this._renderService.dimensions.css.cell.height){this._accessibilityContainer.style.width=\`\${this._renderService.dimensions.css.canvas.width}px\`,this._rowElements.length!==this._terminal.rows&&this._handleResize(this._terminal.rows);for(let e=0;e<this._terminal.rows;e++)this._refreshRowDimensions(this._rowElements[e])}}_refreshRowDimensions(e){e.style.height=\`\${this._renderService.dimensions.css.cell.height}px\`}};r.AccessibilityManager=s=l([u(1,h.IInstantiationService),u(2,g.ICoreBrowserService),u(3,g.IRenderService)],s)},3614:(A,r)=>{function o(d){return d.replace(/\\r?\\n/g,"\\Line truncated
|
||||
<script type="module">var Hs=Object.defineProperty;var Ns=(r,e,p)=>e in r?Hs(r,e,{enumerable:!0,configurable:!0,writable:!0,value:p}):r[e]=p;var le=(r,e,p)=>Ns(r,typeof e!="symbol"?e+"":e,p);(function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const m of document.querySelectorAll('link[rel="modulepreload"]'))x(m);new MutationObserver(m=>{for(const R of m)if(R.type==="childList")for(const b of R.addedNodes)b.tagName==="LINK"&&b.rel==="modulepreload"&&x(b)}).observe(document,{childList:!0,subtree:!0});function p(m){const R={};return m.integrity&&(R.integrity=m.integrity),m.referrerPolicy&&(R.referrerPolicy=m.referrerPolicy),m.crossOrigin==="use-credentials"?R.credentials="include":m.crossOrigin==="anonymous"?R.credentials="omit":R.credentials="same-origin",R}function x(m){if(m.ep)return;m.ep=!0;const R=p(m);fetch(m.href,R)}})();var bt={exports:{}},ci;function Ws(){return ci||(ci=1,(function(r,e){(function(p,x){r.exports=x()})(globalThis,(()=>(()=>{var p={4567:function(b,i,a){var l=this&&this.__decorate||function(s,n,c,g){var S,w=arguments.length,u=w<3?n:g===null?g=Object.getOwnPropertyDescriptor(n,c):g;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")u=Reflect.decorate(s,n,c,g);else for(var L=s.length-1;L>=0;L--)(S=s[L])&&(u=(w<3?S(u):w>3?S(n,c,u):S(n,c))||u);return w>3&&u&&Object.defineProperty(n,c,u),u},_=this&&this.__param||function(s,n){return function(c,g){n(c,g,s)}};Object.defineProperty(i,"__esModule",{value:!0}),i.AccessibilityManager=void 0;const h=a(9042),d=a(9924),v=a(844),C=a(4725),f=a(2585),t=a(3656);let o=i.AccessibilityManager=class extends v.Disposable{constructor(s,n,c,g){super(),this._terminal=s,this._coreBrowserService=c,this._renderService=g,this._rowColumns=new WeakMap,this._liveRegionLineCount=0,this._charsToConsume=[],this._charsToAnnounce="",this._accessibilityContainer=this._coreBrowserService.mainDocument.createElement("div"),this._accessibilityContainer.classList.add("xterm-accessibility"),this._rowContainer=this._coreBrowserService.mainDocument.createElement("div"),this._rowContainer.setAttribute("role","list"),this._rowContainer.classList.add("xterm-accessibility-tree"),this._rowElements=[];for(let S=0;S<this._terminal.rows;S++)this._rowElements[S]=this._createAccessibilityTreeNode(),this._rowContainer.appendChild(this._rowElements[S]);if(this._topBoundaryFocusListener=S=>this._handleBoundaryFocus(S,0),this._bottomBoundaryFocusListener=S=>this._handleBoundaryFocus(S,1),this._rowElements[0].addEventListener("focus",this._topBoundaryFocusListener),this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._refreshRowsDimensions(),this._accessibilityContainer.appendChild(this._rowContainer),this._liveRegion=this._coreBrowserService.mainDocument.createElement("div"),this._liveRegion.classList.add("live-region"),this._liveRegion.setAttribute("aria-live","assertive"),this._accessibilityContainer.appendChild(this._liveRegion),this._liveRegionDebouncer=this.register(new d.TimeBasedDebouncer(this._renderRows.bind(this))),!this._terminal.element)throw new Error("Cannot enable accessibility before Terminal.open");this._terminal.element.insertAdjacentElement("afterbegin",this._accessibilityContainer),this.register(this._terminal.onResize((S=>this._handleResize(S.rows)))),this.register(this._terminal.onRender((S=>this._refreshRows(S.start,S.end)))),this.register(this._terminal.onScroll((()=>this._refreshRows()))),this.register(this._terminal.onA11yChar((S=>this._handleChar(S)))),this.register(this._terminal.onLineFeed((()=>this._handleChar(\`
|
||||
\`)))),this.register(this._terminal.onA11yTab((S=>this._handleTab(S)))),this.register(this._terminal.onKey((S=>this._handleKey(S.key)))),this.register(this._terminal.onBlur((()=>this._clearLiveRegion()))),this.register(this._renderService.onDimensionsChange((()=>this._refreshRowsDimensions()))),this.register((0,t.addDisposableDomListener)(document,"selectionchange",(()=>this._handleSelectionChange()))),this.register(this._coreBrowserService.onDprChange((()=>this._refreshRowsDimensions()))),this._refreshRows(),this.register((0,v.toDisposable)((()=>{this._accessibilityContainer.remove(),this._rowElements.length=0})))}_handleTab(s){for(let n=0;n<s;n++)this._handleChar(" ")}_handleChar(s){this._liveRegionLineCount<21&&(this._charsToConsume.length>0?this._charsToConsume.shift()!==s&&(this._charsToAnnounce+=s):this._charsToAnnounce+=s,s===\`
|
||||
\`&&(this._liveRegionLineCount++,this._liveRegionLineCount===21&&(this._liveRegion.textContent+=h.tooMuchOutput)))}_clearLiveRegion(){this._liveRegion.textContent="",this._liveRegionLineCount=0}_handleKey(s){this._clearLiveRegion(),new RegExp("\\\\p{Control}","u").test(s)||this._charsToConsume.push(s)}_refreshRows(s,n){this._liveRegionDebouncer.refresh(s,n,this._terminal.rows)}_renderRows(s,n){const c=this._terminal.buffer,g=c.lines.length.toString();for(let S=s;S<=n;S++){const w=c.lines.get(c.ydisp+S),u=[],L=(w==null?void 0:w.translateToString(!0,void 0,void 0,u))||"",M=(c.ydisp+S+1).toString(),T=this._rowElements[S];T&&(L.length===0?(T.innerText=" ",this._rowColumns.set(T,[0,1])):(T.textContent=L,this._rowColumns.set(T,u)),T.setAttribute("aria-posinset",M),T.setAttribute("aria-setsize",g))}this._announceCharacters()}_announceCharacters(){this._charsToAnnounce.length!==0&&(this._liveRegion.textContent+=this._charsToAnnounce,this._charsToAnnounce="")}_handleBoundaryFocus(s,n){const c=s.target,g=this._rowElements[n===0?1:this._rowElements.length-2];if(c.getAttribute("aria-posinset")===(n===0?"1":\`\${this._terminal.buffer.lines.length}\`)||s.relatedTarget!==g)return;let S,w;if(n===0?(S=c,w=this._rowElements.pop(),this._rowContainer.removeChild(w)):(S=this._rowElements.shift(),w=c,this._rowContainer.removeChild(S)),S.removeEventListener("focus",this._topBoundaryFocusListener),w.removeEventListener("focus",this._bottomBoundaryFocusListener),n===0){const u=this._createAccessibilityTreeNode();this._rowElements.unshift(u),this._rowContainer.insertAdjacentElement("afterbegin",u)}else{const u=this._createAccessibilityTreeNode();this._rowElements.push(u),this._rowContainer.appendChild(u)}this._rowElements[0].addEventListener("focus",this._topBoundaryFocusListener),this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._terminal.scrollLines(n===0?-1:1),this._rowElements[n===0?1:this._rowElements.length-2].focus(),s.preventDefault(),s.stopImmediatePropagation()}_handleSelectionChange(){var L;if(this._rowElements.length===0)return;const s=document.getSelection();if(!s)return;if(s.isCollapsed)return void(this._rowContainer.contains(s.anchorNode)&&this._terminal.clearSelection());if(!s.anchorNode||!s.focusNode)return void console.error("anchorNode and/or focusNode are null");let n={node:s.anchorNode,offset:s.anchorOffset},c={node:s.focusNode,offset:s.focusOffset};if((n.node.compareDocumentPosition(c.node)&Node.DOCUMENT_POSITION_PRECEDING||n.node===c.node&&n.offset>c.offset)&&([n,c]=[c,n]),n.node.compareDocumentPosition(this._rowElements[0])&(Node.DOCUMENT_POSITION_CONTAINED_BY|Node.DOCUMENT_POSITION_FOLLOWING)&&(n={node:this._rowElements[0].childNodes[0],offset:0}),!this._rowContainer.contains(n.node))return;const g=this._rowElements.slice(-1)[0];if(c.node.compareDocumentPosition(g)&(Node.DOCUMENT_POSITION_CONTAINED_BY|Node.DOCUMENT_POSITION_PRECEDING)&&(c={node:g,offset:((L=g.textContent)==null?void 0:L.length)??0}),!this._rowContainer.contains(c.node))return;const S=({node:M,offset:T})=>{const D=M instanceof Text?M.parentNode:M;let k=parseInt(D==null?void 0:D.getAttribute("aria-posinset"),10)-1;if(isNaN(k))return console.warn("row is invalid. Race condition?"),null;const O=this._rowColumns.get(D);if(!O)return console.warn("columns is null. Race condition?"),null;let F=T<O.length?O[T]:O.slice(-1)[0]+1;return F>=this._terminal.cols&&(++k,F=0),{row:k,column:F}},w=S(n),u=S(c);if(w&&u){if(w.row>u.row||w.row===u.row&&w.column>=u.column)throw new Error("invalid range");this._terminal.select(w.column,w.row,(u.row-w.row)*this._terminal.cols-w.column+u.column)}}_handleResize(s){this._rowElements[this._rowElements.length-1].removeEventListener("focus",this._bottomBoundaryFocusListener);for(let n=this._rowContainer.children.length;n<this._terminal.rows;n++)this._rowElements[n]=this._createAccessibilityTreeNode(),this._rowContainer.appendChild(this._rowElements[n]);for(;this._rowElements.length>s;)this._rowContainer.removeChild(this._rowElements.pop());this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._refreshRowsDimensions()}_createAccessibilityTreeNode(){const s=this._coreBrowserService.mainDocument.createElement("div");return s.setAttribute("role","listitem"),s.tabIndex=-1,this._refreshRowDimensions(s),s}_refreshRowsDimensions(){if(this._renderService.dimensions.css.cell.height){this._accessibilityContainer.style.width=\`\${this._renderService.dimensions.css.canvas.width}px\`,this._rowElements.length!==this._terminal.rows&&this._handleResize(this._terminal.rows);for(let s=0;s<this._terminal.rows;s++)this._refreshRowDimensions(this._rowElements[s])}}_refreshRowDimensions(s){s.style.height=\`\${this._renderService.dimensions.css.cell.height}px\`}};i.AccessibilityManager=o=l([_(1,f.IInstantiationService),_(2,C.ICoreBrowserService),_(3,C.IRenderService)],o)},3614:(b,i)=>{function a(d){return d.replace(/\\r?\\n/g,"\\Line truncated
|
||||
|
||||
WARNING: This link could potentially be dangerous\`)){const s=window.open();if(s){try{s.opener=null}catch{}s.location.href=i}else console.warn("Opening link blocked as opener could not be cleared")}}r.OscLinkProvider=_=l([u(0,d.IBufferService),u(1,d.IOptionsService),u(2,d.IOscLinkService)],_)},6193:(A,r)=>{Object.defineProperty(r,"__esModule",{value:!0}),r.RenderDebouncer=void 0,r.RenderDebouncer=class{constructor(o,l){this._renderCallback=o,this._coreBrowserService=l,this._refreshCallbacks=[]}dispose(){this._animationFrame&&(this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame),this._animationFrame=void 0)}addRefreshCallback(o){return this._refreshCallbacks.push(o),this._animationFrame||(this._animationFrame=this._coreBrowserService.window.requestAnimationFrame((()=>this._innerRefresh()))),this._animationFrame}refresh(o,l,u){this._rowCount=u,o=o!==void 0?o:0,l=l!==void 0?l:this._rowCount-1,this._rowStart=this._rowStart!==void 0?Math.min(this._rowStart,o):o,this._rowEnd=this._rowEnd!==void 0?Math.max(this._rowEnd,l):l,this._animationFrame||(this._animationFrame=this._coreBrowserService.window.requestAnimationFrame((()=>this._innerRefresh())))}_innerRefresh(){if(this._animationFrame=void 0,this._rowStart===void 0||this._rowEnd===void 0||this._rowCount===void 0)return void this._runRefreshCallbacks();const o=Math.max(this._rowStart,0),l=Math.min(this._rowEnd,this._rowCount-1);this._rowStart=void 0,this._rowEnd=void 0,this._renderCallback(o,l),this._runRefreshCallbacks()}_runRefreshCallbacks(){for(const o of this._refreshCallbacks)o(0);this._refreshCallbacks=[]}}},3236:(A,r,o)=>{Object.defineProperty(r,"__esModule",{value:!0}),r.Terminal=void 0;const l=o(3614),u=o(3656),n=o(3551),d=o(9042),_=o(3730),g=o(1680),h=o(3107),i=o(5744),s=o(2950),e=o(1296),t=o(428),a=o(4269),f=o(5114),v=o(8934),p=o(3230),c=o(9312),m=o(4725),y=o(6731),k=o(8055),L=o(8969),w=o(8460),R=o(844),T=o(6114),P=o(8437),U=o(2584),I=o(7399),S=o(5941),b=o(9074),E=o(2585),D=o(5435),M=o(4567),H=o(779);class N extends L.CoreTerminal{get onFocus(){return this._onFocus.event}get onBlur(){return this._onBlur.event}get onA11yChar(){return this._onA11yCharEmitter.event}get onA11yTab(){return this._onA11yTabEmitter.event}get onWillOpen(){return this._onWillOpen.event}constructor(x={}){super(x),this.browser=T,this._keyDownHandled=!1,this._keyDownSeen=!1,this._keyPressHandled=!1,this._unprocessedDeadKey=!1,this._accessibilityManager=this.register(new R.MutableDisposable),this._onCursorMove=this.register(new w.EventEmitter),this.onCursorMove=this._onCursorMove.event,this._onKey=this.register(new w.EventEmitter),this.onKey=this._onKey.event,this._onRender=this.register(new w.EventEmitter),this.onRender=this._onRender.event,this._onSelectionChange=this.register(new w.EventEmitter),this.onSelectionChange=this._onSelectionChange.event,this._onTitleChange=this.register(new w.EventEmitter),this.onTitleChange=this._onTitleChange.event,this._onBell=this.register(new w.EventEmitter),this.onBell=this._onBell.event,this._onFocus=this.register(new w.EventEmitter),this._onBlur=this.register(new w.EventEmitter),this._onA11yCharEmitter=this.register(new w.EventEmitter),this._onA11yTabEmitter=this.register(new w.EventEmitter),this._onWillOpen=this.register(new w.EventEmitter),this._setup(),this._decorationService=this._instantiationService.createInstance(b.DecorationService),this._instantiationService.setService(E.IDecorationService,this._decorationService),this._linkProviderService=this._instantiationService.createInstance(H.LinkProviderService),this._instantiationService.setService(m.ILinkProviderService,this._linkProviderService),this._linkProviderService.registerLinkProvider(this._instantiationService.createInstance(_.OscLinkProvider)),this.register(this._inputHandler.onRequestBell((()=>this._onBell.fire()))),this.register(this._inputHandler.onRequestRefreshRows(((C,O)=>this.refresh(C,O)))),this.register(this._inputHandler.onRequestSendFocus((()=>this._reportFocus()))),this.register(this._inputHandler.onRequestReset((()=>this.reset()))),this.register(this._inputHandler.onRequestWindowsOptionsReport((C=>this._reportWindowsOptions(C)))),this.register(this._inputHandler.onColor((C=>this._handleColorEvent(C)))),this.register((0,w.forwardEvent)(this._inputHandler.onCursorMove,this._onCursorMove)),this.register((0,w.forwardEvent)(this._inputHandler.onTitleChange,this._onTitleChange)),this.register((0,w.forwardEvent)(this._inputHandler.onA11yChar,this._onA11yCharEmitter)),this.register((0,w.forwardEvent)(this._inputHandler.onA11yTab,this._onA11yTabEmitter)),this.register(this._bufferService.onResize((C=>this._afterResize(C.cols,C.rows)))),this.register((0,R.toDisposable)((()=>{var C,O;this._customKeyEventHandler=void 0,(O=(C=this.element)==null?void 0:C.parentNode)==null||O.removeChild(this.element)})))}_handleColorEvent(x){if(this._themeService)for(const C of x){let O,B="";switch(C.index){case 256:O="foreground",B="10";break;case 257:O="background",B="11";
Line truncated
|
||||
WARNING: This link could potentially be dangerous\`)){const o=window.open();if(o){try{o.opener=null}catch{}o.location.href=t}else console.warn("Opening link blocked as opener could not be cleared")}}i.OscLinkProvider=v=l([_(0,d.IBufferService),_(1,d.IOptionsService),_(2,d.IOscLinkService)],v)},6193:(b,i)=>{Object.defineProperty(i,"__esModule",{value:!0}),i.RenderDebouncer=void 0,i.RenderDebouncer=class{constructor(a,l){this._renderCallback=a,this._coreBrowserService=l,this._refreshCallbacks=[]}dispose(){this._animationFrame&&(this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame),this._animationFrame=void 0)}addRefreshCallback(a){return this._refreshCallbacks.push(a),this._animationFrame||(this._animationFrame=this._coreBrowserService.window.requestAnimationFrame((()=>this._innerRefresh()))),this._animationFrame}refresh(a,l,_){this._rowCount=_,a=a!==void 0?a:0,l=l!==void 0?l:this._rowCount-1,this._rowStart=this._rowStart!==void 0?Math.min(this._rowStart,a):a,this._rowEnd=this._rowEnd!==void 0?Math.max(this._rowEnd,l):l,this._animationFrame||(this._animationFrame=this._coreBrowserService.window.requestAnimationFrame((()=>this._innerRefresh())))}_innerRefresh(){if(this._animationFrame=void 0,this._rowStart===void 0||this._rowEnd===void 0||this._rowCount===void 0)return void this._runRefreshCallbacks();const a=Math.max(this._rowStart,0),l=Math.min(this._rowEnd,this._rowCount-1);this._rowStart=void 0,this._rowEnd=void 0,this._renderCallback(a,l),this._runRefreshCallbacks()}_runRefreshCallbacks(){for(const a of this._refreshCallbacks)a(0);this._refreshCallbacks=[]}}},3236:(b,i,a)=>{Object.defineProperty(i,"__esModule",{value:!0}),i.Terminal=void 0;const l=a(3614),_=a(3656),h=a(3551),d=a(9042),v=a(3730),C=a(1680),f=a(3107),t=a(5744),o=a(2950),s=a(1296),n=a(428),c=a(4269),g=a(5114),S=a(8934),w=a(3230),u=a(9312),L=a(4725),M=a(6731),T=a(8055),D=a(8969),k=a(8460),O=a(844),F=a(6114),U=a(8437),$=a(2584),W=a(7399),y=a(5941),A=a(9074),B=a(2585),P=a(5435),z=a(4567),q=a(779);class K extends D.CoreTerminal{get onFocus(){return this._onFocus.event}get onBlur(){return this._onBlur.event}get onA11yChar(){return this._onA11yCharEmitter.event}get onA11yTab(){return this._onA11yTabEmitter.event}get onWillOpen(){return this._onWillOpen.event}constructor(I={}){super(I),this.browser=F,this._keyDownHandled=!1,this._keyDownSeen=!1,this._keyPressHandled=!1,this._unprocessedDeadKey=!1,this._accessibilityManager=this.register(new O.MutableDisposable),this._onCursorMove=this.register(new k.EventEmitter),this.onCursorMove=this._onCursorMove.event,this._onKey=this.register(new k.EventEmitter),this.onKey=this._onKey.event,this._onRender=this.register(new k.EventEmitter),this.onRender=this._onRender.event,this._onSelectionChange=this.register(new k.EventEmitter),this.onSelectionChange=this._onSelectionChange.event,this._onTitleChange=this.register(new k.EventEmitter),this.onTitleChange=this._onTitleChange.event,this._onBell=this.register(new k.EventEmitter),this.onBell=this._onBell.event,this._onFocus=this.register(new k.EventEmitter),this._onBlur=this.register(new k.EventEmitter),this._onA11yCharEmitter=this.register(new k.EventEmitter),this._onA11yTabEmitter=this.register(new k.EventEmitter),this._onWillOpen=this.register(new k.EventEmitter),this._setup(),this._decorationService=this._instantiationService.createInstance(A.DecorationService),this._instantiationService.setService(B.IDecorationService,this._decorationService),this._linkProviderService=this._instantiationService.createInstance(q.LinkProviderService),this._instantiationService.setService(L.ILinkProviderService,this._linkProviderService),this._linkProviderService.registerLinkProvider(this._instantiationService.createInstance(v.OscLinkProvider)),this.register(this._inputHandler.onRequestBell((()=>this._onBell.fire()))),this.register(this._inputHandler.onRequestRefreshRows(((E,N)=>this.refresh(E,N)))),this.register(this._inputHandler.onRequestSendFocus((()=>this._reportFocus()))),this.register(this._inputHandler.onRequestReset((()=>this.reset()))),this.register(this._inputHandler.onRequestWindowsOptionsReport((E=>this._reportWindowsOptions(E)))),this.register(this._inputHandler.onColor((E=>this._handleColorEvent(E)))),this.register((0,k.forwardEvent)(this._inputHandler.onCursorMove,this._onCursorMove)),this.register((0,k.forwardEvent)(this._inputHandler.onTitleChange,this._onTitleChange)),this.register((0,k.forwardEvent)(this._inputHandler.onA11yChar,this._onA11yCharEmitter)),this.register((0,k.forwardEvent)(this._inputHandler.onA11yTab,this._onA11yTabEmitter)),this.register(this._bufferService.onResize((E=>this._afterResize(E.cols,E.rows)))),this.register((0,O.toDisposable)((()=>{var E,N;this._customKeyEventHandler=void 0,(N=(E=this.element)==null?void 0:E.parentNode)==null||N.removeChild(this.element)})))}_handleColorEvent(I){if(this._themeService)for(const E of I){let N,H="";switch(E.index){case 256:N="foreground",H="10";break;case 257:N="background",H="11";
Line truncated
|
||||
\`:\`
|
||||
\`)}clearSelection(){this._model.clearSelection(),this._removeMouseDownListeners(),this.refresh(),this._onSelectionChange.fire()}refresh(c){this._refreshAnimationFrame||(this._refreshAnimationFrame=this._coreBrowserService.window.requestAnimationFrame((()=>this._refresh()))),s.isLinux&&c&&this.selectionText.length&&this._onLinuxMouseSelection.fire(this.selectionText)}_refresh(){this._refreshAnimationFrame=void 0,this._onRedrawRequest.fire({start:this._model.finalSelectionStart,end:this._model.finalSelectionEnd,columnSelectMode:this._activeSelectionMode===3})}_isClickInSelection(c){const m=this._getMouseBufferCoords(c),y=this._model.finalSelectionStart,k=this._model.finalSelectionEnd;return!!(y&&k&&m)&&this._areCoordsInSelection(m,y,k)}isCellInSelection(c,m){const y=this._model.finalSelectionStart,k=this._model.finalSelectionEnd;return!(!y||!k)&&this._areCoordsInSelection([c,m],y,k)}_areCoordsInSelection(c,m,y){return c[1]>m[1]&&c[1]<y[1]||m[1]===y[1]&&c[1]===m[1]&&c[0]>=m[0]&&c[0]<y[0]||m[1]<y[1]&&c[1]===y[1]&&c[0]<y[0]||m[1]<y[1]&&c[1]===m[1]&&c[0]>=m[0]}_selectWordAtCursor(c,m){var L,w;const y=(w=(L=this._linkifier.currentLink)==null?void 0:L.link)==null?void 0:w.range;if(y)return this._model.selectionStart=[y.start.x-1,y.start.y-1],this._model.selectionStartLength=(0,e.getRangeLength)(y,this._bufferService.cols),this._model.selectionEnd=void 0,!0;const k=this._getMouseBufferCoords(c);return!!k&&(this._selectWordAt(k,m),this._model.selectionEnd=void 0,!0)}selectAll(){this._model.isSelectAllActive=!0,this.refresh(),this._onSelectionChange.fire()}selectLines(c,m){this._model.clearSelection(),c=Math.max(c,0),m=Math.min(m,this._bufferService.buffer.lines.length-1),this._model.selectionStart=[0,c],this._model.selectionEnd=[this._bufferService.cols,m],this.refresh(),this._onSelectionChange.fire()}_handleTrim(c){this._model.handleTrim(c)&&this.refresh()}_getMouseBufferCoords(c){const m=this._mouseService.getCoords(c,this._screenElement,this._bufferService.cols,this._bufferService.rows,!0);if(m)return m[0]--,m[1]--,m[1]+=this._bufferService.buffer.ydisp,m}_getMouseEventScrollAmount(c){let m=(0,n.getCoordsRelativeToElement)(this._coreBrowserService.window,c,this._screenElement)[1];const y=this._renderService.dimensions.css.canvas.height;return m>=0&&m<=y?0:(m>y&&(m-=y),m=Math.min(Math.max(m,-50),50),m/=50,m/Math.abs(m)+Math.round(14*m))}shouldForceSelection(c){return s.isMac?c.altKey&&this._optionsService.rawOptions.macOptionClickForcesSelection:c.shiftKey}handleMouseDown(c){if(this._mouseDownTimeStamp=c.timeStamp,(c.button!==2||!this.hasSelection)&&c.button===0){if(!this._enabled){if(!this.shouldForceSelection(c))return;c.stopPropagation()}c.preventDefault(),this._dragScrollAmount=0,this._enabled&&c.shiftKey?this._handleIncrementalClick(c):c.detail===1?this._handleSingleClick(c):c.detail===2?this._handleDoubleClick(c):c.detail===3&&this._handleTripleClick(c),this._addMouseDownListeners(),this.refresh(!0)}}_addMouseDownListeners(){this._screenElement.ownerDocument&&(this._screenElement.ownerDocument.addEventListener("mousemove",this._mouseMoveListener),this._screenElement.ownerDocument.addEventListener("mouseup",this._mouseUpListener)),this._dragScrollIntervalTimer=this._coreBrowserService.window.setInterval((()=>this._dragScroll()),50)}_removeMouseDownListeners(){this._screenElement.ownerDocument&&(this._screenElement.ownerDocument.removeEventListener("mousemove",this._mouseMoveListener),this._screenElement.ownerDocument.removeEventListener("mouseup",this._mouseUpListener)),this._coreBrowserService.window.clearInterval(this._dragScrollIntervalTimer),this._dragScrollIntervalTimer=void 0}_handleIncrementalClick(c){this._model.selectionStart&&(this._model.selectionEnd=this._getMouseBufferCoords(c))}_handleSingleClick(c){if(this._model.selectionStartLength=0,this._model.isSelectAllActive=!1,this._activeSelectionMode=this.shouldColumnSelect(c)?3:0,this._model.selectionStart=this._getMouseBufferCoords(c),!this._model.selectionStart)return;this._model.selectionEnd=void 0;const m=this._bufferService.buffer.lines.get(this._model.selectionStart[1]);m&&m.length!==this._model.selectionStart[0]&&m.hasWidth(this._model.selectionStart[0])===0&&this._model.selectionStart[0]++}_handleDoubleClick(c){this._selectWordAtCursor(c,!0)&&(this._activeSelectionMode=1)}_handleTripleClick(c){const m=this._getMouseBufferCoords(c);m&&(this._activeSelectionMode=2,this._selectLineAt(m[1]))}shouldColumnSelect(c){return c.altKey&&!(s.isMac&&this._optionsService.rawOptions.macOptionClickForcesSelection)}_handleMouseMove(c){if(c.stopImmediatePropagation(),!this._model.selectionStart)return;const m=this._model.selectionEnd?[this._model.selectionEnd[0],this._model.selectionEnd[1]]:null;if(this._model.selectionEnd=this._getMouseBufferCoords(c),!this._model.selectionEnd)return void this.refresh(!0);this._activeSelectionMode===2?this._model.selectionEnd[1]<this._model.selectionStart[1]?this._model.selectionEnd[0]=0:this._model.selectionEnd[0]=this._buf
Line truncated
|
||||
\`,n.VT="\\v",n.FF="\\f",n.CR="\\r",n.SO="",n.SI="",n.DLE="",n.DC1="",n.DC2="",n.DC3="",n.DC4="",n.NAK="",n.SYN="",n.ETB="",n.CAN="",n.EM="",n.SUB="",n.ESC="\\x1B",n.FS="",n.GS="",n.RS="",n.US="",n.SP=" ",n.DEL=""})(o||(r.C0=o={})),(function(n){n.PAD="",n.HOP="",n.BPH="",n.NBH="",n.IND="",n.NEL="
",n.SSA="",n.ESA="",n.HTS="",n.HTJ="",n.VTS="",n.PLD="",n.PLU="",n.RI="",n.SS2="",n.SS3="",n.DCS="",n.PU1="",n.PU2="",n.STS="",n.CCH="",n.MW="",n.SPA="",n.EPA="",n.SOS="",n.SGCI="",n.SCI="",n.CSI="",n.ST="",n.OSC="",n.PM="",n.APC=""})(l||(r.C1=l={})),(function(n){n.ST=\`\${o.ESC}\\\\\`})(u||(r.C1_ESCAPED=u={}))},7399:(A,r,o)=>{Object.defineProperty(r,"__esModule",{value:!0}),r.evaluateKeyboardEvent=void 0;const l=o(2584),u={48:["0",")"],49:["1","!"],50:["2","@"],51:["3","#"],52:["4","$"],53:["5","%"],54:["6","^"],55:["7","&"],56:["8","*"],57:["9","("],186:[";",":"],187:["=","+"],188:[",","<"],189:["-","_"],190:[".",">"],191:["/","?"],192:["\`","~"],219:["[","{"],220:["\\\\","|"],221:["]","}"],222:["'",'"']};r.evaluateKeyboardEvent=function(n,d,_,g){const h={type:0,cancel:!1,key:void 0},i=(n.shiftKey?1:0)|(n.altKey?2:0)|(n.ctrlKey?4:0)|(n.metaKey?8:0);switch(n.keyCode){case 0:n.key==="UIKeyInputUpArrow"?h.key=d?l.C0.ESC+"OA":l.C0.ESC+"[A":n.key==="UIKeyInputLeftArrow"?h.key=d?l.C0.ESC+"OD":l.C0.ESC+"[D":n.key==="UIKeyInputRightArrow"?h.key=d?l.C0.ESC+"OC":l.C0.ESC+"[C":n.key==="UIKeyInputDownArrow"&&(h.key=d?l.C0.ESC+"OB":l.C0.ESC+"[B");break;case 8:h.key=n.ctrlKey?"\\b":l.C0.DEL,n.altKey&&(h.key=l.C0.ESC+h.key);break;case 9:if(n.shiftKey){h.key=l.C0.ESC+"[Z";break}h.key=l.C0.HT,h.cancel=!0;break;case 13:h.key=n.altKey?l.C0.ESC+l.C0.CR:l.C0.CR,h.cancel=!0;break;case 27:h.key=l.C0.ESC,n.altKey&&(h.key=l.C0.ESC+l.C0.ESC),h.cancel=!0;break;case 37:if(n.metaKey)break;i?(h.key=l.C0.ESC+"[1;"+(i+1)+"D",h.key===l.C0.ESC+"[1;3D"&&(h.key=l.C0.ESC+(_?"b":"[1;5D"))):h.key=d?l.C0.ESC+"OD":l.C0.ESC+"[D";break;case 39:if(n.metaKey)break;i?(h.key=l.C0.ESC+"[1;"+(i+1)+"C",h.key===l.C0.ESC+"[1;3C"&&(h.key=l.C0.ESC+(_?"f":"[1;5C"))):h.key=d?l.C0.ESC+"OC":l.C0.ESC+"[C";break;case 38:if(n.metaKey)break;i?(h.key=l.C0.ESC+"[1;"+(i+1)+"A",_||h.key!==l.C0.ESC+"[1;3A"||(h.key=l.C0.ESC+"[1;5A")):h.key=d?l.C0.ESC+"OA":l.C0.ESC+"[A";break;case 40:if(n.metaKey)break;i?(h.key=l.C0.ESC+"[1;"+(i+1)+"B",_||h.key!==l.C0.ESC+"[1;3B"||(h.key=l.C0.ESC+"[1;5B")):h.key=d?l.C0.ESC+"OB":l.C0.ESC+"[B";break;case 45:n.shiftKey||n.ctrlKey||(h.key=l.C0.ESC+"[2~");break;case 46:h.key=i?l.C0.ESC+"[3;"+(i+1)+"~":l.C0.ESC+"[3~";break;case 36:h.key=i?l.C0.ESC+"[1;"+(i+1)+"H":d?l.C0.ESC+"OH":l.C0.ESC+"[H";break;case 35:h.key=i?l.C0.ESC+"[1;"+(i+1)+"F":d?l.C0.ESC+"OF":l.C0.ESC+"[F";break;case 33:n.shiftKey?h.type=2:n.ctrlKey?h.key=l.C0.ESC+"[5;"+(i+1)+"~":h.key=l.C0.ESC+"[5~";break;case 34:n.shiftKey?h.type=3:n.ctrlKey?h.key=l.C0.ESC+"[6;"+(i+1)+"~":h.key=l.C0.ESC+"[6~";break;case 112:h.key=i?l.C0.ESC+"[1;"+(i+1)+"P":l.C0.ESC+"OP";break;case 113:h.key=i?l.C0.ESC+"[1;"+(i+1)+"Q":l.C0.ESC+"OQ";break;case 114:h.key=i?l.C0.ESC+"[1;"+(i+1)+"R":l.C0.ESC+"OR";break;case 115:h.key=i?l.C0.ESC+"[1;"+(i+1)+"S":l.C0.ESC+"OS";break;case 116:h.key=i?l.C0.ESC+"[15;"+(i+1)+"~":l.C0.ESC+"[15~";break;case 117:h.key=i?l.C0.ESC+"[17;"+(i+1)+"~":l.C0.ESC+"[17~";break;case 118:h.key=i?l.C0.ESC+"[18;"+(i+1)+"~":l.C0.ESC+"[18~";break;case 119:h.key=i?l.C0.ESC+"[19;"+(i+1)+"~":l.C0.ESC+"[19~";break;case 120:h.key=i?l.C0.ESC+"[20;"+(i+1)+"~":l.C0.ESC+"[20~";break;case 121:h.key=i?l.C0.ESC+"[21;"+(i+1)+"~":l.C0.ESC+"[21~";break;case 122:h.key=i?l.C0.ESC+"[23;"+(i+1)+"~":l.C0.ESC+"[23~";break;case 123:h.key=i?l.C0.ESC+"[24;"+(i+1)+"~":l.C0.ESC+"[24~";break;default:if(!n.ctrlKey||n.shiftKey||n.altKey||n.metaKey)if(_&&!g||!n.altKey||n.metaKey)!_||n.altKey||n.ctrlKey||n.shiftKey||!n.metaKey?n.key&&!n.ctrlKey&&!n.altKey&&!n.metaKey&&n.keyCode>=48&&n.key.length===1?h.key=n.key:n.key&&n.ctrlKey&&(n.key==="_"&&(h.key=l.C0.US),n.key==="@"&&(h.key=l.C0.NUL)):n.keyCode===65&&(h.type=1);else{const s=u[n.keyCode],e=s==null?void 0:s[n.shiftKey?1:0];if(e)h.key=l.C0.ESC+e;else if(n.keyCode>=65&&n.keyCode<=90){const t=n.ctrlKey?n.keyCode-64:n.keyCode+32;let a=String.fromCharCode(t);n.shiftKey&&(a=a.toUpperCase()),h.key=l.C0.ESC+a}else if(n.keyCode===32)h.key=l.C0.ESC+(n.ctrlKey?l.C0.NUL:" ");else if(n.key==="Dead"&&n.code.startsWith("Key")){let t=n.code.slice(3,4);n.shiftKey||(t=t.toLowerCase()),h.key=l.C0.ESC+t,h.cancel=!0}}else n.keyCode>=65&&n.keyCode<=90?h.key=String.fromCharCode(n.keyCode-64):n.keyCode===32?h.key=l.C0.NUL:n.keyCode>=51&&n.keyCode<=55?h.key=String.fromCharCode(n.keyCode-51+27):n.keyCode===56?h.key=l.C0.DEL:n.keyCode===219?h.key=l.C0.ESC:n.keyCode===220?h.key=l.C0.FS:n.keyCode===221&&(h.key=l.C0.GS)}return h}},482:(A,r)=>{Object.defineProperty(r,"__esModule",{value:!0}),r.Utf8ToUtf32=r.StringToUtf32=r.utf32ToString=r.stringFromCodePoint=void 0,r.stringFromCodePoint=function(o){return o>65535?(o-=65536,String.f
Line truncated
|
||||
\`,e)}paste(s){this._core.paste(s)}refresh(s,e){this._verifyIntegers(s,e),this._core.refresh(s,e)}reset(){this._core.reset()}clearTextureAtlas(){this._core.clearTextureAtlas()}loadAddon(s){this._addonManager.loadAddon(this,s)}static get strings(){return r}_verifyIntegers(...s){for(const e of s)if(e===1/0||isNaN(e)||e%1!=0)throw new Error("This API only accepts integers")}_verifyPositiveIntegers(...s){for(const e of s)if(e&&(e===1/0||isNaN(e)||e%1!=0||e<0))throw new Error("This API only accepts positive integers")}}A.Terminal=h})(),V})()))})(pe)),pe.exports}var Be=Re(),ge={exports:{}},Ce;function Ae(){return Ce||(Ce=1,(function(Q,K){(function(J,G){Q.exports=G()})(self,(()=>(()=>{var J={};return(()=>{var G=J;Object.defineProperty(G,"__esModule",{value:!0}),G.FitAddon=void 0,G.FitAddon=class{activate(j){this._terminal=j}dispose(){}fit(){const j=this.proposeDimensions();if(!j||!this._terminal||isNaN(j.cols)||isNaN(j.rows))return;const V=this._terminal._core;this._terminal.rows===j.rows&&this._terminal.cols===j.cols||(V._renderService.clear(),this._terminal.resize(j.cols,j.rows))}proposeDimensions(){if(!this._terminal||!this._terminal.element||!this._terminal.element.parentElement)return;const j=this._terminal._core,V=j._renderService.dimensions;if(V.css.cell.width===0||V.css.cell.height===0)return;const A=this._terminal.options.scrollback===0?0:j.viewport.scrollBarWidth,r=window.getComputedStyle(this._terminal.element.parentElement),o=parseInt(r.getPropertyValue("height")),l=Math.max(0,parseInt(r.getPropertyValue("width"))),u=window.getComputedStyle(this._terminal.element),n=o-(parseInt(u.getPropertyValue("padding-top"))+parseInt(u.getPropertyValue("padding-bottom"))),d=l-(parseInt(u.getPropertyValue("padding-right"))+parseInt(u.getPropertyValue("padding-left")))-A;return{cols:Math.max(2,Math.floor(d/V.css.cell.width)),rows:Math.max(1,Math.floor(n/V.css.cell.height))}}}})(),J})()))})(ge)),ge.exports}var Te=Ae(),me={exports:{}},be;function Me(){return be||(be=1,(function(Q,K){(function(J,G){Q.exports=G()})(self,(()=>(()=>{var J={6:(A,r)=>{function o(u){try{const n=new URL(u),d=n.password&&n.username?\`\${n.protocol}//\${n.username}:\${n.password}@\${n.host}\`:n.username?\`\${n.protocol}//\${n.username}@\${n.host}\`:\`\${n.protocol}//\${n.host}\`;return u.toLocaleLowerCase().startsWith(d.toLocaleLowerCase())}catch{return!1}}Object.defineProperty(r,"__esModule",{value:!0}),r.LinkComputer=r.WebLinkProvider=void 0,r.WebLinkProvider=class{constructor(u,n,d,_={}){this._terminal=u,this._regex=n,this._handler=d,this._options=_}provideLinks(u,n){const d=l.computeLink(u,this._regex,this._terminal,this._handler);n(this._addCallbacks(d))}_addCallbacks(u){return u.map((n=>(n.leave=this._options.leave,n.hover=(d,_)=>{if(this._options.hover){const{range:g}=n;this._options.hover(d,_,g)}},n)))}};class l{static computeLink(n,d,_,g){const h=new RegExp(d.source,(d.flags||"")+"g"),[i,s]=l._getWindowedLineStrings(n-1,_),e=i.join("");let t;const a=[];for(;t=h.exec(e);){const f=t[0];if(!o(f))continue;const[v,p]=l._mapStrIdx(_,s,0,t.index),[c,m]=l._mapStrIdx(_,v,p,f.length);if(v===-1||p===-1||c===-1||m===-1)continue;const y={start:{x:p+1,y:v+1},end:{x:m,y:c+1}};a.push({range:y,text:f,activate:g})}return a}static _getWindowedLineStrings(n,d){let _,g=n,h=n,i=0,s="";const e=[];if(_=d.buffer.active.getLine(n)){const t=_.translateToString(!0);if(_.isWrapped&&t[0]!==" "){for(i=0;(_=d.buffer.active.getLine(--g))&&i<2048&&(s=_.translateToString(!0),i+=s.length,e.push(s),_.isWrapped&&s.indexOf(" ")===-1););e.reverse()}for(e.push(t),i=0;(_=d.buffer.active.getLine(++h))&&_.isWrapped&&i<2048&&(s=_.translateToString(!0),i+=s.length,e.push(s),s.indexOf(" ")===-1););}return[e,g]}static _mapStrIdx(n,d,_,g){const h=n.buffer.active,i=h.getNullCell();let s=_;for(;g;){const e=h.getLine(d);if(!e)return[-1,-1];for(let t=s;t<e.length;++t){e.getCell(t,i);const a=i.getChars();if(i.getWidth()&&(g-=a.length||1,t===e.length-1&&a==="")){const f=h.getLine(d+1);f&&f.isWrapped&&(f.getCell(0,i),i.getWidth()===2&&(g+=1))}if(g<0)return[d,t]}d++,s=0}return[d,s]}}r.LinkComputer=l}},G={};function j(A){var r=G[A];if(r!==void 0)return r.exports;var o=G[A]={exports:{}};return J[A](o,o.exports,j),o.exports}var V={};return(()=>{var A=V;Object.defineProperty(A,"__esModule",{value:!0}),A.WebLinksAddon=void 0;const r=j(6),o=/(https?|HTTPS?):[/]{2}[^\\s"'!*(){}|\\\\\\^<>\`]*[^\\s"':,.!?{}|\\\\\\^~\\[\\]\`()<>]/;function l(u,n){const d=window.open();if(d){try{d.opener=null}catch{}d.location.href=n}else console.warn("Opening link blocked as opener could not be cleared")}A.WebLinksAddon=class{constructor(u=l,n={}){this._handler=u,this._options=n}activate(u){this._terminal=u;const n=this._options,d=n.urlRegex||o;this._linkProvider=this._terminal.registerLinkProvider(new r.WebLinkProvider(this._terminal,d,this._handler,n))}dispose(){var u;(u=this._linkProvider)==null||u.dispose()}}})(),V})()))})(me)),me.exports}var Oe=Me();class Pe{constructor(K){ne(this,"terminal");ne(this,"fitAddon");
Line truncated
|
||||
\`)}clearSelection(){this._model.clearSelection(),this._removeMouseDownListeners(),this.refresh(),this._onSelectionChange.fire()}refresh(u){this._refreshAnimationFrame||(this._refreshAnimationFrame=this._coreBrowserService.window.requestAnimationFrame((()=>this._refresh()))),o.isLinux&&u&&this.selectionText.length&&this._onLinuxMouseSelection.fire(this.selectionText)}_refresh(){this._refreshAnimationFrame=void 0,this._onRedrawRequest.fire({start:this._model.finalSelectionStart,end:this._model.finalSelectionEnd,columnSelectMode:this._activeSelectionMode===3})}_isClickInSelection(u){const L=this._getMouseBufferCoords(u),M=this._model.finalSelectionStart,T=this._model.finalSelectionEnd;return!!(M&&T&&L)&&this._areCoordsInSelection(L,M,T)}isCellInSelection(u,L){const M=this._model.finalSelectionStart,T=this._model.finalSelectionEnd;return!(!M||!T)&&this._areCoordsInSelection([u,L],M,T)}_areCoordsInSelection(u,L,M){return u[1]>L[1]&&u[1]<M[1]||L[1]===M[1]&&u[1]===L[1]&&u[0]>=L[0]&&u[0]<M[0]||L[1]<M[1]&&u[1]===M[1]&&u[0]<M[0]||L[1]<M[1]&&u[1]===L[1]&&u[0]>=L[0]}_selectWordAtCursor(u,L){var D,k;const M=(k=(D=this._linkifier.currentLink)==null?void 0:D.link)==null?void 0:k.range;if(M)return this._model.selectionStart=[M.start.x-1,M.start.y-1],this._model.selectionStartLength=(0,s.getRangeLength)(M,this._bufferService.cols),this._model.selectionEnd=void 0,!0;const T=this._getMouseBufferCoords(u);return!!T&&(this._selectWordAt(T,L),this._model.selectionEnd=void 0,!0)}selectAll(){this._model.isSelectAllActive=!0,this.refresh(),this._onSelectionChange.fire()}selectLines(u,L){this._model.clearSelection(),u=Math.max(u,0),L=Math.min(L,this._bufferService.buffer.lines.length-1),this._model.selectionStart=[0,u],this._model.selectionEnd=[this._bufferService.cols,L],this.refresh(),this._onSelectionChange.fire()}_handleTrim(u){this._model.handleTrim(u)&&this.refresh()}_getMouseBufferCoords(u){const L=this._mouseService.getCoords(u,this._screenElement,this._bufferService.cols,this._bufferService.rows,!0);if(L)return L[0]--,L[1]--,L[1]+=this._bufferService.buffer.ydisp,L}_getMouseEventScrollAmount(u){let L=(0,h.getCoordsRelativeToElement)(this._coreBrowserService.window,u,this._screenElement)[1];const M=this._renderService.dimensions.css.canvas.height;return L>=0&&L<=M?0:(L>M&&(L-=M),L=Math.min(Math.max(L,-50),50),L/=50,L/Math.abs(L)+Math.round(14*L))}shouldForceSelection(u){return o.isMac?u.altKey&&this._optionsService.rawOptions.macOptionClickForcesSelection:u.shiftKey}handleMouseDown(u){if(this._mouseDownTimeStamp=u.timeStamp,(u.button!==2||!this.hasSelection)&&u.button===0){if(!this._enabled){if(!this.shouldForceSelection(u))return;u.stopPropagation()}u.preventDefault(),this._dragScrollAmount=0,this._enabled&&u.shiftKey?this._handleIncrementalClick(u):u.detail===1?this._handleSingleClick(u):u.detail===2?this._handleDoubleClick(u):u.detail===3&&this._handleTripleClick(u),this._addMouseDownListeners(),this.refresh(!0)}}_addMouseDownListeners(){this._screenElement.ownerDocument&&(this._screenElement.ownerDocument.addEventListener("mousemove",this._mouseMoveListener),this._screenElement.ownerDocument.addEventListener("mouseup",this._mouseUpListener)),this._dragScrollIntervalTimer=this._coreBrowserService.window.setInterval((()=>this._dragScroll()),50)}_removeMouseDownListeners(){this._screenElement.ownerDocument&&(this._screenElement.ownerDocument.removeEventListener("mousemove",this._mouseMoveListener),this._screenElement.ownerDocument.removeEventListener("mouseup",this._mouseUpListener)),this._coreBrowserService.window.clearInterval(this._dragScrollIntervalTimer),this._dragScrollIntervalTimer=void 0}_handleIncrementalClick(u){this._model.selectionStart&&(this._model.selectionEnd=this._getMouseBufferCoords(u))}_handleSingleClick(u){if(this._model.selectionStartLength=0,this._model.isSelectAllActive=!1,this._activeSelectionMode=this.shouldColumnSelect(u)?3:0,this._model.selectionStart=this._getMouseBufferCoords(u),!this._model.selectionStart)return;this._model.selectionEnd=void 0;const L=this._bufferService.buffer.lines.get(this._model.selectionStart[1]);L&&L.length!==this._model.selectionStart[0]&&L.hasWidth(this._model.selectionStart[0])===0&&this._model.selectionStart[0]++}_handleDoubleClick(u){this._selectWordAtCursor(u,!0)&&(this._activeSelectionMode=1)}_handleTripleClick(u){const L=this._getMouseBufferCoords(u);L&&(this._activeSelectionMode=2,this._selectLineAt(L[1]))}shouldColumnSelect(u){return u.altKey&&!(o.isMac&&this._optionsService.rawOptions.macOptionClickForcesSelection)}_handleMouseMove(u){if(u.stopImmediatePropagation(),!this._model.selectionStart)return;const L=this._model.selectionEnd?[this._model.selectionEnd[0],this._model.selectionEnd[1]]:null;if(this._model.selectionEnd=this._getMouseBufferCoords(u),!this._model.selectionEnd)return void this.refresh(!0);this._activeSelectionMode===2?this._model.selectionEnd[1]<this._model.selectionStart[1]?this._model.selectionEnd[0]=0:this._model.selectionEnd[0]=this._buf
Line truncated
|
||||
\`,h.VT="\\v",h.FF="\\f",h.CR="\\r",h.SO="",h.SI="",h.DLE="",h.DC1="",h.DC2="",h.DC3="",h.DC4="",h.NAK="",h.SYN="",h.ETB="",h.CAN="",h.EM="",h.SUB="",h.ESC="\\x1B",h.FS="",h.GS="",h.RS="",h.US="",h.SP=" ",h.DEL=""})(a||(i.C0=a={})),(function(h){h.PAD="",h.HOP="",h.BPH="",h.NBH="",h.IND="",h.NEL="
",h.SSA="",h.ESA="",h.HTS="",h.HTJ="",h.VTS="",h.PLD="",h.PLU="",h.RI="",h.SS2="",h.SS3="",h.DCS="",h.PU1="",h.PU2="",h.STS="",h.CCH="",h.MW="",h.SPA="",h.EPA="",h.SOS="",h.SGCI="",h.SCI="",h.CSI="",h.ST="",h.OSC="",h.PM="",h.APC=""})(l||(i.C1=l={})),(function(h){h.ST=\`\${a.ESC}\\\\\`})(_||(i.C1_ESCAPED=_={}))},7399:(b,i,a)=>{Object.defineProperty(i,"__esModule",{value:!0}),i.evaluateKeyboardEvent=void 0;const l=a(2584),_={48:["0",")"],49:["1","!"],50:["2","@"],51:["3","#"],52:["4","$"],53:["5","%"],54:["6","^"],55:["7","&"],56:["8","*"],57:["9","("],186:[";",":"],187:["=","+"],188:[",","<"],189:["-","_"],190:[".",">"],191:["/","?"],192:["\`","~"],219:["[","{"],220:["\\\\","|"],221:["]","}"],222:["'",'"']};i.evaluateKeyboardEvent=function(h,d,v,C){const f={type:0,cancel:!1,key:void 0},t=(h.shiftKey?1:0)|(h.altKey?2:0)|(h.ctrlKey?4:0)|(h.metaKey?8:0);switch(h.keyCode){case 0:h.key==="UIKeyInputUpArrow"?f.key=d?l.C0.ESC+"OA":l.C0.ESC+"[A":h.key==="UIKeyInputLeftArrow"?f.key=d?l.C0.ESC+"OD":l.C0.ESC+"[D":h.key==="UIKeyInputRightArrow"?f.key=d?l.C0.ESC+"OC":l.C0.ESC+"[C":h.key==="UIKeyInputDownArrow"&&(f.key=d?l.C0.ESC+"OB":l.C0.ESC+"[B");break;case 8:f.key=h.ctrlKey?"\\b":l.C0.DEL,h.altKey&&(f.key=l.C0.ESC+f.key);break;case 9:if(h.shiftKey){f.key=l.C0.ESC+"[Z";break}f.key=l.C0.HT,f.cancel=!0;break;case 13:f.key=h.altKey?l.C0.ESC+l.C0.CR:l.C0.CR,f.cancel=!0;break;case 27:f.key=l.C0.ESC,h.altKey&&(f.key=l.C0.ESC+l.C0.ESC),f.cancel=!0;break;case 37:if(h.metaKey)break;t?(f.key=l.C0.ESC+"[1;"+(t+1)+"D",f.key===l.C0.ESC+"[1;3D"&&(f.key=l.C0.ESC+(v?"b":"[1;5D"))):f.key=d?l.C0.ESC+"OD":l.C0.ESC+"[D";break;case 39:if(h.metaKey)break;t?(f.key=l.C0.ESC+"[1;"+(t+1)+"C",f.key===l.C0.ESC+"[1;3C"&&(f.key=l.C0.ESC+(v?"f":"[1;5C"))):f.key=d?l.C0.ESC+"OC":l.C0.ESC+"[C";break;case 38:if(h.metaKey)break;t?(f.key=l.C0.ESC+"[1;"+(t+1)+"A",v||f.key!==l.C0.ESC+"[1;3A"||(f.key=l.C0.ESC+"[1;5A")):f.key=d?l.C0.ESC+"OA":l.C0.ESC+"[A";break;case 40:if(h.metaKey)break;t?(f.key=l.C0.ESC+"[1;"+(t+1)+"B",v||f.key!==l.C0.ESC+"[1;3B"||(f.key=l.C0.ESC+"[1;5B")):f.key=d?l.C0.ESC+"OB":l.C0.ESC+"[B";break;case 45:h.shiftKey||h.ctrlKey||(f.key=l.C0.ESC+"[2~");break;case 46:f.key=t?l.C0.ESC+"[3;"+(t+1)+"~":l.C0.ESC+"[3~";break;case 36:f.key=t?l.C0.ESC+"[1;"+(t+1)+"H":d?l.C0.ESC+"OH":l.C0.ESC+"[H";break;case 35:f.key=t?l.C0.ESC+"[1;"+(t+1)+"F":d?l.C0.ESC+"OF":l.C0.ESC+"[F";break;case 33:h.shiftKey?f.type=2:h.ctrlKey?f.key=l.C0.ESC+"[5;"+(t+1)+"~":f.key=l.C0.ESC+"[5~";break;case 34:h.shiftKey?f.type=3:h.ctrlKey?f.key=l.C0.ESC+"[6;"+(t+1)+"~":f.key=l.C0.ESC+"[6~";break;case 112:f.key=t?l.C0.ESC+"[1;"+(t+1)+"P":l.C0.ESC+"OP";break;case 113:f.key=t?l.C0.ESC+"[1;"+(t+1)+"Q":l.C0.ESC+"OQ";break;case 114:f.key=t?l.C0.ESC+"[1;"+(t+1)+"R":l.C0.ESC+"OR";break;case 115:f.key=t?l.C0.ESC+"[1;"+(t+1)+"S":l.C0.ESC+"OS";break;case 116:f.key=t?l.C0.ESC+"[15;"+(t+1)+"~":l.C0.ESC+"[15~";break;case 117:f.key=t?l.C0.ESC+"[17;"+(t+1)+"~":l.C0.ESC+"[17~";break;case 118:f.key=t?l.C0.ESC+"[18;"+(t+1)+"~":l.C0.ESC+"[18~";break;case 119:f.key=t?l.C0.ESC+"[19;"+(t+1)+"~":l.C0.ESC+"[19~";break;case 120:f.key=t?l.C0.ESC+"[20;"+(t+1)+"~":l.C0.ESC+"[20~";break;case 121:f.key=t?l.C0.ESC+"[21;"+(t+1)+"~":l.C0.ESC+"[21~";break;case 122:f.key=t?l.C0.ESC+"[23;"+(t+1)+"~":l.C0.ESC+"[23~";break;case 123:f.key=t?l.C0.ESC+"[24;"+(t+1)+"~":l.C0.ESC+"[24~";break;default:if(!h.ctrlKey||h.shiftKey||h.altKey||h.metaKey)if(v&&!C||!h.altKey||h.metaKey)!v||h.altKey||h.ctrlKey||h.shiftKey||!h.metaKey?h.key&&!h.ctrlKey&&!h.altKey&&!h.metaKey&&h.keyCode>=48&&h.key.length===1?f.key=h.key:h.key&&h.ctrlKey&&(h.key==="_"&&(f.key=l.C0.US),h.key==="@"&&(f.key=l.C0.NUL)):h.keyCode===65&&(f.type=1);else{const o=_[h.keyCode],s=o==null?void 0:o[h.shiftKey?1:0];if(s)f.key=l.C0.ESC+s;else if(h.keyCode>=65&&h.keyCode<=90){const n=h.ctrlKey?h.keyCode-64:h.keyCode+32;let c=String.fromCharCode(n);h.shiftKey&&(c=c.toUpperCase()),f.key=l.C0.ESC+c}else if(h.keyCode===32)f.key=l.C0.ESC+(h.ctrlKey?l.C0.NUL:" ");else if(h.key==="Dead"&&h.code.startsWith("Key")){let n=h.code.slice(3,4);h.shiftKey||(n=n.toLowerCase()),f.key=l.C0.ESC+n,f.cancel=!0}}else h.keyCode>=65&&h.keyCode<=90?f.key=String.fromCharCode(h.keyCode-64):h.keyCode===32?f.key=l.C0.NUL:h.keyCode>=51&&h.keyCode<=55?f.key=String.fromCharCode(h.keyCode-51+27):h.keyCode===56?f.key=l.C0.DEL:h.keyCode===219?f.key=l.C0.ESC:h.keyCode===220?f.key=l.C0.FS:h.keyCode===221&&(f.key=l.C0.GS)}return f}},482:(b,i)=>{Object.defineProperty(i,"__esModule",{value:!0}),i.Utf8ToUtf32=i.StringToUtf32=i.utf32ToString=i.stringFromCodePoint=void 0,i.stringFromCodePoint=function(a){return a>65535?(a-=65536,String.f
Line truncated
|
||||
\`,s)}paste(o){this._core.paste(o)}refresh(o,s){this._verifyIntegers(o,s),this._core.refresh(o,s)}reset(){this._core.reset()}clearTextureAtlas(){this._core.clearTextureAtlas()}loadAddon(o){this._addonManager.loadAddon(this,o)}static get strings(){return i}_verifyIntegers(...o){for(const s of o)if(s===1/0||isNaN(s)||s%1!=0)throw new Error("This API only accepts integers")}_verifyPositiveIntegers(...o){for(const s of o)if(s&&(s===1/0||isNaN(s)||s%1!=0||s<0))throw new Error("This API only accepts positive integers")}}b.Terminal=f})(),R})()))})(bt)),bt.exports}var $s=Ws(),wt={exports:{}},di;function Us(){return di||(di=1,(function(r,e){(function(p,x){r.exports=x()})(self,(()=>(()=>{var p={};return(()=>{var x=p;Object.defineProperty(x,"__esModule",{value:!0}),x.FitAddon=void 0,x.FitAddon=class{activate(m){this._terminal=m}dispose(){}fit(){const m=this.proposeDimensions();if(!m||!this._terminal||isNaN(m.cols)||isNaN(m.rows))return;const R=this._terminal._core;this._terminal.rows===m.rows&&this._terminal.cols===m.cols||(R._renderService.clear(),this._terminal.resize(m.cols,m.rows))}proposeDimensions(){if(!this._terminal||!this._terminal.element||!this._terminal.element.parentElement)return;const m=this._terminal._core,R=m._renderService.dimensions;if(R.css.cell.width===0||R.css.cell.height===0)return;const b=this._terminal.options.scrollback===0?0:m.viewport.scrollBarWidth,i=window.getComputedStyle(this._terminal.element.parentElement),a=parseInt(i.getPropertyValue("height")),l=Math.max(0,parseInt(i.getPropertyValue("width"))),_=window.getComputedStyle(this._terminal.element),h=a-(parseInt(_.getPropertyValue("padding-top"))+parseInt(_.getPropertyValue("padding-bottom"))),d=l-(parseInt(_.getPropertyValue("padding-right"))+parseInt(_.getPropertyValue("padding-left")))-b;return{cols:Math.max(2,Math.floor(d/R.css.cell.width)),rows:Math.max(1,Math.floor(h/R.css.cell.height))}}}})(),p})()))})(wt)),wt.exports}var zs=Us(),yt={exports:{}},_i;function js(){return _i||(_i=1,(function(r,e){(function(p,x){r.exports=x()})(self,(()=>(()=>{var p={6:(b,i)=>{function a(_){try{const h=new URL(_),d=h.password&&h.username?\`\${h.protocol}//\${h.username}:\${h.password}@\${h.host}\`:h.username?\`\${h.protocol}//\${h.username}@\${h.host}\`:\`\${h.protocol}//\${h.host}\`;return _.toLocaleLowerCase().startsWith(d.toLocaleLowerCase())}catch{return!1}}Object.defineProperty(i,"__esModule",{value:!0}),i.LinkComputer=i.WebLinkProvider=void 0,i.WebLinkProvider=class{constructor(_,h,d,v={}){this._terminal=_,this._regex=h,this._handler=d,this._options=v}provideLinks(_,h){const d=l.computeLink(_,this._regex,this._terminal,this._handler);h(this._addCallbacks(d))}_addCallbacks(_){return _.map((h=>(h.leave=this._options.leave,h.hover=(d,v)=>{if(this._options.hover){const{range:C}=h;this._options.hover(d,v,C)}},h)))}};class l{static computeLink(h,d,v,C){const f=new RegExp(d.source,(d.flags||"")+"g"),[t,o]=l._getWindowedLineStrings(h-1,v),s=t.join("");let n;const c=[];for(;n=f.exec(s);){const g=n[0];if(!a(g))continue;const[S,w]=l._mapStrIdx(v,o,0,n.index),[u,L]=l._mapStrIdx(v,S,w,g.length);if(S===-1||w===-1||u===-1||L===-1)continue;const M={start:{x:w+1,y:S+1},end:{x:L,y:u+1}};c.push({range:M,text:g,activate:C})}return c}static _getWindowedLineStrings(h,d){let v,C=h,f=h,t=0,o="";const s=[];if(v=d.buffer.active.getLine(h)){const n=v.translateToString(!0);if(v.isWrapped&&n[0]!==" "){for(t=0;(v=d.buffer.active.getLine(--C))&&t<2048&&(o=v.translateToString(!0),t+=o.length,s.push(o),v.isWrapped&&o.indexOf(" ")===-1););s.reverse()}for(s.push(n),t=0;(v=d.buffer.active.getLine(++f))&&v.isWrapped&&t<2048&&(o=v.translateToString(!0),t+=o.length,s.push(o),o.indexOf(" ")===-1););}return[s,C]}static _mapStrIdx(h,d,v,C){const f=h.buffer.active,t=f.getNullCell();let o=v;for(;C;){const s=f.getLine(d);if(!s)return[-1,-1];for(let n=o;n<s.length;++n){s.getCell(n,t);const c=t.getChars();if(t.getWidth()&&(C-=c.length||1,n===s.length-1&&c==="")){const g=f.getLine(d+1);g&&g.isWrapped&&(g.getCell(0,t),t.getWidth()===2&&(C+=1))}if(C<0)return[d,n]}d++,o=0}return[d,o]}}i.LinkComputer=l}},x={};function m(b){var i=x[b];if(i!==void 0)return i.exports;var a=x[b]={exports:{}};return p[b](a,a.exports,m),a.exports}var R={};return(()=>{var b=R;Object.defineProperty(b,"__esModule",{value:!0}),b.WebLinksAddon=void 0;const i=m(6),a=/(https?|HTTPS?):[/]{2}[^\\s"'!*(){}|\\\\\\^<>\`]*[^\\s"':,.!?{}|\\\\\\^~\\[\\]\`()<>]/;function l(_,h){const d=window.open();if(d){try{d.opener=null}catch{}d.location.href=h}else console.warn("Opening link blocked as opener could not be cleared")}b.WebLinksAddon=class{constructor(_=l,h={}){this._handler=_,this._options=h}activate(_){this._terminal=_;const h=this._options,d=h.urlRegex||a;this._linkProvider=this._terminal.registerLinkProvider(new i.WebLinkProvider(this._terminal,d,this._handler,h))}dispose(){var _;(_=this._linkProvider)==null||_.dispose()}}})(),R})()))})(yt)),yt.exports}var Zs=js();/**
|
||||
* Copyright (c) 2014-2024 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*
|
||||
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
|
||||
* @license MIT
|
||||
*
|
||||
* Originally forked from (with the author's permission):
|
||||
* Fabrice Bellard's javascript vt100 for jslinux:
|
||||
* http://bellard.org/jslinux/
|
||||
* Copyright (c) 2011 Fabrice Bellard
|
||||
*/var qs=(r,e,p,x)=>{for(var m=e,R=r.length-1,b;R>=0;R--)(b=r[R])&&(m=b(m)||m);return m},Ks=(r,e)=>(p,x)=>e(p,x,r),Gs=class{constructor(){this.listeners=[],this.unexpectedErrorHandler=function(r){setTimeout(()=>{throw r.stack?ui.isErrorNoTelemetry(r)?new ui(r.message+\`
|
||||
|
||||
\`+r.stack):new Error(r.message+\`
|
||||
|
||||
\`+r.stack):r},0)}}addListener(r){return this.listeners.push(r),()=>{this._removeListener(r)}}emit(r){this.listeners.forEach(e=>{e(r)})}_removeListener(r){this.listeners.splice(this.listeners.indexOf(r),1)}setUnexpectedErrorHandler(r){this.unexpectedErrorHandler=r}getUnexpectedErrorHandler(){return this.unexpectedErrorHandler}onUnexpectedError(r){this.unexpectedErrorHandler(r),this.emit(r)}onUnexpectedExternalError(r){this.unexpectedErrorHandler(r)}},Vs=new Gs;function xt(r){Xs(r)||Vs.onUnexpectedError(r)}var Yt="Canceled";function Xs(r){return r instanceof Ys?!0:r instanceof Error&&r.name===Yt&&r.message===Yt}var Ys=class extends Error{constructor(){super(Yt),this.name=this.message}},ui=class Jt extends Error{constructor(e){super(e),this.name="CodeExpectedError"}static fromError(e){if(e instanceof Jt)return e;let p=new Jt;return p.message=e.message,p.stack=e.stack,p}static isErrorNoTelemetry(e){return e.name==="CodeExpectedError"}},Js;(r=>{function e(R){return R<0}r.isLessThan=e;function p(R){return R<=0}r.isLessThanOrEqual=p;function x(R){return R>0}r.isGreaterThan=x;function m(R){return R===0}r.isNeitherLessOrGreaterThan=m,r.greaterThan=1,r.lessThan=-1,r.neitherLessOrGreaterThan=0})(Js||(Js={}));function Qs(r,e){let p=this,x=!1,m;return function(){return x||(x=!0,e||(m=r.apply(p,arguments))),m}}var hs;(r=>{function e(c){return c&&typeof c=="object"&&typeof c[Symbol.iterator]=="function"}r.is=e;let p=Object.freeze([]);function x(){return p}r.empty=x;function*m(c){yield c}r.single=m;function R(c){return e(c)?c:m(c)}r.wrap=R;function b(c){return c||p}r.from=b;function*i(c){for(let g=c.length-1;g>=0;g--)yield c[g]}r.reverse=i;function a(c){return!c||c[Symbol.iterator]().next().done===!0}r.isEmpty=a;function l(c){return c[Symbol.iterator]().next().value}r.first=l;function _(c,g){let S=0;for(let w of c)if(g(w,S++))return!0;return!1}r.some=_;function h(c,g){for(let S of c)if(g(S))return S}r.find=h;function*d(c,g){for(let S of c)g(S)&&(yield S)}r.filter=d;function*v(c,g){let S=0;for(let w of c)yield g(w,S++)}r.map=v;function*C(c,g){let S=0;for(let w of c)yield*g(w,S++)}r.flatMap=C;function*f(...c){for(let g of c)yield*g}r.concat=f;function t(c,g,S){let w=S;for(let u of c)w=g(w,u);return w}r.reduce=t;function*o(c,g,S=c.length){for(g<0&&(g+=c.length),S<0?S+=c.length:S>c.length&&(S=c.length);g<S;g++)yield c[g]}r.slice=o;function s(c,g=Number.POSITIVE_INFINITY){let S=[];if(g===0)return[S,c];let w=c[Symbol.iterator]();for(let u=0;u<g;u++){let L=w.next();if(L.done)return[S,r.empty()];S.push(L.value)}return[S,{[Symbol.iterator](){return w}}]}r.consume=s;async function n(c){let g=[];for await(let S of c)g.push(S);return Promise.resolve(g)}r.asyncToArray=n})(hs||(hs={}));function ls(r){if(hs.is(r)){let e=[];for(let p of r)if(p)try{p.dispose()}catch(x){e.push(x)}if(e.length===1)throw e[0];if(e.length>1)throw new AggregateError(e,"Encountered errors while disposing of store");return Array.isArray(r)?[]:r}else if(r)return r.dispose(),r}function cs(...r){return ue(()=>ls(r))}function ue(r){return{dispose:Qs(()=>{r()})}}var ds=class _s{constructor(){this._toDispose=new Set,this._isDisposed=!1}dispose(){this._isDisposed||(this._isDisposed=!0,this.clear())}get isDisposed(){return this._isDisposed}clear(){if(this._toDispose.size!==0)try{ls(this._toDispose)}finally{this._toDispose.clear()}}add(e){if(!e)return e;if(e===this)throw new Error("Cannot register a disposable on itself!");return this._isDisposed?_s.DISABLE_DISPOSED_WARNING||console.warn(new Error("Trying to add a disposable to a DisposableStore that has already been disposed of. The added object will be leaked!").stack):this._toDispose.add(e),e}delete(e){if(e){if(e===this)throw new Error("Cannot dispose a disposable on itself!");this._toDispose.delete(e),e.dispose()}}deleteAndLeak(e){e&&this._toDispose.has(e)&&(this._toDispose.delete(e),void 0)}};ds.DISABLE_DISPOSED_WARNING=!1;var Ge=ds,we=class{constructor(){this._store=new Ge,this._store}dispose(){this._store.dispose()}_register(r){if(r===this)throw new Error("Cannot register a disposable on itself!");return this._store.add(r)}};we.None=Object.freeze({dispose(){}});var et=class{constructor(){this._isDisposed=!1}get value(){return this._isDisposed?void 0:this._value}set value(r){var e;this._isDisposed||r===this._value||((e=this._value)==null||e.dispose(),this._value=r)}clear(){this.value=void 0}dispose(){var r;this._isDisposed=!0,(r=this._value)==null||r.dispose(),this._value=void 0}clearAndLeak(){let r=this._value;return this._value=void 0,r}},ri=typeof process<"u"&&"title"in process,mt=ri?"node":navigator.userAgent,er=ri?"node":navigator.platform,tr=mt.includes("Firefox"),ir=mt.includes("Edge"),us=/^((?!chrome|android).)*safari/i.test(mt);function sr(){if(!us)return 0;let r=mt.match(/Version\\/(\\d+)/);return r===null||r.length<2?0:parseInt(r[1])}er.indexOf("Linux")>=0;var rr="",oe=0,ae=0,he=0,se=0,me={css:"#00000000",rgba:0},ve;(r=>{function e(m,R,b,i){return i!==void 0?\`#\${He(m)}\${He(R)}\${He(b)}\${He(i)}\`:\`#\Line truncated
|
||||
\`).slice(2).join(\`
|
||||
\`))}},Tr=class extends Error{constructor(r,e){super(r),this.name="ListenerLeakError",this.stack=e}},Br=class extends Error{constructor(r,e){super(r),this.name="ListenerRefusalError",this.stack=e}},Ir=0,Lt=class{constructor(r){this.value=r,this.id=Ir++}},Or=2,Pr,Q=class{constructor(r){var e,p,x,m;this._size=0,this._options=r,this._leakageMon=(e=this._options)!=null&&e.leakWarningThreshold?new Mr((r==null?void 0:r.onListenerError)??xt,((p=this._options)==null?void 0:p.leakWarningThreshold)??Ar):void 0,this._perfMon=(x=this._options)!=null&&x._profName?new Rr(this._options._profName):void 0,this._deliveryQueue=(m=this._options)==null?void 0:m.deliveryQueue}dispose(){var r,e,p,x;this._disposed||(this._disposed=!0,((r=this._deliveryQueue)==null?void 0:r.current)===this&&this._deliveryQueue.reset(),this._listeners&&(this._listeners=void 0,this._size=0),(p=(e=this._options)==null?void 0:e.onDidRemoveLastListener)==null||p.call(e),(x=this._leakageMon)==null||x.dispose())}get event(){return this._event??(this._event=(r,e,p)=>{var b,i,a,l,_;if(this._leakageMon&&this._size>this._leakageMon.threshold**2){let h=\`[\${this._leakageMon.name}] REFUSES to accept new listeners because it exceeded its threshold by far (\${this._size} vs \${this._leakageMon.threshold})\`;console.warn(h);let d=this._leakageMon.getMostFrequentStack()??["UNKNOWN stack",-1],v=new Br(\`\${h}. HINT: Stack shows most frequent listener (\${d[1]}-times)\`,d[0]);return(((b=this._options)==null?void 0:b.onListenerError)||xt)(v),we.None}if(this._disposed)return we.None;e&&(r=r.bind(e));let x=new Lt(r),m;this._leakageMon&&this._size>=Math.ceil(this._leakageMon.threshold*.2)&&(x.stack=Dr.create(),m=this._leakageMon.check(x.stack,this._size+1)),this._listeners?this._listeners instanceof Lt?(this._deliveryQueue??(this._deliveryQueue=new Fr),this._listeners=[this._listeners,x]):this._listeners.push(x):((a=(i=this._options)==null?void 0:i.onWillAddFirstListener)==null||a.call(i,this),this._listeners=x,(_=(l=this._options)==null?void 0:l.onDidAddFirstListener)==null||_.call(l,this)),this._size++;let R=ue(()=>{m==null||m(),this._removeListener(x)});return p instanceof Ge?p.add(R):Array.isArray(p)&&p.push(R),R}),this._event}_removeListener(r){var m,R,b,i;if((R=(m=this._options)==null?void 0:m.onWillRemoveListener)==null||R.call(m,this),!this._listeners)return;if(this._size===1){this._listeners=void 0,(i=(b=this._options)==null?void 0:b.onDidRemoveLastListener)==null||i.call(b,this),this._size=0;return}let e=this._listeners,p=e.indexOf(r);if(p===-1)throw console.log("disposed?",this._disposed),console.log("size?",this._size),console.log("arr?",JSON.stringify(this._listeners)),new Error("Attempted to dispose unknown listener");this._size--,e[p]=void 0;let x=this._deliveryQueue.current===this;if(this._size*Or<=e.length){let a=0;for(let l=0;l<e.length;l++)e[l]?e[a++]=e[l]:x&&(this._deliveryQueue.end--,a<this._deliveryQueue.i&&this._deliveryQueue.i--);e.length=a}}_deliver(r,e){var x;if(!r)return;let p=((x=this._options)==null?void 0:x.onListenerError)||xt;if(!p){r.value(e);return}try{r.value(e)}catch(m){p(m)}}_deliverQueue(r){let e=r.current._listeners;for(;r.i<r.end;)this._deliver(e[r.i++],r.value);r.reset()}fire(r){var e,p,x,m;if((e=this._deliveryQueue)!=null&&e.current&&(this._deliverQueue(this._deliveryQueue),(p=this._perfMon)==null||p.stop()),(x=this._perfMon)==null||x.start(this._size),this._listeners)if(this._listeners instanceof Lt)this._deliver(this._listeners,r);else{let R=this._deliveryQueue;R.enqueue(this,r,this._listeners.length),this._deliverQueue(R)}(m=this._perfMon)==null||m.stop()}hasListeners(){return this._size>0}},Fr=class{constructor(){this.i=-1,this.end=0}enqueue(r,e,p){this.i=0,this.end=p,this.current=r,this.value=e}reset(){this.i=this.end,this.current=void 0,this.value=void 0}},mi={texturePage:0,texturePosition:{x:0,y:0},texturePositionClipSpace:{x:0,y:0},offset:{x:0,y:0},size:{x:0,y:0},sizeClipSpace:{x:0,y:0}},tt=2,it,Pe=class Ze{constructor(e,p,x){this._document=e,this._config=p,this._unicodeService=x,this._didWarmUp=!1,this._cacheMap=new gi,this._cacheMapCombined=new gi,this._pages=[],this._activePages=[],this._workBoundingBox={top:0,left:0,bottom:0,right:0},this._workAttributeData=new qe,this._textureSize=512,this._onAddTextureAtlasCanvas=new Q,this.onAddTextureAtlasCanvas=this._onAddTextureAtlasCanvas.event,this._onRemoveTextureAtlasCanvas=new Q,this.onRemoveTextureAtlasCanvas=this._onRemoveTextureAtlasCanvas.event,this._requestClearModel=!1,this._createNewPage(),this._tmpCanvas=Ls(e,this._config.deviceCellWidth*4+tt*2,this._config.deviceCellHeight+tt*2),this._tmpCtx=re(this._tmpCanvas.getContext("2d",{alpha:this._config.allowTransparency,willReadFrequently:!0}))}get pages(){return this._pages}dispose(){this._tmpCanvas.remove();for(let e of this.pages)e.canvas.remove();this._onAddTextureAtlasCanvas.dispose()}warmUp(){this._didWarmUp||(this._doWarmUp(),this._didWarmUp=!0)}_doWarmUp(){let e=new Er;for(let p=33;p<126;p++)e.enqueue(()=>{if(!t
Line truncated
|
||||
layout (location = 0) in vec2 a_unitquad;
|
||||
layout (location = 1) in vec2 a_cellpos;
|
||||
layout (location = 2) in vec2 a_offset;
|
||||
layout (location = 3) in vec2 a_size;
|
||||
layout (location = 4) in float a_texpage;
|
||||
layout (location = 5) in vec2 a_texcoord;
|
||||
layout (location = 6) in vec2 a_texsize;
|
||||
|
||||
uniform mat4 u_projection;
|
||||
uniform vec2 u_resolution;
|
||||
|
||||
out vec2 v_texcoord;
|
||||
flat out int v_texpage;
|
||||
|
||||
void main() {
|
||||
vec2 zeroToOne = (a_offset / u_resolution) + a_cellpos + (a_unitquad * a_size);
|
||||
gl_Position = u_projection * vec4(zeroToOne, 0.0, 1.0);
|
||||
v_texpage = int(a_texpage);
|
||||
v_texcoord = a_texcoord + a_unitquad * a_texsize;
|
||||
}\`;function qr(r){let e="";for(let p=1;p<r;p++)e+=\` else if (v_texpage == \${p}) { outColor = texture(u_texture[\${p}], v_texcoord); }\`;return\`#version 300 es
|
||||
precision lowp float;
|
||||
|
||||
in vec2 v_texcoord;
|
||||
flat in int v_texpage;
|
||||
|
||||
uniform sampler2D u_texture[\${r}];
|
||||
|
||||
out vec4 outColor;
|
||||
|
||||
void main() {
|
||||
if (v_texpage == 0) {
|
||||
outColor = texture(u_texture[0], v_texcoord);
|
||||
} \${e}
|
||||
}\`}var Fe=11,ze=Fe*Float32Array.BYTES_PER_ELEMENT,Kr=2,ie=0,J,At=0,st=0,Gr=class extends we{constructor(r,e,p,x){super(),this._terminal=r,this._gl=e,this._dimensions=p,this._optionsService=x,this._activeBuffer=0,this._vertices={count:0,attributes:new Float32Array(0),attributesBuffers:[new Float32Array(0),new Float32Array(0)]};let m=this._gl;Pe.maxAtlasPages===void 0&&(Pe.maxAtlasPages=Math.min(32,re(m.getParameter(m.MAX_TEXTURE_IMAGE_UNITS))),Pe.maxTextureSize=re(m.getParameter(m.MAX_TEXTURE_SIZE))),this._program=re(Ms(m,Zr,qr(Pe.maxAtlasPages))),this._register(ue(()=>m.deleteProgram(this._program))),this._projectionLocation=re(m.getUniformLocation(this._program,"u_projection")),this._resolutionLocation=re(m.getUniformLocation(this._program,"u_resolution")),this._textureLocation=re(m.getUniformLocation(this._program,"u_texture")),this._vertexArrayObject=m.createVertexArray(),m.bindVertexArray(this._vertexArrayObject);let R=new Float32Array([0,0,1,0,0,1,1,1]),b=m.createBuffer();this._register(ue(()=>m.deleteBuffer(b))),m.bindBuffer(m.ARRAY_BUFFER,b),m.bufferData(m.ARRAY_BUFFER,R,m.STATIC_DRAW),m.enableVertexAttribArray(0),m.vertexAttribPointer(0,2,this._gl.FLOAT,!1,0,0);let i=new Uint8Array([0,1,2,3]),a=m.createBuffer();this._register(ue(()=>m.deleteBuffer(a))),m.bindBuffer(m.ELEMENT_ARRAY_BUFFER,a),m.bufferData(m.ELEMENT_ARRAY_BUFFER,i,m.STATIC_DRAW),this._attributesBuffer=re(m.createBuffer()),this._register(ue(()=>m.deleteBuffer(this._attributesBuffer))),m.bindBuffer(m.ARRAY_BUFFER,this._attributesBuffer),m.enableVertexAttribArray(2),m.vertexAttribPointer(2,2,m.FLOAT,!1,ze,0),m.vertexAttribDivisor(2,1),m.enableVertexAttribArray(3),m.vertexAttribPointer(3,2,m.FLOAT,!1,ze,2*Float32Array.BYTES_PER_ELEMENT),m.vertexAttribDivisor(3,1),m.enableVertexAttribArray(4),m.vertexAttribPointer(4,1,m.FLOAT,!1,ze,4*Float32Array.BYTES_PER_ELEMENT),m.vertexAttribDivisor(4,1),m.enableVertexAttribArray(5),m.vertexAttribPointer(5,2,m.FLOAT,!1,ze,5*Float32Array.BYTES_PER_ELEMENT),m.vertexAttribDivisor(5,1),m.enableVertexAttribArray(6),m.vertexAttribPointer(6,2,m.FLOAT,!1,ze,7*Float32Array.BYTES_PER_ELEMENT),m.vertexAttribDivisor(6,1),m.enableVertexAttribArray(1),m.vertexAttribPointer(1,2,m.FLOAT,!1,ze,9*Float32Array.BYTES_PER_ELEMENT),m.vertexAttribDivisor(1,1),m.useProgram(this._program);let l=new Int32Array(Pe.maxAtlasPages);for(let _=0;_<Pe.maxAtlasPages;_++)l[_]=_;m.uniform1iv(this._textureLocation,l),m.uniformMatrix4fv(this._projectionLocation,!1,As),this._atlasTextures=[];for(let _=0;_<Pe.maxAtlasPages;_++){let h=new jr(re(m.createTexture()));this._register(ue(()=>m.deleteTexture(h.texture))),m.activeTexture(m.TEXTURE0+_),m.bindTexture(m.TEXTURE_2D,h.texture),m.texParameteri(m.TEXTURE_2D,m.TEXTURE_WRAP_S,m.CLAMP_TO_EDGE),m.texParameteri(m.TEXTURE_2D,m.TEXTURE_WRAP_T,m.CLAMP_TO_EDGE),m.texImage2D(m.TEXTURE_2D,0,m.RGBA,1,1,0,m.RGBA,m.UNSIGNED_BYTE,new Uint8Array([255,0,0,255])),this._atlasTextures[_]=h}m.enable(m.BLEND),m.blendFunc(m.SRC_ALPHA,m.ONE_MINUS_SRC_ALPHA),this.handleResize()}beginFrame(){return this._atlas?this._atlas.beginFrame():!0}updateCell(r,e,p,x,m,R,b,i,a){this._updateCell(this._vertices.attributes,r,e,p,x,m,R,b,i,a)}_updateCell(r,e,p,x,m,R,b,i,a,l){if(ie=(p*this._terminal.cols+e)*Fe,x===0||x===void 0){r.fill(0,ie,ie+Fe-1-Kr);return}this._atlas&&(i&&i.length>1?J=this._atlas.getRasterizedGlyphCombinedChar(i,m,R,b,!1,this._terminal.element):J=this._atlas.getRasterizedGlyph(x,m,R,b,!1,this._terminal.element),At=Math.floor((this._dimensions.device.cell.width-this._dimensions.device.char.width)/2),m!==l&&J.offset.x>At?(st=J.offset.x-At,r[ie]=-(J.offset.x-st)+this._dimensions.device.char.left,r[ie+1]=-J.offset.y+this._dimensions.device.char.top,r[ie+2]=(J.size.x-st)/this._dimensions.device.canvas.width,r[ie+3]=J.size.y/this._dimensions.device.canvas.height,r[ie+4]=J.texturePage,r[ie+5]=J.texturePositionClipSpace.x+st/this._atlas.pages[J.texturePage].canvas.width,r[ie+6]=J.texturePositionClipSpace.y,r[ie+7]=J.sizeClipSpace.x-st/this._atlas.pages[J.texturePage].canvas.width,r[ie+8]=J.sizeClipSpace.y):(r[ie]=-J.offset.x+this._dimensions.device.char.left,r[ie+1]=-J.offset.y+this._dimensions.device.char.top,r[ie+2]=J.size.x/this._dimensions.device.canvas.width,r[ie+3]=J.size.y/this._dimensions.device.canvas.height,r[ie+4]=J.texturePage,r[ie+5]=J.texturePositionClipSpace.x,r[ie+6]=J.texturePositionClipSpace.y,r[ie+7]=J.sizeClipSpace.x,r[ie+8]=J.sizeClipSpace.y),this._optionsService.rawOptions.rescaleOverlappingGlyphs&&cr(x,a,J.size.x,this._dimensions.device.cell.width)&&(r[ie+2]=(this._dimensions.device.cell.width-1)/this._dimensions.device.canvas.width))}clear(){let r=this._terminal,e=r.cols*r.rows*Fe;this._vertices.count!==e?this._vertices.attributes=new Float32Array(e):this._vertices.attributes.fill(0);let p=0;for(;p<this._vertices.attributesBuffers.length;p++)this._vertices.count!==e?this._vertices.attributesBuffers[p]=new Float32Array(e):this._vertices.attributesBuffers[p].fill(0);this._vertices.count=e,p=0;for
Line truncated
|
||||
layout (location = 0) in vec2 a_position;
|
||||
layout (location = 1) in vec2 a_size;
|
||||
layout (location = 2) in vec4 a_color;
|
||||
layout (location = 3) in vec2 a_unitquad;
|
||||
|
||||
uniform mat4 u_projection;
|
||||
|
||||
out vec4 v_color;
|
||||
|
||||
void main() {
|
||||
vec2 zeroToOne = a_position + (a_unitquad * a_size);
|
||||
gl_Position = u_projection * vec4(zeroToOne, 0.0, 1.0);
|
||||
v_color = a_color;
|
||||
}\`,en=\`#version 300 es
|
||||
precision lowp float;
|
||||
|
||||
in vec4 v_color;
|
||||
|
||||
out vec4 outColor;
|
||||
|
||||
void main() {
|
||||
outColor = v_color;
|
||||
}\`,Me=8,Dt=Me*Float32Array.BYTES_PER_ELEMENT,tn=20*Me,xi=class{constructor(){this.attributes=new Float32Array(tn),this.count=0}},Ae=0,Ei=0,Li=0,ki=0,Ri=0,Ai=0,Mi=0,sn=class extends we{constructor(r,e,p,x){super(),this._terminal=r,this._gl=e,this._dimensions=p,this._themeService=x,this._vertices=new xi,this._verticesCursor=new xi;let m=this._gl;this._program=re(Ms(m,Qr,en)),this._register(ue(()=>m.deleteProgram(this._program))),this._projectionLocation=re(m.getUniformLocation(this._program,"u_projection")),this._vertexArrayObject=m.createVertexArray(),m.bindVertexArray(this._vertexArrayObject);let R=new Float32Array([0,0,1,0,0,1,1,1]),b=m.createBuffer();this._register(ue(()=>m.deleteBuffer(b))),m.bindBuffer(m.ARRAY_BUFFER,b),m.bufferData(m.ARRAY_BUFFER,R,m.STATIC_DRAW),m.enableVertexAttribArray(3),m.vertexAttribPointer(3,2,this._gl.FLOAT,!1,0,0);let i=new Uint8Array([0,1,2,3]),a=m.createBuffer();this._register(ue(()=>m.deleteBuffer(a))),m.bindBuffer(m.ELEMENT_ARRAY_BUFFER,a),m.bufferData(m.ELEMENT_ARRAY_BUFFER,i,m.STATIC_DRAW),this._attributesBuffer=re(m.createBuffer()),this._register(ue(()=>m.deleteBuffer(this._attributesBuffer))),m.bindBuffer(m.ARRAY_BUFFER,this._attributesBuffer),m.enableVertexAttribArray(0),m.vertexAttribPointer(0,2,m.FLOAT,!1,Dt,0),m.vertexAttribDivisor(0,1),m.enableVertexAttribArray(1),m.vertexAttribPointer(1,2,m.FLOAT,!1,Dt,2*Float32Array.BYTES_PER_ELEMENT),m.vertexAttribDivisor(1,1),m.enableVertexAttribArray(2),m.vertexAttribPointer(2,4,m.FLOAT,!1,Dt,4*Float32Array.BYTES_PER_ELEMENT),m.vertexAttribDivisor(2,1),this._updateCachedColors(x.colors),this._register(this._themeService.onChangeColors(l=>{this._updateCachedColors(l),this._updateViewportRectangle()}))}renderBackgrounds(){this._renderVertices(this._vertices)}renderCursor(){this._renderVertices(this._verticesCursor)}_renderVertices(r){let e=this._gl;e.useProgram(this._program),e.bindVertexArray(this._vertexArrayObject),e.uniformMatrix4fv(this._projectionLocation,!1,As),e.bindBuffer(e.ARRAY_BUFFER,this._attributesBuffer),e.bufferData(e.ARRAY_BUFFER,r.attributes,e.DYNAMIC_DRAW),e.drawElementsInstanced(this._gl.TRIANGLE_STRIP,4,e.UNSIGNED_BYTE,0,r.count)}handleResize(){this._updateViewportRectangle()}setDimensions(r){this._dimensions=r}_updateCachedColors(r){this._bgFloat=this._colorToFloat32Array(r.background),this._cursorFloat=this._colorToFloat32Array(r.cursor)}_updateViewportRectangle(){this._addRectangleFloat(this._vertices.attributes,0,0,0,this._terminal.cols*this._dimensions.device.cell.width,this._terminal.rows*this._dimensions.device.cell.height,this._bgFloat)}updateBackgrounds(r){let e=this._terminal,p=this._vertices,x=1,m,R,b,i,a,l,_,h,d,v,C;for(m=0;m<e.rows;m++){for(b=-1,i=0,a=0,l=!1,R=0;R<e.cols;R++)_=(m*e.cols+R)*gt,h=r.cells[_+ft],d=r.cells[_+vt],v=!!(d&67108864),(h!==i||d!==a&&(l||v))&&((i!==0||l&&a!==0)&&(C=x++*Me,this._updateRectangle(p,C,a,i,b,R,m)),b=R,i=h,a=d,l=v);(i!==0||l&&a!==0)&&(C=x++*Me,this._updateRectangle(p,C,a,i,b,e.cols,m))}p.count=x}updateCursor(r){let e=this._verticesCursor,p=r.cursor;if(!p||p.style==="block"){e.count=0;return}let x,m=0;(p.style==="bar"||p.style==="outline")&&(x=m++*Me,this._addRectangleFloat(e.attributes,x,p.x*this._dimensions.device.cell.width,p.y*this._dimensions.device.cell.height,p.style==="bar"?p.dpr*p.cursorWidth:p.dpr,this._dimensions.device.cell.height,this._cursorFloat)),(p.style==="underline"||p.style==="outline")&&(x=m++*Me,this._addRectangleFloat(e.attributes,x,p.x*this._dimensions.device.cell.width,(p.y+1)*this._dimensions.device.cell.height-p.dpr,p.width*this._dimensions.device.cell.width,p.dpr,this._cursorFloat)),p.style==="outline"&&(x=m++*Me,this._addRectangleFloat(e.attributes,x,p.x*this._dimensions.device.cell.width,p.y*this._dimensions.device.cell.height,p.width*this._dimensions.device.cell.width,p.dpr,this._cursorFloat),x=m++*Me,this._addRectangleFloat(e.attributes,x,(p.x+p.width)*this._dimensions.device.cell.width-p.dpr,p.y*this._dimensions.device.cell.height,p.dpr,this._dimensions.device.cell.height,this._cursorFloat)),e.count=m}_updateRectangle(r,e,p,x,m,R,b){if(p&67108864)switch(p&50331648){case 16777216:case 33554432:Ae=this._themeService.colors.ansi[p&255].rgba;break;case 50331648:Ae=(p&16777215)<<8;break;case 0:default:Ae=this._themeService.colors.foreground.rgba}else switch(x&50331648){case 16777216:case 33554432:Ae=this._themeService.colors.ansi[x&255].rgba;break;case 50331648:Ae=(x&16777215)<<8;break;case 0:default:Ae=this._themeService.colors.background.rgba}r.attributes.length<e+4&&(r.attributes=zr(r.attributes,this._terminal.rows*this._terminal.cols*Me)),Ei=m*this._dimensions.device.cell.width,Li=b*this._dimensions.device.cell.height,ki=(Ae>>24&255)/255,Ri=(Ae>>16&255)/255,Ai=(Ae>>8&255)/255,Mi=1,this._addRectangle(r.attributes,e,Ei,Li,(R-m)*this._dimensions.device.cell.width,this._dimensions.device.cell.height,ki,Ri,Ai,Mi)}_addRectangle(r,e,p,x,m,R,b,i,a,l){r[e]=p/this._dimensions.device.canvas.width,r[e+1]=x/this._dimensions.device.canvas.heig
Line truncated
|
||||
\`+x+" ("+m+" bytes)")){p.skip();return}const b=[];p.on("input",i=>{b.push(i)}),p.accept().then(()=>{Xt.Browser.save_to_disk(b,x)})}),e.start()}handleSend(e){const p=document.createElement("input");p.type="file",p.multiple=!0,p.style.display="none",p.onchange=x=>{const m=x.target.files;m&&m.length>0?Xt.Browser.send_files(e,m,{on_offer_response:(R,b)=>{b||console.warn("Server rejected file:",R.name)},on_progress:(R,b,i)=>{},on_file_complete:R=>{console.log("Finished uploading:",R.name)}}).then(()=>{e.close()}):e.close()},document.body.appendChild(p),p.click(),document.body.removeChild(p)}}const es={cyberpunk:{background:"#0a0a0a",foreground:"#4af626",cursor:"#14d1ff",cursorAccent:"#0a0a0a",selectionBackground:"#273747"},glacier:{background:"#0a192f",foreground:"#64ffda",cursor:"#e6f1ff",cursorAccent:"#0a192f",selectionBackground:"#112240"},gruvbox:{background:"#282828",foreground:"#ebdbb2",cursor:"#d3869b",cursorAccent:"#282828",selectionBackground:"#504945"}};class Hn{constructor(e){le(this,"terminal");le(this,"fitAddon");le(this,"webglAddon");le(this,"ws",null);le(this,"container");le(this,"config",null);le(this,"roamId",null);le(this,"reconnecting",!1);le(this,"reconnectAttempts",0);le(this,"maxReconnectAttempts",5);this.container=document.getElementById(e),this.terminal=new $s.Terminal({cursorBlink:!0,cursorStyle:"block",fontSize:14,fontFamily:'"JetBrains Mono", "Fira Code", "Cascadia Code", monospace',theme:es.cyberpunk,allowProposedApi:!0,scrollback:1e4}),this.fitAddon=new zs.FitAddon,this.terminal.loadAddon(this.fitAddon),this.terminal.loadAddon(new Zs.WebLinksAddon),window.addEventListener("resize",()=>this.fit()),this.container.addEventListener("contextmenu",async p=>{var x;p.preventDefault();try{const m=await navigator.clipboard.readText();m&&((x=this.ws)==null?void 0:x.readyState)===WebSocket.OPEN&&this.ws.send(m)}catch(m){console.error("Failed to read clipboard",m)}})}setTheme(e){this.terminal.options.theme=es[e]}mount(){this.terminal.open(this.container);try{this.webglAddon=new kn,this.webglAddon.onContextLoss(e=>{console.warn("WebGL context lost",e),this.webglAddon.dispose()}),this.terminal.loadAddon(this.webglAddon)}catch(e){console.warn("WebGL addon failed to load, falling back to canvas/dom",e)}this.fit(),this.terminal.writeln("\\x1B[1;33m╔══════════════════════════════════╗\\x1B[0m"),this.terminal.writeln("\\x1B[1;33m║ Connecting to CloudSSH ║\\x1B[0m"),this.terminal.writeln("\\x1B[1;33m╚══════════════════════════════════╝\\x1B[0m"),this.terminal.writeln("")}attemptReconnect(){if(this.reconnectAttempts>=this.maxReconnectAttempts||!this.roamId||!this.config){this.terminal.writeln("\\x1B[31m[!] 连接已断开,无法恢复。\\x1B[0m"),document.getElementById("status-text").innerHTML='<span class="w-2 h-2 bg-[#ea6c73] inline-block"></span> STATUS: DISCONNECTED';return}this.reconnecting=!0,this.reconnectAttempts++,this.terminal.writeln(\`\\x1B[33m[*] 正在尝试恢复连接 (\${this.reconnectAttempts}/\${this.maxReconnectAttempts})...\\x1B[0m\`),setTimeout(()=>{this.connect(this.config,this.roamId)},2e3)}async connect(e,p){p||(this.config=e,this.reconnectAttempts=0,this.roamId=null);const x=new URL(window.location.href);return x.protocol=x.protocol==="https:"?"wss:":"ws:",x.pathname="/api/ssh",new Promise((m,R)=>{this.ws=new WebSocket(x.toString()),this.ws.onopen=()=>{var a;p||this.terminal.writeln("\\x1B[32m[+] WebSocket connected, sending credentials...\\x1B[0m");const i=p?{roamId:p}:{host:e.host,port:e.port,username:e.username,password:e.password,authMethod:e.authMethod,privateKey:e.privateKey};(a=this.ws)==null||a.send(JSON.stringify(i)),p&&(this.reconnectAttempts=0,this.reconnecting=!1),m()},this.ws.onerror=()=>{this.reconnecting||R(new Error("WebSocket connection failed"))},this.ws.onclose=()=>{!this.reconnecting&&this.roamId?this.attemptReconnect():this.reconnecting||(this.terminal.writeln("\\x1B[31m[-] 连接已关闭\\x1B[0m"),document.getElementById("status-text").innerHTML='<span class="w-2 h-2 bg-[#353534] inline-block"></span> STATUS: OFFLINE')};const b=new Fn(i=>this.terminal.write(i),i=>{var a;((a=this.ws)==null?void 0:a.readyState)===WebSocket.OPEN&&this.ws.send(i)});this.ws.onmessage=i=>{if(typeof i.data=="string")try{const a=JSON.parse(i.data);switch(a.type){case"roamId":this.roamId=a.roamId;break;case"status":this.terminal.writeln(\`\\x1B[32m[*] \${a.message}\\x1B[0m\`),(a.message==="认证成功"||a.message==="会话已恢复")&&(document.getElementById("status-text").innerHTML='<span class="w-2 h-2 bg-[#4af626] inline-block animate-pulse"></span> STATUS: ONLINE');break;case"error":this.terminal.writeln(\`\\x1B[31m[!] \${a.message}\\x1B[0m\`);break}}catch{this.terminal.write(i.data)}else{const a=new FileReader;a.onload=()=>{b.consume(a.result)},a.readAsArrayBuffer(i.data)}},this.ws.onclose=i=>{this.terminal.writeln
Line truncated
|
||||
<form class="space-y-6" id="connection-form">
|
||||
<div class="grid grid-cols-4 gap-4">
|
||||
<div class="col-span-3">
|
||||
@@ -73,13 +148,26 @@ WARNING: This link could potentially be dangerous\`)){const s=window.open();if(s
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-bold tracking-[0.1em] text-[#bbccb0] mb-2">AUTH_KEY</label>
|
||||
<div class="flex items-center">
|
||||
<span class="material-symbols-outlined text-[#bbccb0] mr-2" style="font-size: 16px;">key</span>
|
||||
<input id="password" class="terminal-input text-[13px]" placeholder="••••••••" type="password" required>
|
||||
<label class="block text-xs font-bold tracking-[0.1em] text-[#bbccb0] mb-2">AUTH_METHOD</label>
|
||||
<div class="flex gap-2 mb-3">
|
||||
<button type="button" id="auth-tab-password" class="auth-tab px-3 py-1 text-[11px] font-bold tracking-[0.1em] border border-[#4af626] text-[#4af626] bg-transparent cursor-pointer transition-all" style="background:#4af626;color:#0a0a0a;">PASSWORD</button>
|
||||
<button type="button" id="auth-tab-key" class="auth-tab px-3 py-1 text-[11px] font-bold tracking-[0.1em] border border-[#3c4b36] text-[#bbccb0] bg-transparent cursor-pointer transition-all">PRIVATE_KEY</button>
|
||||
</div>
|
||||
<div id="auth-password-section">
|
||||
<div class="flex items-center">
|
||||
<span class="material-symbols-outlined text-[#bbccb0] mr-2" style="font-size: 16px;">key</span>
|
||||
<input id="password" class="terminal-input text-[13px]" placeholder="••••••••" type="password">
|
||||
</div>
|
||||
</div>
|
||||
<div id="auth-key-section" style="display:none;">
|
||||
<textarea id="private-key" class="terminal-input text-[11px] w-full" rows="5" placeholder="-----BEGIN OPENSSH PRIVATE KEY----- ...粘贴 Ed25519 私钥内容... -----END OPENSSH PRIVATE KEY-----" style="resize:vertical;border:1px solid #3c4b36;padding:8px;"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pt-6">
|
||||
<div class="flex items-center gap-2 mt-2">
|
||||
<input type="checkbox" id="remember-me" class="accent-[#4af626] w-4 h-4 cursor-pointer">
|
||||
<label for="remember-me" class="text-xs text-[#bbccb0] cursor-pointer select-none">REMEMBER_CONNECTION</label>
|
||||
</div>
|
||||
<div class="pt-4">
|
||||
<button id="connect-btn" class="cyber-button w-full py-3 px-4 text-xs font-bold tracking-[0.1em] uppercase flex items-center justify-center gap-2 bg-[#4af626] text-[#022100]" type="button">
|
||||
<span class="material-symbols-outlined" style="font-size: 18px;">power_settings_new</span>
|
||||
Execute_Connection
|
||||
@@ -91,7 +179,7 @@ WARNING: This link could potentially be dangerous\`)){const s=window.open();if(s
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
\`,document.getElementById("connect-btn").addEventListener("click",()=>{this.handleConnect()}),document.getElementById("connection-form").addEventListener("keypress",J=>{J.key==="Enter"&&this.handleConnect()})}async handleConnect(){const K=document.getElementById("host").value,J=parseInt(document.getElementById("port").value||"22"),G=document.getElementById("username").value,j=document.getElementById("password").value;if(!K||!G||!j){alert("请填写所有必填字段");return}const V=document.getElementById("auth-section"),A=document.getElementById("terminal-section");V.classList.add("hidden"),A.classList.remove("hidden"),A.classList.add("flex"),document.getElementById("term-host").textContent="Host: "+K,document.getElementById("term-user").textContent="User: "+G,document.getElementById("term-port").textContent="Port: "+J,this.terminal.mount();try{await this.terminal.connect({host:K,port:J,username:G,password:j})}catch{A.classList.add("hidden"),A.classList.remove("flex"),V.classList.remove("hidden"),document.getElementById("status-text").innerHTML='<span class="w-2 h-2 bg-[#353534] inline-block"></span> STATUS: OFFLINE'}}}const ke=new Pe("terminal-container");new Ie(ke);var ye;(ye=document.getElementById("disconnect-btn"))==null||ye.addEventListener("click",()=>{ke.disconnect();const Q=document.getElementById("terminal-section");Q.classList.add("hidden"),Q.classList.remove("flex"),document.getElementById("auth-section").classList.remove("hidden"),document.getElementById("status-text").innerHTML='<span class="w-2 h-2 bg-[#353534] inline-block"></span> STATUS: OFFLINE'});const we=document.getElementById("copyright-year");we&&(we.textContent=new Date().getFullYear().toString());
|
||||
\`,document.getElementById("connect-btn").addEventListener("click",()=>{this.handleConnect()}),document.getElementById("connection-form").addEventListener("keypress",p=>{p.key==="Enter"&&this.handleConnect()}),document.getElementById("auth-tab-password").addEventListener("click",()=>{this.setAuthMode("password")}),document.getElementById("auth-tab-key").addEventListener("click",()=>{this.setAuthMode("key")})}setAuthMode(e){this.authMode=e;const p=document.getElementById("auth-tab-password"),x=document.getElementById("auth-tab-key"),m=document.getElementById("auth-password-section"),R=document.getElementById("auth-key-section");e==="password"?(p.style.background="#4af626",p.style.color="#0a0a0a",p.style.borderColor="#4af626",x.style.background="transparent",x.style.color="#bbccb0",x.style.borderColor="#3c4b36",m.style.display="",R.style.display="none"):(x.style.background="#4af626",x.style.color="#0a0a0a",x.style.borderColor="#4af626",p.style.background="transparent",p.style.color="#bbccb0",p.style.borderColor="#3c4b36",R.style.display="",m.style.display="none")}async loadSavedCredentials(){const e=localStorage.getItem("cloudssh_cred");if(!e)return;const p=await Wn(e);if(!p){localStorage.removeItem("cloudssh_cred");return}document.getElementById("host").value=p.host||"",document.getElementById("port").value=p.port||"22",document.getElementById("username").value=p.username||"",document.getElementById("password").value=p.password||"",document.getElementById("private-key").value=p.privateKey||"",document.getElementById("remember-me").checked=!0,p.authMethod==="key"?this.setAuthMode("key"):this.setAuthMode("password")}async handleConnect(){const e=document.getElementById("host").value,p=parseInt(document.getElementById("port").value||"22"),x=document.getElementById("username").value,m=document.getElementById("password").value,R=document.getElementById("private-key").value,b=document.getElementById("remember-me").checked;if(!e||!x){alert("请填写主机名和用户名");return}if(this.authMode==="password"&&!m){alert("请输入密码");return}if(this.authMode==="key"&&!R){alert("请粘贴私钥内容");return}if(b){const l=await Nn({host:e,port:p.toString(),username:x,password:m,privateKey:R,authMethod:this.authMode==="key"?"publickey":"password"});localStorage.setItem("cloudssh_cred",l)}else localStorage.removeItem("cloudssh_cred");const i=document.getElementById("auth-section"),a=document.getElementById("terminal-section");i.classList.add("hidden"),a.classList.remove("hidden"),a.classList.add("flex"),document.getElementById("term-host").textContent="Host: "+e,document.getElementById("term-user").textContent="User: "+x,document.getElementById("term-port").textContent="Port: "+p,this.terminal.mount();try{await this.terminal.connect({host:e,port:p,username:x,password:m,authMethod:this.authMode==="key"?"publickey":"password",privateKey:R})}catch{a.classList.add("hidden"),a.classList.remove("flex"),i.classList.remove("hidden"),document.getElementById("status-text").innerHTML='<span class="w-2 h-2 bg-[#353534] inline-block"></span> STATUS: OFFLINE'}}}const li=new Hn("terminal-container");new $n(li);var ns;(ns=document.getElementById("disconnect-btn"))==null||ns.addEventListener("click",()=>{li.disconnect();const r=document.getElementById("terminal-section");r.classList.add("hidden"),r.classList.remove("flex"),document.getElementById("auth-section").classList.remove("hidden"),document.getElementById("status-text").innerHTML='<span class="w-2 h-2 bg-[#353534] inline-block"></span> STATUS: OFFLINE'});var os;(os=document.getElementById("theme-selector"))==null||os.addEventListener("change",r=>{const e=r.target.value;li.setTheme(e)});const ts=document.getElementById("copyright-year");ts&&(ts.textContent=new Date().getFullYear().toString());
|
||||
</script>
|
||||
<style>*{margin:0;padding:0;box-sizing:border-box}body{font-family:JetBrains Mono,monospace;background-color:#0a0a0a;color:#4af626;min-height:100vh;overflow:hidden}.scanlines{position:fixed;top:0;left:0;width:100vw;height:100vh;background:linear-gradient(to bottom,#fff0,#fff0 50%,#0000001a 50%,#0000001a);background-size:100% 4px;pointer-events:none;z-index:50}.flicker{animation:flicker .15s infinite;pointer-events:none;position:fixed;top:0;left:0;width:100%;height:100%;background:#4af62605;z-index:49}@keyframes flicker{0%{opacity:.8}50%{opacity:1}to{opacity:.9}}.terminal-input{background:transparent;border:none;border-bottom:1px solid #3c4b36;color:#4af626;font-family:JetBrains Mono,monospace;padding:8px 0;width:100%;outline:none;transition:border-color .2s}.terminal-input:focus{border-bottom:1px solid #4af626;box-shadow:none}.terminal-input::placeholder{color:#3c4b36}.blinking-cursor:after{content:"█";color:#14d1ff;animation:blink 1s step-end infinite;margin-left:4px;font-size:.9em}@keyframes blink{0%,to{opacity:1}50%{opacity:0}}.cyber-box{background-color:#121212;border:1px solid #1f1f1f}.cyber-button{border:1px solid #4af626;color:#4af626;transition:all .2s ease;position:relative;overflow:hidden;cursor:pointer}.cyber-button:hover{background-color:#4af626;color:#0a0a0a}.cursor-blink{animation:blink 1s step-end infinite}.scanline{background:linear-gradient(to bottom,#fff0,#fff0 50%,#0000001a 50%,#0000001a);background-size:100% 4px}.custom-scrollbar::-webkit-scrollbar{width:8px}.custom-scrollbar::-webkit-scrollbar-track{background:#1c1b1b80}.custom-scrollbar::-webkit-scrollbar-thumb{background:#3c4b36cc;border-radius:4px}.custom-scrollbar::-webkit-scrollbar-thumb:hover{background:#86957dcc}/**
|
||||
* Copyright (c) 2014 The xterm.js authors. All rights reserved.
|
||||
@@ -160,7 +248,12 @@ WARNING: This link could potentially be dangerous\`)){const s=window.open();if(s
|
||||
<h1 class="text-2xl font-bold text-primary-container tracking-tighter cursor-pointer">CloudSSH</h1>
|
||||
</div>
|
||||
<div class="flex items-center gap-4 text-primary-container">
|
||||
<button id="disconnect-btn" class="hover:opacity-80 transition-opacity cursor-pointer p-1 flex items-center justify-center">
|
||||
<select id="theme-selector" class="bg-surface-variant text-on-surface-variant text-xs py-1 px-2 border-none outline-none cursor-pointer rounded">
|
||||
<option value="cyberpunk">Cyberpunk</option>
|
||||
<option value="glacier">Glacier</option>
|
||||
<option value="gruvbox">Gruvbox</option>
|
||||
</select>
|
||||
<button id="disconnect-btn" class="hover:opacity-80 transition-opacity cursor-pointer p-1 flex items-center justify-center" title="Disconnect">
|
||||
<span class="material-symbols-outlined" style="font-variation-settings: 'FILL' 0;">power_settings_new</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -3,11 +3,32 @@ import { HTML } from './html';
|
||||
|
||||
export { SSHSessionDO } from './durable-object';
|
||||
|
||||
// --- Rate Limiting (per-edge-node, best-effort) ---
|
||||
const rateLimitMap = new Map<string, { count: number; resetTime: number }>();
|
||||
const RATE_LIMIT_MAX = 10; // max requests per window
|
||||
const RATE_LIMIT_WINDOW = 60000; // 1 minute window
|
||||
|
||||
function isRateLimited(ip: string): boolean {
|
||||
const now = Date.now();
|
||||
const entry = rateLimitMap.get(ip);
|
||||
if (!entry || now > entry.resetTime) {
|
||||
rateLimitMap.set(ip, { count: 1, resetTime: now + RATE_LIMIT_WINDOW });
|
||||
return false;
|
||||
}
|
||||
entry.count++;
|
||||
return entry.count > RATE_LIMIT_MAX;
|
||||
}
|
||||
|
||||
export default {
|
||||
async fetch(request: Request, env: Env): Promise<Response> {
|
||||
const url = new URL(request.url);
|
||||
|
||||
if (url.pathname === '/api/ssh') {
|
||||
// Apply rate limiting
|
||||
const clientIP = request.headers.get('CF-Connecting-IP') || 'unknown';
|
||||
if (isRateLimited(clientIP)) {
|
||||
return new Response('Too Many Requests', { status: 429 });
|
||||
}
|
||||
return handleSSHConnection(request, env);
|
||||
}
|
||||
|
||||
|
||||
@@ -64,20 +64,41 @@ export class SSHSession {
|
||||
|
||||
private state: 'connecting' | 'version' | 'kex' | 'auth' | 'shell' | 'ready'
|
||||
= 'connecting';
|
||||
private hostKeyFingerprint: string = '';
|
||||
|
||||
private versionRawBuffer: Uint8Array = new Uint8Array(0);
|
||||
private negotiatedCipherC2S: string = 'aes256-gcm@openssh.com';
|
||||
private negotiatedCipherS2C: string = 'aes256-gcm@openssh.com';
|
||||
|
||||
constructor(ws: WebSocket, socket: any, config: SSHConnectionConfig) {
|
||||
public readonly roamId: string;
|
||||
|
||||
constructor(
|
||||
ws: WebSocket,
|
||||
socket: any,
|
||||
config: SSHConnectionConfig
|
||||
) {
|
||||
this.ws = ws;
|
||||
this.socket = socket;
|
||||
this.config = config;
|
||||
this.roamId = crypto.randomUUID();
|
||||
|
||||
this.transport = new SSHTransport();
|
||||
this.packetParser = new SSHPacketParser();
|
||||
this.channel = new SSHChannel();
|
||||
}
|
||||
|
||||
updateWebSocket(ws: WebSocket): void {
|
||||
this.ws = ws;
|
||||
try {
|
||||
this.ws.send(JSON.stringify({ type: 'roamId', roamId: this.roamId }));
|
||||
} catch {}
|
||||
if (this.state === 'ready') {
|
||||
try {
|
||||
this.ws.send(JSON.stringify({ type: 'status', message: '会话已恢复' }));
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
async startHandshake(): Promise<void> {
|
||||
this.sendStatus('正在交换版本信息...');
|
||||
this.state = 'version';
|
||||
@@ -335,6 +356,7 @@ export class SSHSession {
|
||||
|
||||
await this.enableEncryption();
|
||||
this.sendDebug('Encryption enabled');
|
||||
|
||||
this.state = 'auth';
|
||||
try {
|
||||
await this.sendServiceRequest();
|
||||
@@ -383,6 +405,12 @@ export class SSHSession {
|
||||
const hHex = Array.from(H).map(b => b.toString(16).padStart(2, '0')).join('');
|
||||
this.sendDebug(`Exchange hash H=${hHex}`);
|
||||
|
||||
// Compute host key fingerprint (SHA-256)
|
||||
const fpHash = new Uint8Array(await crypto.subtle.digest('SHA-256', hostKey));
|
||||
this.hostKeyFingerprint = 'SHA256:' + btoa(String.fromCharCode(...fpHash)).replace(/=+$/, '');
|
||||
this.sendStatus(`服务器指纹: ${this.hostKeyFingerprint}`);
|
||||
this.sendDebug(`Host key fingerprint: ${this.hostKeyFingerprint}`);
|
||||
|
||||
// Verify host key signature to confirm exchange hash is correct
|
||||
try {
|
||||
const sigVerified = await this.verifyHostKeySignature(hostKey, signature, H);
|
||||
@@ -570,10 +598,21 @@ export class SSHSession {
|
||||
}
|
||||
|
||||
private async authenticate(): Promise<void> {
|
||||
const authRequest = SSHAuth.buildPasswordAuthRequest(
|
||||
this.config.username,
|
||||
this.config.password
|
||||
);
|
||||
let authRequest: Uint8Array;
|
||||
|
||||
if (this.config.authMethod === 'publickey' && this.config.privateKey) {
|
||||
this.sendStatus('正在使用密钥认证...');
|
||||
authRequest = await SSHAuth.buildPublicKeyAuthRequest(
|
||||
this.config.username,
|
||||
this.config.privateKey,
|
||||
this.sessionID!
|
||||
);
|
||||
} else {
|
||||
authRequest = SSHAuth.buildPasswordAuthRequest(
|
||||
this.config.username,
|
||||
this.config.password
|
||||
);
|
||||
}
|
||||
|
||||
const packet = await SSHPacketBuilder.build(
|
||||
authRequest, 16,
|
||||
|
||||
Reference in new issue
Block a user