Commit a3da557f by 李生煜

init

1 parent d1a7f04a
...@@ -10933,6 +10933,21 @@ ...@@ -10933,6 +10933,21 @@
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
"dev": true "dev": true
}, },
"xterm": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/xterm/-/xterm-4.9.0.tgz",
"integrity": "sha512-wGfqufmioctKr8VkbRuZbVDfjlXWGZZ1PWHy1yqqpGT3Nm6yaJx8lxDbSEBANtgaiVPTcKSp97sxOy5IlpqYfw=="
},
"xterm-addon-attach": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/xterm-addon-attach/-/xterm-addon-attach-0.6.0.tgz",
"integrity": "sha512-Mo8r3HTjI/EZfczVCwRU6jh438B4WLXxdFO86OB7bx0jGhwh2GdF4ifx/rP+OB+Cb2vmLhhVIZ00/7x3YSP3dg=="
},
"xterm-addon-fit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/xterm-addon-fit/-/xterm-addon-fit-0.4.0.tgz",
"integrity": "sha512-p4BESuV/g2L6pZzFHpeNLLnep9mp/DkF3qrPglMiucSFtD8iJxtMufEoEJbN8LZwB4i+8PFpFvVuFrGOSpW05w=="
},
"y18n": { "y18n": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
......
...@@ -10,7 +10,10 @@ ...@@ -10,7 +10,10 @@
"dependencies": { "dependencies": {
"vue": "^2.6.11", "vue": "^2.6.11",
"vue-class-component": "^7.2.3", "vue-class-component": "^7.2.3",
"vue-property-decorator": "^8.4.2" "vue-property-decorator": "^8.4.2",
"xterm": "^4.9.0",
"xterm-addon-attach": "^0.6.0",
"xterm-addon-fit": "^0.4.0"
}, },
"devDependencies": { "devDependencies": {
"@typescript-eslint/eslint-plugin": "^2.33.0", "@typescript-eslint/eslint-plugin": "^2.33.0",
......
<template> <template>
<div id="app"> <div id="xterm" class="xterm"></div>
<img alt="Vue logo" src="./assets/logo.png" />
<HelloWorld msg="Welcome to Your Vue.js + TypeScript App" />
</div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Vue } from "vue-property-decorator"; import { Component, Vue } from "vue-property-decorator";
import HelloWorld from "./components/HelloWorld.vue"; import "xterm/css/xterm.css";
import { Xterm } from "./xterm";
import { FitAddon } from "xterm-addon-fit";
import { AttachAddon } from "xterm-addon-attach";
@Component({ import { WebTTY, protocols } from "./webtty";
components: { import { ConnectionFactory } from "./websocket";
HelloWorld
@Component
export default class App extends Vue {
socketURL = "ws://localhost:8080/ws";
closer: unknown = "";
term: any = "";
mounted() {
console.log("Mounted");
this.initTerm()
}
created() {
console.log("Created");
} }
}) //TODO: 样式还需修改。请求后端时改为ws://.../id
export default class App extends Vue {} initTerm() {
const xt = document.getElementById("xterm");
console.log(xt);
if (xt) {
const term: any = new Xterm(xt);
this.term = term;
// const attachAddon = new AttachAddon();
const fitAddon = new FitAddon();
// this.term.term.loadAddon(attachAddon);
this.term.term.loadAddon(fitAddon);
fitAddon.fit();
this.term.term.focus();
const httpsEnabled = window.location.protocol == "https:";
const url =
(httpsEnabled ? "wss://" : "ws://") + "localhost:8080/" + "ws";
const args = window.location.search;
const factory = new ConnectionFactory(url, protocols);
const wt = new WebTTY(term, factory, args, "");
this.closer = wt.open();
}
}
}
</script> </script>
<style> <style>
......
export class ConnectionFactory {
url: string;
protocols: string[];
constructor(url: string, protocols: string[]) {
this.url = url;
this.protocols = protocols;
};
create(): Connection {
return new Connection(this.url, this.protocols);
};
}
export class Connection {
bare: WebSocket;
constructor(url: string, protocols: string[]) {
this.bare = new WebSocket(url, protocols);
}
open() {
// nothing todo for websocket
};
close() {
this.bare.close();
};
send(data: string) {
this.bare.send(data);
};
isOpen(): boolean {
if (this.bare.readyState == WebSocket.CONNECTING ||
this.bare.readyState == WebSocket.OPEN) {
return true
}
return false
}
onOpen(callback: () => void) {
this.bare.onopen = (event) => {
callback();
}
};
onReceive(callback: (data: string) => void) {
this.bare.onmessage = (event) => {
callback(event.data);
}
};
onClose(callback: () => void) {
this.bare.onclose = (event) => {
callback();
};
};
}
export const protocols = ["webtty"];
export const msgInputUnknown = '0';
export const msgInput = '1';
export const msgPing = '2';
export const msgResizeTerminal = '3';
export const msgUnknownOutput = '0';
export const msgOutput = '1';
export const msgPong = '2';
export const msgSetWindowTitle = '3';
export const msgSetPreferences = '4';
export const msgSetReconnect = '5';
export interface Terminal {
info(): { columns: number, rows: number };
output(data: string): void;
showMessage(message: string, timeout: number): void;
removeMessage(): void;
setWindowTitle(title: string): void;
setPreferences(value: object): void;
onInput(callback: (input: string) => void): void;
onResize(callback: (colmuns: number, rows: number) => void): void;
reset(): void;
deactivate(): void;
close(): void;
}
export interface Connection {
open(): void;
close(): void;
send(data: string): void;
isOpen(): boolean;
onOpen(callback: () => void): void;
onReceive(callback: (data: string) => void): void;
onClose(callback: () => void): void;
}
export interface ConnectionFactory {
create(): Connection;
}
export class WebTTY {
term: Terminal;
connectionFactory: ConnectionFactory;
args: string;
authToken: string;
reconnect: number;
constructor(term: Terminal, connectionFactory: ConnectionFactory, args: string, authToken: string) {
this.term = term;
this.connectionFactory = connectionFactory;
this.args = args;
this.authToken = authToken;
this.reconnect = -1;
};
open() {
let connection = this.connectionFactory.create();
let pingTimer: any;
let reconnectTimeout: any;
const setup = () => {
connection.onOpen(() => {
const termInfo = this.term.info();
connection.send(JSON.stringify(
{
Arguments: this.args,
AuthToken: this.authToken,
}
));
const resizeHandler = (colmuns: number, rows: number) => {
connection.send(
msgResizeTerminal + JSON.stringify(
{
columns: colmuns,
rows: rows
}
)
);
};
this.term.onResize(resizeHandler);
resizeHandler(termInfo.columns, termInfo.rows);
this.term.onInput(
(input: string) => {
connection.send(msgInput + input);
}
);
pingTimer = setInterval(() => {
connection.send(msgPing)
}, 30 * 1000);
});
connection.onReceive((data) => {
const payload = data.slice(1);
switch (data[0]) {
case msgOutput:
this.term.output(atob(payload));
break;
case msgPong:
break;
case msgSetWindowTitle:
this.term.setWindowTitle(payload);
break;
case msgSetPreferences:
const preferences = JSON.parse(payload);
this.term.setPreferences(preferences);
break;
case msgSetReconnect:
const autoReconnect = JSON.parse(payload);
console.log("Enabling reconnect: " + autoReconnect + " seconds")
this.reconnect = autoReconnect;
break;
}
});
connection.onClose(() => {
clearInterval(pingTimer);
this.term.deactivate();
this.term.showMessage("Connection Closed", 0);
if (this.reconnect > 0) {
reconnectTimeout = setTimeout(() => {
connection = this.connectionFactory.create();
this.term.reset();
setup();
}, this.reconnect * 1000);
}
});
connection.open();
}
setup();
return () => {
clearTimeout(reconnectTimeout);
connection.close();
}
};
};
import {Terminal} from "xterm";
// import { lib } from "libapps"
export class Xterm {
elem: HTMLElement;
term: Terminal;
resizeListener: (() => void) | undefined;
// decoder: lib.UTF8Decoder;
message: HTMLElement;
messageTimeout: number;
messageTimer: any;
constructor(elem: HTMLElement) {
this.elem = elem;
this.term = new Terminal();
this.message = elem.ownerDocument.createElement("div");
this.message.className = "xterm-overlay";
this.messageTimeout = 2000;
this.term.onResize( () => {
this.term.scrollToBottom();
this.showMessage(String(this.term.cols) + "x" + String(this.term.rows), this.messageTimeout);
});
this.term.open(elem);
// this.decoder = new lib.UTF8Decoder()
};
info(): { columns: number, rows: number } {
return { columns: this.term.cols, rows: this.term.rows };
};
output(data: string) {
// this.term.write(this.decoder.decode(data));
this.term.write(data);
};
showMessage(message: string, timeout: number) {
this.message.textContent = message;
this.elem.appendChild(this.message);
if (this.messageTimer) {
clearTimeout(this.messageTimer);
}
if (timeout > 0) {
this.messageTimer = setTimeout(() => {
this.elem.removeChild(this.message);
}, timeout);
}
};
removeMessage(): void {
if (this.message.parentNode == this.elem) {
this.elem.removeChild(this.message);
}
}
setWindowTitle(title: string) {
document.title = title;
};
setPreferences(value: object) {
};
onInput(callback: (input: string) => void) {
this.term.onData(data => {
callback(data);
});
};
onResize(callback: (colmuns: number, rows: number) => void) {
this.term.onResize( (data) => {
callback(data.cols, data.rows);
});
};
deactivate(): void {
// this.term.off("data");
// this.term.resize();
this.term.blur();
}
reset(): void {
this.removeMessage();
this.term.clear();
}
close(): void {
// window.removeEventListener("resize", this.resizeListener);
this.term.dispose();
}
}
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!