UNPKG

vue-ssh-terminal

Version:

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

298 lines (297 loc) 8.14 kB
import { Terminal as v } from "xterm"; import { FitAddon as T } from "xterm-addon-fit"; import { WebLinksAddon as z } from "xterm-addon-web-links"; import { SearchAddon as E } from "xterm-addon-search"; import { openBlock as S, createElementBlock as y, ref as H, onMounted as k, onBeforeUnmount as A } from "vue"; const w = { 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 }; function p(i, t, o = {}) { if (!i) throw new Error("Container element is required"); if (!t || !t.host || !t.username || !t.password) throw new Error("SSH configuration (host, username, password) is required"); const s = { ...w, ...o.theme ? { theme: { ...w.theme, ...o.theme } } : {}, ...o }, e = new v(s), l = new T(), c = new z(), d = new E(); e.loadAddon(l), e.loadAddon(c), e.loadAddon(d), e.open(i), setTimeout(() => { l.fit(); }, 0); const m = new ResizeObserver(() => { l.fit(); }); m.observe(i); const u = t.wsUrl || "ws://localhost:8080"; let n = null, h = !1; const f = () => { n && n.close(), n = new WebSocket(u), n.onopen = () => { h = !0; const a = { type: "ssh-connect", host: t.host, username: t.username, password: t.password }; n.send(JSON.stringify(a)), e.writeln("Connected to SSH relay server..."); }, n.onmessage = (a) => { try { const r = JSON.parse(a.data); r.type === "ssh-data" ? e.write(r.content) : r.type === "ssh-error" ? e.writeln(`\r Error: ${r.message}\r `) : r.type === "ssh-connected" ? e.writeln(`\r Connected to ${t.host} as ${t.username}\r `) : r.type === "ssh-closed" && e.writeln(`\r SSH connection closed\r `); } catch { e.write(a.data); } }, n.onclose = () => { h = !1, e.writeln(`\r Connection to SSH relay server closed\r `); }, n.onerror = (a) => { h = !1, e.writeln(`\r WebSocket error: ${a.message}\r `); }, e.onData((a) => { if (h && n.readyState === WebSocket.OPEN) { const r = { type: "ssh-data", content: a }; n.send(JSON.stringify(r)); } }), e.onResize(({ cols: a, rows: r }) => { if (h && n.readyState === WebSocket.OPEN) { const g = { type: "ssh-resize", cols: a, rows: r }; n.send(JSON.stringify(g)); } }); }; return f(), l.fit(), { terminal: e, fitAddon: l, searchAddon: d, reconnect: () => { f(); }, resize: () => { l.fit(); }, dispose: () => { n && n.close(), m.disconnect(), e.dispose(); }, search: (a, r) => { d.findNext(a, r); }, searchPrevious: (a, r) => { d.findPrevious(a, r); } }; } const _ = (i, t) => { const o = i.__vccOpts || i; for (const [s, e] of t) o[s] = e; return o; }, O = { name: "SSHTerminal", props: { host: { type: String, required: !0 }, username: { type: String, required: !0 }, password: { type: String, required: !0 }, wsUrl: { type: String, default: "ws://localhost:8080" }, options: { type: Object, default: () => ({}) } }, data() { return { terminal: null }; }, mounted() { this.initTerminal(), window.addEventListener("resize", this.handleResize); }, beforeDestroy() { this.terminal && this.terminal.dispose(), window.removeEventListener("resize", this.handleResize); }, methods: { initTerminal() { const i = this.$refs.terminalContainer, t = { host: this.host, username: this.username, password: this.password, wsUrl: this.wsUrl }; this.terminal = p(i, t, this.options), this.$emit("ready", this.terminal); }, handleResize() { this.terminal && this.terminal.resize(); } } }, x = { ref: "terminalContainer", class: "ssh-terminal-container" }; function C(i, t, o, s, e, l) { return S(), y("div", x, null, 512); } const W = /* @__PURE__ */ _(O, [["render", C], ["__scopeId", "data-v-c3dec533"]]); const $ = { __name: "SSHTerminal", props: { host: { type: String, required: !0 }, username: { type: String, required: !0 }, password: { type: String, required: !0 }, wsUrl: { type: String, default: "ws://localhost:8080" }, options: { type: Object, default: () => ({}) } }, emits: ["ready"], setup(i, { expose: t, emit: o }) { const s = i, e = o, l = H(null); let c = null; k(() => { d(), window.addEventListener("resize", m); }), A(() => { c && c.dispose(), window.removeEventListener("resize", m); }); function d() { const u = l.value, n = { host: s.host, username: s.username, password: s.password, wsUrl: s.wsUrl }; c = p(u, n, s.options), e("ready", c); } function m() { c && c.resize(); } return t({ getTerminal: () => c }), (u, n) => (S(), y("div", { ref_key: "terminalContainer", ref: l, class: "ssh-terminal-container" }, null, 512)); } }, P = /* @__PURE__ */ _($, [["__scopeId", "data-v-5b1c13db"]]); class b extends HTMLElement { constructor() { super(), this.attachShadow({ mode: "open" }), this.container = document.createElement("div"), this.container.className = "ssh-terminal-container", this.styles = document.createElement("style"), this.styles.textContent = ` .ssh-terminal-container { width: 100%; height: 100%; min-height: 300px; background-color: #000; } `, this.shadowRoot.appendChild(this.styles), this.shadowRoot.appendChild(this.container), this.terminal = null; } static get observedAttributes() { return ["host", "username", "password", "ws-url", "options"]; } connectedCallback() { this.initTerminal(), this.resizeHandler = this.handleResize.bind(this), window.addEventListener("resize", this.resizeHandler); } disconnectedCallback() { this.terminal && this.terminal.dispose(), window.removeEventListener("resize", this.resizeHandler); } attributeChangedCallback(t, o, s) { o !== s && this.terminal && ["host", "username", "password", "ws-url"].includes(t) && this.initTerminal(); } initTerminal() { this.terminal && this.terminal.dispose(); const t = this.getAttribute("host"), o = this.getAttribute("username"), s = this.getAttribute("password"), e = this.getAttribute("ws-url") || "ws://localhost:8080"; let l = {}; try { const d = this.getAttribute("options"); d && (l = JSON.parse(d)); } catch (d) { console.error("Invalid options format:", d); } if (!t || !o || !s) { console.error("Missing required attributes: host, username, password"); return; } const c = { host: t, username: o, password: s, wsUrl: e }; this.terminal = p(this.container, c, l), this.dispatchEvent(new CustomEvent("ready", { detail: { terminal: this.terminal } })); } handleResize() { this.terminal && this.terminal.resize(); } } customElements.define("ssh-terminal", b); const N = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, default: b }, Symbol.toStringTag, { value: "Module" })); typeof window < "u" && window.customElements && !window.customElements.get("ssh-terminal") && Promise.resolve().then(() => N).then(() => { console.log("SSH Terminal Web Component registered"); }).catch((i) => { console.error("Failed to register SSH Terminal Web Component", i); }); export { b as SSHTerminalElement, W as Vue2SSHTerminal, P as Vue3SSHTerminal, p as createTerminal };