UNPKG

ssh-terminal

Version:

SSH Terminal component based on xterm.js for multiple frontend frameworks

1,436 lines 55.7 kB
var ee = Object.defineProperty;
var te = (m, e, t) => e in m ? ee(m, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : m[e] = t;
var D = (m, e, t) => (te(m, typeof e != "symbol" ? e + "" : e, t), t);
import { Terminal as re } from "xterm";
import { FitAddon as ne } from "xterm-addon-fit";
import { WebLinksAddon as oe } from "xterm-addon-web-links";
import { SearchAddon as ie } from "xterm-addon-search";
import { ref as se, onMounted as ae, onBeforeUnmount as ce, watch as N, nextTick as V, openBlock as le, createElementBlock as de, normalizeClass as K, createElementVNode as ue } from "vue";
class he {
  constructor() {
    this.tabId = this.getOrCreateTabId();
  }
  getOrCreateTabId() {
    const e = `tab-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
    return console.log("🆕 Created new Tab ID:", e), e;
  }
  getTabId() {
    return this.tabId;
  }
}
const ge = new he();
class pe {
  constructor() {
    this.keyPair = null, this.serverPublicKey = null, this.useWebCrypto = this._isSecureContext(), this.microRsa = null, console.log("🔧 EncryptionService constructor:", {
      protocol: window.location.protocol,
      hostname: window.location.hostname,
      isSecureContext: window.isSecureContext,
      hasWebCrypto: !!(window.crypto && window.crypto.subtle),
      useWebCrypto: this.useWebCrypto
    }), this.useWebCrypto ? console.log("✅ Will use Web Crypto API") : console.warn("🔓 HTTP environment detected - will use micro-rsa-dsa-dh fallback");
  }
  /**
   * Kiểm tra có phải secure context không (HTTPS hoặc localhost)
   */
  _isSecureContext() {
    if (typeof window > "u")
      return !1;
    const e = !!(window.crypto && window.crypto.subtle), t = window.isSecureContext || window.location.protocol === "https:", r = window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1" || window.location.hostname === "::1";
    if (console.log("🔧 _isSecureContext check:", {
      hasWebCrypto: e,
      isSecure: t,
      isLocalhost: r,
      protocol: window.location.protocol,
      hostname: window.location.hostname
    }), e && (t || r))
      try {
        return window.crypto.subtle.generateKey && window.crypto.subtle.encrypt ? (console.log("✅ Web Crypto API methods are available"), !0) : (console.warn("⚠️ Web Crypto API object exists but methods not available"), !1);
      } catch (n) {
        return console.warn("⚠️ Web Crypto API test failed:", n.message), !1;
      }
    return !t && !r ? (console.warn("🔧 HTTP environment detected - will use micro-rsa-dsa-dh fallback"), !1) : e && (t || r);
  }
  /**
   * Load node-forge khi cần thiết (chỉ cho HTTP)
   */
  async _loadNodeForge() {
    if (!this.nodeForge)
      try {
        const e = await import("./index-9234c122.mjs").then((t) => t.i);
        this.nodeForge = e.default || e, console.log("✅ node-forge loaded for HTTP environment");
      } catch (e) {
        console.error("❌ Failed to load node-forge from node_modules:", e), console.warn("⚠️ Using mock crypto implementation for testing"), this.nodeForge = {
          pki: {
            rsa: {
              generateKeyPair: () => ({
                publicKey: { encrypt: () => new Uint8Array(256) },
                privateKey: { decrypt: () => new Uint8Array([72, 101, 108, 108, 111]) }
                // "Hello"
              })
            }
          },
          md: {
            sha256: {
              create: () => ({ digest: () => ({ bytes: () => new Uint8Array(32) }) })
            }
          }
        }, console.log("✅ Mock crypto implementation loaded");
      }
    return this.nodeForge;
  }
  /**
   * Generate key pair với node-forge
   */
  async _generateNodeForgeKeyPair() {
    const e = await this._loadNodeForge();
    console.log("✅ node-forge loaded successfully");
    try {
      console.log("🔧 Generating RSA key pair with node-forge...");
      const t = e.pki.rsa.generateKeyPair({ bits: 2048 });
      return this.keyPair = {
        publicKey: {
          type: "public",
          algorithm: { name: "RSA-OAEP", hash: "SHA-256" },
          extractable: !0,
          usages: ["encrypt"],
          nodeForge: t.publicKey,
          // Convert to PEM for compatibility
          pem: e.pki.publicKeyToPem(t.publicKey)
        },
        privateKey: {
          type: "private",
          algorithm: { name: "RSA-OAEP", hash: "SHA-256" },
          extractable: !0,
          usages: ["decrypt"],
          nodeForge: t.privateKey,
          // Convert to PEM for compatibility
          pem: e.pki.privateKeyToPem(t.privateKey)
        }
      }, console.log("🔧 RSA key pair generated successfully with node-forge"), this.keyPair;
    } catch (t) {
      throw console.error("❌ Failed to generate key pair with node-forge:", t), new Error("Key generation failed: " + t.message);
    }
  }
  /**
   * Tạo cặp khóa RSA cho client
   */
  async generateKeyPair() {
    try {
      if (this.useWebCrypto) {
        console.log("🔧 Attempting Web Crypto API key generation...");
        try {
          this.keyPair = await window.crypto.subtle.generateKey(
            {
              name: "RSA-OAEP",
              modulusLength: 2048,
              publicExponent: new Uint8Array([1, 0, 1]),
              hash: "SHA-256"
            },
            !0,
            ["encrypt", "decrypt"]
          ), console.log("✅ Web Crypto API key generation successful");
        } catch (e) {
          return console.error("❌ Web Crypto API failed in this environment:", e), console.warn("🔄 Falling back to node-forge..."), this.useWebCrypto = !1, await this._generateNodeForgeKeyPair();
        }
      } else
        return await this._generateNodeForgeKeyPair();
      return this.keyPair;
    } catch (e) {
      throw console.error("❌ Lỗi tạo key pair:", e), new Error("Không thể tạo key pair");
    }
  }
  /**
   * Xuất public key dưới dạng PEM
   */
  async exportPublicKey(e = this.keyPair) {
    try {
      if (this.useWebCrypto) {
        const t = await window.crypto.subtle.exportKey(
          "spki",
          e.publicKey
        ), r = String.fromCharCode(...new Uint8Array(t));
        return `-----BEGIN PUBLIC KEY-----
${window.btoa(r)}
-----END PUBLIC KEY-----`;
      } else
        return console.warn("⚠️ Using mock public key export for HTTP testing"), `-----BEGIN PUBLIC KEY-----
${btoa("mock-public-key-data-for-testing")}
-----END PUBLIC KEY-----`;
    } catch (t) {
      throw console.error("❌ Lỗi export public key:", t), new Error("Không thể export public key");
    }
  }
  /**
   * Import public key từ server
   */
  async importServerPublicKey(e) {
    try {
      const t = e.replace(/\r\n/g, `
`).replace(/\r/g, `
`), r = "-----BEGIN PUBLIC KEY-----", n = "-----END PUBLIC KEY-----";
      if (!t.includes(r) || !t.includes(n))
        throw new Error("Invalid PEM format - missing headers");
      const o = t.indexOf(r) + r.length, s = t.indexOf(n);
      if (o === -1 || s === -1 || o >= s)
        throw new Error("Invalid PEM format - malformed headers");
      const d = t.substring(o, s).replace(/\s+/g, "");
      if (d.length === 0)
        throw new Error("Empty PEM content after cleaning");
      let i;
      try {
        i = window.atob(d);
      } catch {
        throw new Error("Invalid base64 content in PEM");
      }
      const h = new Uint8Array(i.length);
      for (let g = 0; g < i.length; g++)
        h[g] = i.charCodeAt(g);
      return this.useWebCrypto ? (this.serverPublicKey = await window.crypto.subtle.importKey(
        "spki",
        h.buffer,
        {
          name: "RSA-OAEP",
          hash: "SHA-256"
        },
        !0,
        ["encrypt"]
      ), console.log("🔧 Server public key imported successfully:", this.serverPublicKey)) : (console.warn("⚠️ Using node-forge server public key import for HTTP testing"), this.serverPublicKey = {
        type: "node-forge-server-key",
        pem: e
      }, console.log("✅ Server public key stored for node-forge")), this.serverPublicKey;
    } catch (t) {
      throw console.error("❌ Import server public key error:", t), new Error("Không thể import server public key: " + t.message);
    }
  }
  /**
   * Mã hóa dữ liệu bằng public key của server
   */
  async encryptForServer(e, t = this.serverPublicKey) {
    try {
      if (console.log("🔧 encryptForServer called with:", {
        dataLength: e?.length,
        hasPublicKey: !!t,
        useWebCrypto: this.useWebCrypto,
        publicKeyType: typeof t
      }), !t)
        throw new Error("Server public key chưa được import");
      if (this.useWebCrypto) {
        console.log("🔧 Using Web Crypto API for encryption");
        const n = new TextEncoder().encode(e);
        console.log("🔧 Encoded data length:", n.length), console.log("🔧 Public key object:", t);
        const o = await window.crypto.subtle.encrypt(
          { name: "RSA-OAEP" },
          t,
          n
        );
        console.log("🔧 Encryption successful, result length:", o.byteLength);
        const s = btoa(String.fromCharCode(...new Uint8Array(o)));
        return console.log("🔧 Base64 result length:", s.length), s;
      } else {
        console.warn("⚠️ Using node-forge RSA encryption for HTTP environment");
        try {
          const r = await this._loadNodeForge();
          if (!t || !t.pem)
            throw new Error("Server public key not available for node-forge");
          console.log("🔧 Attempting node-forge RSA encryption...");
          try {
            const n = r.pki.publicKeyFromPem(t.pem);
            console.log("✅ Successfully parsed RSA public key with node-forge"), console.log("🔧 Encrypting with node-forge RSA-OAEP...");
            const o = n.encrypt(e, "RSA-OAEP", {
              md: r.md.sha256.create(),
              mgf1: {
                md: r.md.sha256.create()
              }
            }), s = btoa(o);
            return console.log("✅ RSA-OAEP encryption successful with node-forge"), console.log(`🔧 Encrypted data length: ${s.length} chars`), s;
          } catch (n) {
            console.error("❌ Real encryption failed, falling back to mock:", n), console.warn("⚠️ Using mock encryption as fallback");
            const o = new Uint8Array(256);
            if (window.crypto && window.crypto.getRandomValues) {
              window.crypto.getRandomValues(o);
              const l = new TextEncoder().encode(e);
              for (let d = 0; d < l.length && d < o.length; d++)
                o[d] ^= l[d];
            } else {
              const l = new TextEncoder().encode(e);
              for (let d = 0; d < o.length; d++)
                o[d] = (l[d % l.length] + d + 42) % 256;
            }
            const s = btoa(String.fromCharCode(...o));
            return console.log("⚠️ Generated mock encryption result"), s;
          }
        } catch (r) {
          throw console.error("❌ node-forge encryption failed:", r), new Error("Không thể mã hóa với node-forge: " + r.message);
        }
      }
    } catch (r) {
      throw console.error("❌ Lỗi mã hóa dữ liệu:", r), console.error("❌ Error stack:", r.stack), new Error("Không thể mã hóa dữ liệu: " + r.message);
    }
  }
  /**
   * Giải mã dữ liệu bằng private key của client
   */
  async decryptFromServer(e) {
    try {
      if (!this.keyPair)
        throw new Error("Key pair chưa được tạo");
      if (this.useWebCrypto) {
        console.log("🔧 Using Web Crypto API for decryption");
        const t = atob(e), r = new Uint8Array(t.length);
        for (let s = 0; s < t.length; s++)
          r[s] = t.charCodeAt(s);
        const n = await window.crypto.subtle.decrypt(
          { name: "RSA-OAEP" },
          this.keyPair.privateKey,
          r
        );
        return new TextDecoder().decode(n);
      } else {
        console.log("🔧 Using node-forge for decryption"), await this._loadNodeForge();
        const t = this.keyPair.privateKey.nodeForge, r = atob(e);
        return t.decrypt(r, "RSA-OAEP", {
          md: forge.md.sha256.create(),
          mgf1: {
            md: forge.md.sha256.create()
          }
        });
      }
    } catch (t) {
      throw console.error("❌ Lỗi giải mã dữ liệu:", t), new Error("Không thể giải mã dữ liệu: " + t.message);
    }
  }
  /**
   * Tạo AES key cho session encryption
   */
  async generateAESKey() {
    try {
      return await window.crypto.subtle.generateKey(
        {
          name: "AES-GCM",
          length: 256
        },
        !0,
        ["encrypt", "decrypt"]
      );
    } catch (e) {
      throw console.error("❌ Lỗi tạo AES key:", e), new Error("Không thể tạo AES key");
    }
  }
  /**
   * Mã hóa dữ liệu bằng AES
   */
  async encryptAES(e, t) {
    try {
      const n = new TextEncoder().encode(e), o = window.crypto.getRandomValues(new Uint8Array(12)), s = await window.crypto.subtle.encrypt(
        {
          name: "AES-GCM",
          iv: o
        },
        t,
        n
      ), l = new Uint8Array(o.length + s.byteLength);
      return l.set(o), l.set(new Uint8Array(s), o.length), btoa(String.fromCharCode(...l));
    } catch (r) {
      throw console.error("❌ Lỗi mã hóa AES:", r), new Error("Không thể mã hóa AES");
    }
  }
  /**
   * Giải mã dữ liệu AES
   */
  async decryptAES(e, t) {
    try {
      const r = atob(e), n = new Uint8Array(r.length);
      for (let i = 0; i < r.length; i++)
        n[i] = r.charCodeAt(i);
      const o = n.slice(0, 12), s = n.slice(12), l = await window.crypto.subtle.decrypt(
        {
          name: "AES-GCM",
          iv: o
        },
        t,
        s
      );
      return new TextDecoder().decode(l);
    } catch (r) {
      throw console.error("❌ Lỗi giải mã AES:", r), new Error("Không thể giải mã AES");
    }
  }
  /**
   * Tạo hash SHA-256
   */
  async hash(e) {
    try {
      if (this.useWebCrypto) {
        const r = new TextEncoder().encode(e), n = await window.crypto.subtle.digest("SHA-256", r);
        return Array.from(new Uint8Array(n)).map((s) => s.toString(16).padStart(2, "0")).join("");
      } else if (console.warn("⚠️ Using mock hash for HTTP testing"), window.crypto && window.crypto.getRandomValues) {
        let t = 0;
        for (let n = 0; n < e.length; n++) {
          const o = e.charCodeAt(n);
          t = (t << 5) - t + o, t = t & t;
        }
        return Math.abs(t).toString(16).padStart(8, "0").repeat(8).substring(0, 64);
      } else {
        let t = 0;
        for (let r = 0; r < e.length; r++)
          t = (t << 5) - t + e.charCodeAt(r), t = t & t;
        return Math.abs(t).toString(16).padStart(8, "0").repeat(8).substring(0, 64);
      }
    } catch (t) {
      throw console.error("❌ Lỗi tạo hash:", t), new Error("Không thể tạo hash");
    }
  }
  /**
   * Tạo random string
   */
  generateRandomString(e = 32) {
    if (this.useWebCrypto) {
      const t = new Uint8Array(e);
      return window.crypto.getRandomValues(t), Array.from(t, (r) => r.toString(16).padStart(2, "0")).join("");
    } else {
      console.warn("⚠️ Using Math.random for random generation in HTTP environment");
      let t = "";
      const r = "0123456789abcdef";
      for (let n = 0; n < e * 2; n++)
        t += r.charAt(Math.floor(Math.random() * r.length));
      return t;
    }
  }
}
class y {
  /**
   * Xác thực SSH configuration
   */
  static validateSSHConfig(e) {
    const t = [];
    return e.host ? typeof e.host != "string" ? t.push("Host phải là string") : e.host.length > 255 ? t.push("Host quá dài (tối đa 255 ký tự)") : this.isValidHost(e.host) || t.push("Host không hợp lệ") : t.push("Host là bắt buộc"), e.username ? typeof e.username != "string" ? t.push("Username phải là string") : e.username.length > 32 ? t.push("Username quá dài (tối đa 32 ký tự)") : this.isValidUsername(e.username) || t.push("Username chứa ký tự không hợp lệ") : t.push("Username là bắt buộc"), e.password ? typeof e.password != "string" ? t.push("Password phải là string") : e.password.length < 1 ? t.push("Password không được để trống") : e.password.length > 128 && t.push("Password quá dài (tối đa 128 ký tự)") : t.push("Password là bắt buộc"), e.port !== void 0 && (Number.isInteger(e.port) ? (e.port < 1 || e.port > 65535) && t.push("Port phải trong khoảng 1-65535") : t.push("Port phải là số nguyên")), e.wsUrl && !this.isValidWebSocketURL(e.wsUrl) && t.push("WebSocket URL không hợp lệ"), {
      isValid: t.length === 0,
      errors: t
    };
  }
  /**
   * Kiểm tra host hợp lệ (IP hoặc domain)
   */
  static isValidHost(e) {
    return /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(e) || /^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/.test(e) ? !0 : /^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)*[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/.test(e);
  }
  /**
   * Kiểm tra username hợp lệ
   */
  static isValidUsername(e) {
    return /^[a-zA-Z0-9_-]+$/.test(e);
  }
  /**
   * Kiểm tra WebSocket URL hợp lệ (khuyến nghị wss:// cho bảo mật)
   */
  static isValidWebSocketURL(e) {
    try {
      const t = new URL(e);
      return t.protocol === "ws:" || t.protocol === "wss:";
    } catch {
      return !1;
    }
  }
  /**
   * Xác thực encryption readiness
   */
  static validateEncryptionReadiness(e, t) {
    const r = [];
    return e || r.push("Encryption service not initialized"), t || r.push("Server public key not available"), {
      isValid: r.length === 0,
      errors: r
    };
  }
  /**
   * Xác thực computing configuration
   */
  static validateComputingConfig(e) {
    const t = [];
    return e.computingId ? typeof e.computingId != "string" ? t.push("Computing ID must be a string") : /^[0-9.]+$/.test(e.computingId) ? e.computingId.length > 50 && t.push("Computing ID must not exceed 50 characters") : t.push("Computing ID must contain only numbers and dots") : t.push("Computing ID is required"), e.userToken ? typeof e.userToken != "string" ? t.push("User token must be a string") : e.userToken.length > 8192 && t.push("User token is too large") : t.push("User token is required"), e.wsUrl && !this.isValidWebSocketURL(e.wsUrl) && t.push("Invalid WebSocket URL"), {
      isValid: t.length === 0,
      errors: t
    };
  }
  /**
   * Xác thực rằng connection chỉ sử dụng encrypted protocols
   */
  static validateSecureConnection(e) {
    const t = [], r = [];
    if (!e)
      return t.push("WebSocket URL is required"), { isValid: !1, errors: t, warnings: r };
    try {
      const n = new URL(e);
      n.protocol === "ws:" ? r.push("Warning: Using unencrypted WebSocket (ws://). Consider using wss:// for better security.") : n.protocol !== "wss:" && t.push("Invalid WebSocket protocol. Only ws:// and wss:// are supported.");
    } catch {
      t.push("Invalid WebSocket URL format");
    }
    return {
      isValid: t.length === 0,
      errors: t,
      warnings: r
    };
  }
  /**
   * Làm sạch string input
   */
  static sanitizeString(e, t = 255) {
    return typeof e != "string" ? "" : e.trim().slice(0, t).replace(/[\x00-\x1F\x7F]/g, "");
  }
  /**
   * Xác thực terminal options
   */
  static validateTerminalOptions(e) {
    const t = [];
    if (typeof e != "object" || e === null)
      return { isValid: !1, errors: ["Options phải là object"] };
    if (e.fontSize !== void 0 && (!Number.isInteger(e.fontSize) || e.fontSize < 8 || e.fontSize > 72) && t.push("fontSize phải là số nguyên từ 8-72"), e.scrollback !== void 0 && (!Number.isInteger(e.scrollback) || e.scrollback < 0 || e.scrollback > 1e4) && t.push("scrollback phải là số nguyên từ 0-10000"), e.theme !== void 0)
      if (typeof e.theme != "object")
        t.push("theme phải là object");
      else {
        const r = ["background", "foreground", "cursor", "cursorAccent", "selection"];
        for (const [n, o] of Object.entries(e.theme))
          r.includes(n) && typeof o == "string" && (this.isValidColor(o) || t.push(`theme.${n} không phải màu hợp lệ`));
      }
    return e.reconnection !== void 0 && (typeof e.reconnection != "object" ? t.push("reconnection phải là object") : (e.reconnection.maxAttempts !== void 0 && (!Number.isInteger(e.reconnection.maxAttempts) || e.reconnection.maxAttempts < 0 || e.reconnection.maxAttempts > 20) && t.push("reconnection.maxAttempts phải là số nguyên từ 0-20"), e.reconnection.heartbeatInterval !== void 0 && (!Number.isInteger(e.reconnection.heartbeatInterval) || e.reconnection.heartbeatInterval < 1e3 || e.reconnection.heartbeatInterval > 3e5) && t.push("reconnection.heartbeatInterval phải là số nguyên từ 1000-300000ms"))), {
      isValid: t.length === 0,
      errors: t
    };
  }
  /**
   * Kiểm tra màu hợp lệ (hex, rgb, rgba)
   */
  static isValidColor(e) {
    return /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(e) ? !0 : /^rgba?\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*(?:,\s*[\d.]+\s*)?\)$/.test(e);
  }
  /**
   * Xác thực JWT token format
   */
  static isValidJWTFormat(e) {
    if (typeof e != "string")
      return !1;
    const t = e.split(".");
    return t.length === 3 && t.every((r) => r.length > 0);
  }
  /**
   * Xác thực message size
   */
  static validateMessageSize(e, t = 1e4) {
    if (typeof e == "string")
      return e.length <= t;
    if (e instanceof ArrayBuffer)
      return e.byteLength <= t;
    try {
      return JSON.stringify(e).length <= t;
    } catch {
      return !1;
    }
  }
  /**
   * Làm sạch object để logging an toàn
   */
  static sanitizeForLogging(e) {
    if (typeof e != "object" || e === null)
      return e;
    const t = ["password", "token", "key", "secret", "auth", "credential"], r = {};
    for (const [n, o] of Object.entries(e)) {
      const s = n.toLowerCase();
      t.some((l) => s.includes(l)) ? r[n] = "***HIDDEN***" : typeof o == "object" && o !== null ? r[n] = this.sanitizeForLogging(o) : r[n] = o;
    }
    return r;
  }
}
const L = class L {
  constructor() {
    this.isProduction = typeof window < "u" && window.location.hostname !== "localhost" && window.location.hostname !== "127.0.0.1", this.logLevel = this.isProduction ? "error" : "debug", this.maxLogLength = 1e3;
  }
  /**
   * Kiểm tra có nên log không
   */
  shouldLog(e) {
    return L.LEVELS[e] >= L.LEVELS[this.logLevel];
  }
  /**
   * Làm sạch dữ liệu trước khi log
   */
  sanitizeData(e) {
    if (e == null)
      return e;
    const t = y.sanitizeForLogging(e), r = JSON.stringify(t);
    return r.length > this.maxLogLength ? {
      ...t,
      _truncated: !0,
      _originalLength: r.length
    } : t;
  }
  /**
   * Format log message
   */
  formatMessage(e, t, r = null) {
    const o = `[${(/* @__PURE__ */ new Date()).toISOString()}] [${e.toUpperCase()}] [CLIENT]`;
    if (r) {
      const s = this.sanitizeData(r);
      return `${o} ${t} ${JSON.stringify(s)}`;
    }
    return `${o} ${t}`;
  }
  /**
   * Debug log
   */
  debug(e, t = null) {
    this.shouldLog("debug") && console.debug(this.formatMessage("debug", e, t));
  }
  /**
   * Info log
   */
  info(e, t = null) {
    this.shouldLog("info") && console.info(this.formatMessage("info", e, t));
  }
  /**
   * Warning log
   */
  warn(e, t = null) {
    this.shouldLog("warn") && console.warn(this.formatMessage("warn", e, t));
  }
  /**
   * Error log
   */
  error(e, t = null) {
    this.shouldLog("error") && console.error(this.formatMessage("error", e, t));
  }
  /**
   * Log connection events
   */
  logConnection(e, t = {}) {
    const r = this.sanitizeData(t);
    this.info(`Connection ${e}`, r);
  }
  /**
   * Log authentication events
   */
  logAuth(e, t = {}) {
    const r = this.sanitizeData(t);
    this.info(`Auth ${e}`, r);
  }
  /**
   * Log security events
   */
  logSecurity(e, t = {}) {
    const r = this.sanitizeData(t);
    this.warn(`Security ${e}`, r);
  }
  /**
   * Log performance metrics
   */
  logPerformance(e, t, r = "ms") {
    this.debug(`Performance ${e}`, { value: t, unit: r });
  }
  /**
   * Log với custom level
   */
  log(e, t, r = null) {
    this.shouldLog(e) && console[e](this.formatMessage(e, t, r));
  }
  /**
   * Tạo logger instance với context
   */
  createContextLogger(e) {
    return {
      debug: (t, r) => this.debug(`[${e}] ${t}`, r),
      info: (t, r) => this.info(`[${e}] ${t}`, r),
      warn: (t, r) => this.warn(`[${e}] ${t}`, r),
      error: (t, r) => this.error(`[${e}] ${t}`, r),
      logConnection: (t, r) => this.logConnection(`[${e}] ${t}`, r),
      logAuth: (t, r) => this.logAuth(`[${e}] ${t}`, r),
      logSecurity: (t, r) => this.logSecurity(`[${e}] ${t}`, r),
      logPerformance: (t, r, n) => this.logPerformance(`[${e}] ${t}`, r, n)
    };
  }
  /**
   * Alias for backward compatibility
   */
  createContext(e) {
    return this.createContextLogger(e);
  }
  /**
   * Set log level
   */
  setLogLevel(e) {
    L.LEVELS.hasOwnProperty(e) && (this.logLevel = e);
  }
  /**
   * Get current log level
   */
  getLogLevel() {
    return this.logLevel;
  }
  /**
   * Enable/disable production mode
   */
  setProductionMode(e) {
    this.isProduction = e, this.logLevel = e ? "error" : "debug";
  }
};
/**
 * Các mức độ log
 */
D(L, "LEVELS", {
  debug: 0,
  info: 1,
  warn: 2,
  error: 3
});
let M = L;
const T = new M();
class me {
  constructor() {
    this.connections = /* @__PURE__ */ new Map(), this.contextLogger = T.createContextLogger("WebSocketPool");
  }
  /**
   * Get hoặc tạo mới WebSocket connection
   * @param {string} wsUrl - WebSocket URL
   * @param {string} connectionKey - Unique key (tabId:computingId)
   * @param {object} handlers - {onOpen, onMessage, onError, onClose}
   */
  async getConnection(e, t, r) {
    this.contextLogger.info("Getting WebSocket connection", {
      wsUrl: e,
      connectionKey: t
    }), this.connections.has(e) || this.connections.set(e, /* @__PURE__ */ new Map());
    const n = this.connections.get(e);
    if (n.has(t)) {
      const o = n.get(t), s = o.socket;
      if (s.readyState === WebSocket.OPEN || s.readyState === WebSocket.CONNECTING)
        return this.contextLogger.info("Reusing existing WebSocket", { connectionKey: t }), o.handlers = r, s;
      this.contextLogger.info("Existing WebSocket is dead, creating new one", { connectionKey: t }), n.delete(t);
    }
    return this.createNewConnection(e, t, r);
  }
  /**
   * Tạo WebSocket connection mới
   */
  createNewConnection(e, t, r) {
    const { onMessage: n, onError: o, onClose: s, onOpen: l } = r;
    this.contextLogger.info("Creating new WebSocket connection", { wsUrl: e, connectionKey: t }), console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"), console.log("🔌 Creating WebSocket");
    const d = new WebSocket(e), i = this.connections.get(e);
    return i.set(t, { socket: d, handlers: r }), d.onopen = () => {
      if (console.log(`✅ [${t}] WebSocket OPENED`), this.contextLogger.info("WebSocket opened", { wsUrl: e, connectionKey: t }), l)
        try {
          l();
        } catch (h) {
          this.contextLogger.error("Error in onOpen handler", { error: h.message });
        }
    }, d.onmessage = (h) => {
      const g = i.get(t);
      if (!g)
        return;
      const w = g.handlers;
      try {
        w.onMessage && w.onMessage(h);
      } catch (u) {
        this.contextLogger.error("Error in onMessage handler", {
          connectionKey: t,
          error: u.message
        });
      }
    }, d.onerror = (h) => {
      console.error(`❌ [${t}] WebSocket ERROR:`, h), this.contextLogger.error("WebSocket error", { wsUrl: e, connectionKey: t, error: h });
      const g = i.get(t);
      if (g && g.handlers.onError)
        try {
          g.handlers.onError(h);
        } catch (w) {
          this.contextLogger.error("Error in onError handler", { error: w.message });
        }
    }, d.onclose = (h) => {
      console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"), console.log(`🚪 [${t}] WebSocket CLOSED`), console.log("Code:", h.code), console.log("Reason:", h.reason || "(empty)"), console.log("Was Clean:", h.wasClean), console.log("Timestamp:", (/* @__PURE__ */ new Date()).toISOString()), console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"), this.contextLogger.info("WebSocket closed", {
        wsUrl: e,
        connectionKey: t,
        code: h.code,
        reason: h.reason,
        wasClean: h.wasClean
      });
      const g = i.get(t), w = g ? g.handlers : null;
      if (i.delete(t), console.log(`📊 After close - Remaining connections: ${i.size}`), i.size === 0 && (this.connections.delete(e), this.contextLogger.info("All connections closed for URL", { wsUrl: e })), w && w.onClose)
        try {
          w.onClose(h);
        } catch (u) {
          this.contextLogger.error("Error in onClose handler", { error: u.message });
        }
    }, d;
  }
  /**
   * Xóa subscriber và đóng connection
   */
  removeSubscriber(e, t) {
    this.contextLogger.info("Removing subscriber", { wsUrl: e, connectionKey: t });
    const r = this.connections.get(e);
    if (!r) {
      this.contextLogger.warn("No connections found for URL", { wsUrl: e });
      return;
    }
    const n = r.get(t);
    if (!n) {
      this.contextLogger.warn("Connection not found", { connectionKey: t });
      return;
    }
    const o = n.socket;
    o && o.readyState !== WebSocket.CLOSED && o.readyState !== WebSocket.CLOSING && o.close(1e3, "Subscriber removed"), r.delete(t), r.size === 0 && (this.connections.delete(e), this.contextLogger.info("All connections removed for URL", { wsUrl: e }));
  }
  /**
   * Gửi message qua WebSocket
   * @param {string} wsUrl 
   * @param {string} connectionKey 
   * @param {object|string} message 
   */
  send(e, t, r) {
    const n = this.connections.get(e);
    if (!n)
      throw new Error(`No connections found for ${e}`);
    const o = n.get(t);
    if (!o)
      throw new Error(`No connection found for key: ${t}`);
    const s = o.socket;
    if (s.readyState !== WebSocket.OPEN)
      throw new Error(`Connection not ready (state: ${s.readyState})`);
    const l = typeof r == "string" ? r : JSON.stringify(r);
    s.send(l);
  }
  /**
   * Kiểm tra connection có đang mở không
   */
  isConnected(e, t) {
    const r = this.connections.get(e);
    if (!r)
      return !1;
    const n = r.get(t);
    return n ? n.socket.readyState === WebSocket.OPEN : !1;
  }
  /**
   * Đếm số connection cho một URL
   */
  getConnectionCount(e) {
    const t = this.connections.get(e);
    return t ? t.size : 0;
  }
  /**
   * Đóng tất cả connections
   */
  dispose() {
    this.contextLogger.info("Disposing all connections"), this.connections.forEach((e, t) => {
      e.forEach((r, n) => {
        const o = r.socket;
        o.readyState !== WebSocket.CLOSED && o.readyState !== WebSocket.CLOSING && o.close(1e3, "Pool disposed");
      });
    }), this.connections.clear();
  }
  /**
   * Debug: Lấy thông tin tất cả connections
   */
  getDebugInfo() {
    const e = {};
    return this.connections.forEach((t, r) => {
      e[r] = {
        count: t.size,
        connections: []
      }, t.forEach((n, o) => {
        e[r].connections.push({
          key: o,
          state: n.socket.readyState,
          stateText: ["CONNECTING", "OPEN", "CLOSING", "CLOSED"][n.socket.readyState]
        });
      });
    }), e;
  }
}
const S = new me(), j = {
  fontSize: 14,
  fontFamily: 'Menlo, Monaco, "Courier New", monospace',
  theme: {
    background: "#000000",
    foreground: "#ffffff",
    cursor: "#ffffff",
    cursorAccent: "#000000",
    selection: "rgba(255, 255, 255, 0.3)"
  },
  cursorBlink: !0,
  cursorStyle: "block",
  scrollback: 1e3,
  allowTransparency: !1,
  tabStopWidth: 8,
  screenReaderMode: !1,
  convertEol: !0,
  disableStdin: !1,
  reconnection: {
    enabled: !0,
    maxAttempts: 5,
    heartbeatInterval: 3e4
  }
};
function F(m, e, t = {}) {
  const r = T.createContextLogger("Terminal");
  if (!m)
    throw r.error("Container element is required"), new Error("Container element is required");
  let n;
  if (e.computingId && e.userToken) {
    if (n = y.validateComputingConfig(e), !n.isValid)
      throw r.error("Invalid computing configuration", { errors: n.errors }), new Error(`Computing configuration invalid: ${n.errors.join(", ")}`);
  } else if (n = y.validateSSHConfig(e), !n.isValid)
    throw r.error("Invalid SSH configuration", { errors: n.errors }), new Error(`SSH configuration invalid: ${n.errors.join(", ")}`);
  const s = y.validateTerminalOptions(t);
  if (!s.isValid)
    throw r.error("Invalid terminal options", { errors: s.errors }), new Error(`Terminal options invalid: ${s.errors.join(", ")}`);
  const l = {
    ...j,
    ...t.theme ? { theme: { ...j.theme, ...t.theme } } : {},
    ...t
  }, d = t.showConnectionLogs === !0, i = new re(l), h = new ne(), g = new oe(), w = new ie();
  i.loadAddon(h), i.loadAddon(g), i.loadAddon(w), i.open(m), setTimeout(() => {
    h.fit();
  }, 0);
  const u = e.wsUrl || "wss://ssh-proxy.dev.longvan.vn";
  if (!y.isValidWebSocketURL(u))
    throw r.error("Invalid WebSocket URL", { wsUrl: u }), new Error("Invalid WebSocket URL");
  const v = y.validateSecureConnection(u);
  if (!v.isValid)
    throw r.error("Insecure WebSocket connection", { errors: v.errors }), new Error(`Insecure connection: ${v.errors.join(", ")}`);
  v.warnings && v.warnings.length > 0 && v.warnings.forEach((c) => {
    i.writeln(`⚠️ ${c}`);
  });
  let E = !1, b = null, I = null, C = 0;
  const P = l.reconnection?.maxAttempts || 5, H = l.reconnection?.enabled !== !1;
  let W = null, $ = !1, z = !1;
  const x = e.computingId || `direct-${Date.now()}`, B = e.tabClientId || `direct-${Date.now()}`, A = ge.getTabId(), f = `${A}:${B}`;
  r.info("Creating terminal", {
    tabId: A,
    computingId: x,
    connectionKey: f
  }), console.log("Creating terminal", {
    tabId: A,
    computingId: x,
    connectionKey: f
  });
  const G = async () => {
    try {
      return b = new pe(), await b.generateKeyPair(), !0;
    } catch (c) {
      return r.error("Failed to initialize encryption", { error: c.message }), !1;
    }
  }, q = async () => {
    try {
      const c = y.validateEncryptionReadiness(b, I);
      if (!c.isValid)
        throw new Error(`Encryption not ready: ${c.errors.join(", ")}`);
      let a;
      console.log("typeConnect in createTerminal", e.typeConnect);
      const k = e.typeConnect || "ssh";
      if (console.log("currentTypeConnect terminal", k), e.computingId && e.userToken) {
        const p = await b.encryptForServer(e.userToken, I);
        a = {
          type: "encrypted-auth",
          computingId: e.computingId,
          encryptedToken: p,
          clientPublicKey: await b.exportPublicKey(),
          // 🔥 Gửi kèm typeConnect để Proxy biết đường phân luồng
          typeConnect: k
        }, d && i.writeln("🔐 Sent encrypted computing credentials...");
      } else {
        const p = await b.encryptForServer(e.password, I);
        a = {
          type: "encrypted-auth",
          host: e.host,
          username: e.username,
          encryptedPassword: p,
          clientPublicKey: await b.exportPublicKey(),
          typeConnect: k
        }, d && i.writeln("🔐 Sent encrypted SSH credentials...");
      }
      S.send(u, f, a);
    } catch (c) {
      throw r.error("Failed to send encrypted auth", { error: c.message }), i.writeln(`\r
❌ Encryption failed: ${c.message}\r
`), i.writeln(`🔒 This client only supports secure encrypted authentication.\r
`), S.removeSubscriber(u, f), c;
    }
  }, Y = () => {
    if ($)
      return;
    $ = !0, C++;
    const c = Math.min(1e3 * Math.pow(2, C - 1), 3e4);
    i.writeln(
      `\r
Attempting to reconnect (${C}/${P}) in ${c / 1e3}s...\r
`
    ), W = setTimeout(() => {
      O();
    }, c);
  }, Z = async () => {
    E = !0, C = 0, $ = !1, d && i.writeln(
      C > 0 ? `\r
✅ Reconnected to SSH relay server\r
` : "✅ WebSocket connected to " + u
    ), d && i.writeln("🔑 Waiting for server public key...");
  }, J = async (c) => {
    try {
      if (!c || !c.data)
        return;
      let a;
      if (typeof c.data == "string")
        try {
          a = JSON.parse(c.data);
        } catch {
          i.write(c.data);
          return;
        }
      else
        a = c.data;
      if (console.log(`📩 Message from Server [${a.type}]:`, a), a.type === "rdp-redirect") {
        r.info("🚀 Received RDP redirect command", { url: a.url }), typeof i._onRdpRedirect == "function" && i._onRdpRedirect(a);
        return;
      }
      if (!y.validateMessageSize(c.data, 5e4)) {
        r.logSecurity("message_too_large", { size: c.data.length });
        return;
      }
      if (a.type === "welcome") {
        try {
          if (a.publicKey)
            I = await b.importServerPublicKey(a.publicKey), d && i.writeln("🔑 Server public key received"), await q();
          else {
            i.writeln(`\r
❌ Server does not support encrypted authentication\r
`), i.writeln(`🔒 This client requires RSA encryption for security\r
`), S.removeSubscriber(u, f);
            return;
          }
        } catch (p) {
          r.error("Failed to process welcome message", { error: p.message }), i.writeln(`\r
❌ Failed to process server welcome: ${p.message}\r
`), S.removeSubscriber(u, f);
          return;
        }
        return;
      }
      if (!(!a.computingId || a.computingId === x))
        return;
      if (a.type === "data")
        try {
          const p = window.atob(a.data), _ = new Uint8Array(p.length);
          for (let R = 0; R < p.length; R++)
            _[R] = p.charCodeAt(R);
          const U = new TextDecoder().decode(_);
          i.write(U);
        } catch (p) {
          r.error("Failed to decode terminal data", { error: p.message });
          try {
            i.write(window.atob(a.data));
          } catch {
            i.write(a.data);
          }
        }
      else
        a.type === "error" ? (r.error("Server error received", {
          code: a.code,
          message: a.message
        }), i.writeln(`\r
❌ Error: ${a.message}\r
`)) : a.type === "status" && a.status === "authenticated" ? (z = !0, console.log("Xác thực thành công"), d && i.writeln(`\r
✅ Connected to ${e.host} as ${e.username}\r
`)) : a.type === "status" && a.status === "closed" ? d && i.writeln(`\r
🔌 SSH connection closed\r
`) : a.type === "auth-success" ? (z = !0, console.log("Xác thực thành công"), d && i.writeln(`\r
✅ Authentication successful!\r
`), a.host && a.username && (e.host = a.host, e.username = a.username, e.port = a.port || 22)) : a.type === "session-created" ? z = !0 : a.type === "auth-error" && (r.logAuth("auth_failed", { message: a.message }), i.writeln(`\r
❌ Authentication failed: ${a.message}\r
`));
    } catch {
      c && c.data && i.write(c.data);
    }
  }, X = (c) => {
    E = !1, i.writeln(`\r
WebSocket error: ${c?.message || "Unknown error"}\r
`), i.writeln(`\r
Please check if the SSH proxy server is running at ${u}\r
`);
  }, Q = (c) => {
    if (!c)
      return;
    if (E = !1, c.code === 1e3) {
      i.writeln(`\r
Connection to SSH relay server closed normally\r
`);
      return;
    }
    const k = {
      1006: "Connection lost (network issue or server restart)",
      1001: "Server going away",
      1002: "Protocol error",
      1003: "Unsupported data type",
      1011: "Server error",
      1012: "Server restart",
      1013: "Server overloaded"
    }[c.code] || `Unknown error (Code: ${c.code})`;
    i.writeln(`\r
Connection to SSH relay server closed: ${k}\r
`), c.reason && i.writeln(`Reason: ${c.reason}\r
`), H && !$ && C < P ? Y() : H && C >= P && i.writeln(`\r
Max reconnection attempts reached. Please refresh the page.\r
`);
  }, O = async () => {
    if (d && (i.writeln(`🔌 Connecting to ${u}...`), i.writeln(`📱 Tab ID: ${A}`), i.writeln(`💻 Computing ID: ${x}`)), !await G()) {
      i.writeln(`\r
❌ Failed to initialize encryption\r
`);
      return;
    }
    try {
      const a = {
        onOpen: Z,
        onMessage: J,
        onError: X,
        onClose: Q
      };
      await S.getConnection(u, f, a), r.info("Connected to WebSocket pool", {
        wsUrl: u,
        tabId: A,
        computingId: x,
        connectionKey: f,
        totalConnections: S.getConnectionCount(u)
      });
    } catch (a) {
      r.error("Failed to connect", { error: a.message, wsUrl: u }), i.writeln(`\r
❌ Failed to connect: ${a.message}\r
`);
    }
  };
  return i.onData((c) => {
    if (E && S.isConnected(u, f)) {
      if (!y.validateMessageSize(c, 1e4)) {
        r.warn("Terminal input too large", { size: c.length });
        return;
      }
      let a;
      try {
        const p = new TextEncoder().encode(c), _ = String.fromCharCode(...p);
        a = window.btoa(_);
      } catch (p) {
        r.error("Failed to encode terminal data", { error: p.message });
        try {
          a = window.btoa(c);
        } catch {
          const U = c.replace(/[^\x00-\x7F]/g, "?");
          a = window.btoa(U);
        }
      }
      const k = {
        type: "data",
        data: a,
        computingId: x
        // 🔥 FIX: Dùng computingId thay vì sshConfig.computingId
      };
      try {
        S.send(u, f, k);
      } catch (p) {
        r.error("Failed to send terminal data", { error: p.message });
      }
    }
  }), setTimeout(() => {
    O();
  }, 100), h.fit(), {
    terminal: i,
    fitAddon: h,
    searchAddon: w,
    reconnect: async () => {
      await O();
    },
    resize: () => {
      if (!(!i || !h))
        if (h.fit(), S.isConnected(u, f) && z) {
          const c = {
            type: "resize",
            cols: i.cols,
            rows: i.rows
          };
          try {
            S.send(u, f, c), console.log(`📏 Server PTY Resized to: ${i.cols}x${i.rows}`);
          } catch {
            console.warn("⚠️ Failed to sync resize to server");
          }
        } else
          console.log("⏳ Resize deferred: Waiting for authentication...");
    },
    dispose: () => {
      console.log(`🗑️  Disposing terminal [${f}]`), W && clearTimeout(W), S.removeSubscriber(u, f), i.dispose(), r.info("Terminal disposed", {
        wsUrl: u,
        tabId: A,
        computingId: x,
        connectionKey: f,
        remainingConnections: S.getConnectionCount(u)
      });
    },
    getConnectionInfo: () => ({
      wsUrl: u,
      tabId: A,
      computingId: x,
      connectionKey: f,
      connected: S.isConnected(u, f),
      totalConnections: S.getConnectionCount(u)
    })
  };
}
const fe = (m, e) => {
  const t = m.__vccOpts || m;
  for (const [r, n] of e)
    t[r] = n;
  return t;
}, ye = {
  __name: "SSHTerminal",
  props: {
    computingId: { type: String, required: !0 },
    userToken: { type: String, required: !0 },
    websocketUrl: { type: String, default: "wss://ssh-proxy.dev.longvan.vn" },
    typeConnect: { type: Object },
    options: { type: Object, default: () => ({}) },
    isActive: { type: Boolean, default: !0 },
    resizeSignal: { type: Number, default: 0 },
    tabClientId: { type: String, required: !0 }
  },
  emits: ["ready", "error", "rdp-redirect"],
  setup(m, { expose: e, emit: t }) {
    const r = m, n = t, o = se(null);
    let s = null, l = null, d = null;
    ae(() => {
      h();
    }), ce(() => {
      s && s.dispose(), window.removeEventListener("resize", i), l && l.disconnect(), d && clearTimeout(d);
    }), N(
      () => r.resizeSignal,
      () => {
        V(() => {
          setTimeout(i, 150);
        });
      }
    ), N(
      () => r.isActive,
      (w) => {
        w && s && V(() => {
          s.terminal?.focus();
        });
      }
    );
    function i() {
      d && clearTimeout(d), d = setTimeout(() => {
        g();
      }, 250);
    }
    function h() {
      const w = o.value, u = T.createContextLogger("Vue3Terminal"), v = {
        computingId: r.computingId,
        userToken: r.userToken,
        wsUrl: r.websocketUrl,
        typeConnect: r.typeConnect,
        tabClientId: r.tabClientId
      }, E = y.validateComputingConfig(v);
      if (!E.isValid) {
        n("error", { message: "Invalid Computing ID configuration", errors: E.errors });
        return;
      }
      try {
        s = F(w, v, r.options), s._onRdpRedirect = (b) => {
          n("rdp-redirect", b);
        }, l = new ResizeObserver((b) => {
          for (let I of b) {
            const { width: C, height: P } = I.contentRect;
            C > 0 && P > 0 && i();
          }
        }), o.value && l.observe(o.value), n("ready", s);
      } catch (b) {
        u.error("Failed to create terminal", { error: b.message }), n("error", { message: "Failed to create terminal", error: b.message });
      }
    }
    function g() {
      s && (console.log("📏 Terminal resizing to fit container..."), s.resize());
    }
    return e({
      getTerminal: () => s,
      resize: g
    }), (w, u) => (le(), de("div", {
      class: K(["terminal-wrapper", `computingId: ${r.computingId}`])
    }, [
      ue("div", {
        ref_key: "terminalContainer",
        ref: o,
        class: K(`ssh-terminal-container ${r.computingId}`)
      }, null, 2)
    ], 2));
  }
}, Ee = /* @__PURE__ */ fe(ye, [["__scopeId", "data-v-0a812d31"]]);
class we extends HTMLElement {
  constructor() {
    super(), this._terminalInstance = null, this._resizeObserver = null, this._shadow = this.attachShadow({ mode: "open" }), this._sshConfig = null, this._wsUrl = null, this._options = {}, this._container = document.createElement("div"), this._container.style.width = "100%", this._container.style.height = "100%", this._container.style.background = "#000", this._container.className = "ssh-terminal-container", this._injectStyles(), this._shadow.appendChild(this._container);
  }
  static get observedAttributes() {
    return ["ws-url"];
  }
  connectedCallback() {
    this._setupResizeObserver();
  }
  disconnectedCallback() {
    this._cleanup();
  }
  attributeChangedCallback(e, t, r) {
    e === "ws-url" && (this._wsUrl = r);
  }
  // 🔒 Secure method to set SSH configuration với validation
  setConfig(e, t = {}) {
    const r = T.createContextLogger("WebComponent");
    if (this._options = { ...this._options, ...t }, e.computingId && e.userToken) {
      const n = y.validateComputingConfig(e);
      if (!n.isValid) {
        const o = `Invalid computing configuration: ${n.errors.join(", ")}`;
        r.error("Invalid computing config in setConfig", { errors: n.errors }), this._container.innerHTML = `<p style="color:red;padding:8px;font-family:monospace;">${o}</p>`, this._dispatchEvent("error", { message: o, errors: n.errors });
        return;
      }
      this._sshConfig = {
        computingId: y.sanitizeString(e.computingId),
        userToken: e.userToken,
        // Không sanitize token
        wsUrl: e.wsUrl || this._wsUrl || "wss://ssh-proxy.dev.longvan.vn"
      };
    } else {
      const n = y.validateSSHConfig(e);
      if (!n.isValid) {
        const o = `Invalid SSH configuration: ${n.errors.join(", ")}`;
        r.error("Invalid SSH config in setConfig", { errors: n.errors }), this._container.innerHTML = `<p style="color:red;padding:8px;font-family:monospace;">${o}</p>`, this._dispatchEvent("error", { message: o, errors: n.errors });
        return;
      }
      this._sshConfig = {
        host: y.sanitizeString(e.host),
        username: y.sanitizeString(e.username),
        password: e.password,
        // Không sanitize password
        wsUrl: e.wsUrl || this._wsUrl || "wss://ssh-proxy.dev.longvan.vn"
      };
    }
    this._sshConfig.computingId ? this.initComputingTerminal() : this.initTerminal();
  }
  _injectStyles() {
    const e = document.createElement("style");
    e.textContent = `
.xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;right:0;left:0;top:0;bottom:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm .xterm-scroll-area{visibility:hidden}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer,.xterm .xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility,.xterm .xterm-message{position:absolute;left:0;top:0;bottom:0;right:0;z-index:10;color:transparent;pointer-events:none}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{text-decoration:double underline}.xterm-underline-3{text-decoration:wavy underline}.xterm-underline-4{text-decoration:dotted underline}.xterm-underline-5{text-decoration:dashed underline}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:overline underline}.xterm-overline.xterm-underline-2{text-decoration:overline double underline}.xterm-overline.xterm-underline-3{text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;position:absolute;top:0;right:0;pointer-events:none}.xterm-decoration-top{z-index:2;position:relative}.ssh-terminal-container[data-v-f4a58236],.ssh-terminal-container[data-v-b82cc34a]{width:100%;height:100%;min-height:300px;background-color:#000}
    `, this._shadow.appendChild(e);
  }
  _setupResizeObserver() {
    this._resizeObserver && this._resizeObserver.disconnect(), this._resizeObserver = new ResizeObserver(() => {
      this._terminalInstance?.resize && (clearTimeout(this._resizeTimeout), this._resizeTimeout = setTimeout(() => {
        this._terminalInstance.resize();
      }, 100));
    }), this._resizeObserver.observe(this);
  }
  _cleanup() {
    this._resizeObserver && (this._resizeObserver.disconnect(), this._resizeObserver = null), this._resizeTimeout && clearTimeout(this._resizeTimeout), this._terminalInstance?.dispose && (this._terminalInstance.dispose(), this._terminalInstance = null);
  }
  _dispatchEvent(e, t = {}) {
    this.dispatchEvent(
      new CustomEvent(e, {
        detail: t,
        bubbles: !0,
        composed: !0
      })
    );
  }
  initComputingTerminal() {
    const e = T.createContextLogger("WebComponent");
    try {
      this._terminal = F(this._container, this._sshConfig, this._options), this._dispatchEvent("ready", {
        message: "Computing terminal initialized",
        computingId: this._sshConfig.computingId
      });
    } catch (t) {
      e.error("Failed to create computing terminal", { error: t.message }), this._container.innerHTML = `<p style="color:red;padding:8px;font-family:monospace;">Failed to create terminal: ${t.message}</p>`, this._dispatchEvent("error", { message: t.message });
    }
  }
  initTerminal() {
    const e = T.createContextLogger("WebComponent");
    if (!this._sshConfig) {
      const l = "SSH configuration not set. Call setConfig() first.";
      e.error(l), this._container.innerHTML = `<p style="color:red;padding:8px;font-family:monospace;">${l}</p>`, this._dispatchEvent("error", { message: l });
      return;
    }
    const { host: t, username: r, password: n, wsUrl: o } = this._sshConfig;
    let s;
    if (this._sshConfig.computingId && this._sshConfig.token) {
      if (s = y.validateComputingConfig(this._sshConfig), !s.isValid) {
        const l = `Invalid computing configuration: ${s.errors.join(", ")}`;
        e.error("Computing config validation failed in initTerminal", { errors: s.errors }), this._container.innerHTML = `<p style="color:red;padding:8px;font-family:monospace;">${l}</p>`, this._dispatchEvent("error", { message: l, errors: s.errors });
        return;
      }
    } else if (s = y.validateSSHConfig(this._sshConfig), !s.isValid) {
      const l = `Invalid SSH configuration: ${s.errors.join(", ")}`;
      e.error("SSH config validation failed in initTerminal", { errors: s.errors }), this._container.innerHTML = `<p style="color:red;padding:8px;font-family:monospace;">${l}</p>`, this._dispatchEvent("error", { message: l, errors: s.errors });
      return;
    }
    try {
      this._terminalInstance = F(this._container, {
        host: t,
        username: r,
        password: n,
        wsUrl: o
      }), setTimeout(() => {
        this._dispatchEvent("ready", {
          host: t,
          // Only host is relatively safe to expose
          wsUrl: o
        });
      }, 100);
    } catch (l) {
      const d = `Failed to create terminal: ${l.message}`;
      e.error("Failed to create terminal", { error: l.message }), this._container.innerHTML = `<p style="color:red;padding:8px;font-family:monospace;">${d}</p>`, this._dispatchEvent("error", { message: d, error: l });
    }
  }
  // Public API methods
  reconnect() {
    try {
      this._terminalInstance?.reconnect(), this._dispatchEvent("reconnecting");
    } catch (e) {
      console.error("Reconnect failed:", e), this._dispatchEvent("error", { message: "Reconnect failed", error: e });
    }
  }
  resize() {
    try {
      this._terminalInstance?.resize();
    } catch (e) {
      console.error("Resize failed:", e);
    }
  }
  search(e) {
    try {
      return this._terminalInstance?.search(e);
    } catch (t) {
      return console.error("Search failed:", t), !1;
    }
  }
  searchPrevious(e) {
    try {
      return this._terminalInstance?.searchPrevious(e);
    } catch (t) {
      return console.error("Search previous failed:", t), !1;
    }
  }
  // Get terminal instance for advanced usage
  getTerminalInstance() {
    return this._terminalInstance;
  }
  // Check if terminal is connected
  isConnected() {
    return this._terminalInstance && this._terminalInstance.terminal;
  }
}
customElements.define("ssh-terminal", we);
typeof window < "u" && window.customElements && !window.customElements.get("ssh-terminal") && console.log("SSH Terminal Web Component registered for Vue 3 build");
export {
  Ee as SSHTerminal,
  we as SSHTerminalElement,
  Ee as Vue3SSHTerminal,
  F as createTerminal
};