Feat: socket.io support

This commit is contained in:
unitwk 2023-08-28 14:41:21 +08:00
parent 655c3c27ef
commit fa5c5b8fd6
10 changed files with 416 additions and 1 deletions

View File

@ -14,6 +14,7 @@
"axios": "^1.4.0",
"crc": "^4.3.2",
"dayjs": "^1.11.9",
"eventemitter3": "^5.0.1",
"i18next-scanner": "^4.3.0",
"lodash": "^4.17.21",
"marked": "^7.0.3",
@ -4123,6 +4124,11 @@
"node": ">=0.10.0"
}
},
"node_modules/eventemitter3": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="
},
"node_modules/events": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
@ -11848,6 +11854,11 @@
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
"dev": true
},
"eventemitter3": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="
},
"events": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",

View File

@ -20,6 +20,7 @@
"axios": "^1.4.0",
"crc": "^4.3.2",
"dayjs": "^1.11.9",
"eventemitter3": "^5.0.1",
"i18next-scanner": "^4.3.0",
"lodash": "^4.17.21",
"marked": "^7.0.3",

View File

@ -0,0 +1,86 @@
import { setUpTerminalStreamChannel } from "@/services/apis/instance";
import { parseForwardAddress } from "@/tools/protocol";
import { onUnmounted, ref, unref } from "vue";
import { io, Socket } from "socket.io-client";
import { t } from "@/lang/i18n";
import EventEmitter from "eventemitter3";
import type { DefaultEventsMap } from "@socket.io/component-emitter";
import type { InstanceDetail } from "@/types";
export interface UseTerminalParams {
instanceId: string;
daemonId: string;
}
export function useTerminal() {
const events = new EventEmitter();
let socket: Socket<DefaultEventsMap, DefaultEventsMap>;
const state = ref<InstanceDetail>();
const isReady = ref<boolean>(false);
const execute = async (config: UseTerminalParams) => {
isReady.value = false;
const res = await setUpTerminalStreamChannel().execute({
params: {
remote_uuid: config.daemonId,
uuid: config.instanceId
}
});
const remoteInfo = unref(res.value);
if (!remoteInfo) throw new Error(t("无法获取远程节点信息"));
const addr = parseForwardAddress(remoteInfo?.addr, "ws");
const password = remoteInfo.password;
socket = io(addr, {});
socket.on("connect", () => {
socket.emit("stream/auth", {
data: { password }
});
});
socket.on("stream/auth", (packet) => {
const data = packet.data;
if (data === true) {
socket.emit("stream/detail", {});
events.emit("connect");
isReady.value = true;
} else {
events.emit("error", new Error("Stream/auth error!"));
}
});
socket.on("reconnect", () => {
socket.emit("stream/auth", {
data: { password }
});
});
socket.on("disconnect", () => {
events.emit("disconnect");
socket.close();
});
socket.on("instance/stdout", (packet) => events.emit("stdout", packet?.data));
socket.on("stream/detail", (packet) => {
const v = packet?.data as InstanceDetail | undefined;
state.value = v;
events.emit("detail", v);
});
socket.connect();
return socket;
};
onUnmounted(() => {
events.removeAllListeners();
socket.close();
});
return {
execute,
events,
state
};
}

View File

@ -0,0 +1,22 @@
import { useDefineApi } from "@/stores/useDefineApi";
// 此处 API 接口可以用中文写注释,后期再统一翻译成英语。
export interface MissionPassportResponse {
addr: string;
password: string;
}
// 请求建立终端 Socket 连接
export const setUpTerminalStreamChannel = useDefineApi<
{
params: {
remote_uuid: string;
uuid: string;
};
},
MissionPassportResponse
>({
url: "/api/protected_instance/stream_channel",
method: "POST"
});

View File

@ -0,0 +1,85 @@
export function parseForwardAddress(addr: string, require: "http" | "ws") {
// save its protocol header
//ws://127.0.0.1:25565
let protocol = `${window.location.protocol}//`;
const addrProtocolString = addr.toLocaleLowerCase();
if (require === "http") {
if (addrProtocolString.indexOf("ws://") === 0) protocol = "http://";
else if (addrProtocolString.indexOf("wss://") === 0) protocol = "https://";
else if (addrProtocolString.indexOf("http://") === 0) protocol = "http://";
else if (addrProtocolString.indexOf("https://") === 0) protocol = "https://";
else if (protocol === "https://") protocol = "https://";
else protocol = "http://";
}
if (require === "ws") {
if (addrProtocolString.indexOf("http://") === 0) protocol = "ws://";
else if (addrProtocolString.indexOf("https://") === 0) protocol = "wss://";
else if (addrProtocolString.indexOf("ws://") === 0) protocol = "ws://";
else if (addrProtocolString.indexOf("wss://") === 0) protocol = "wss://";
else if (protocol === "https://") protocol = "wss://";
else protocol = "ws://";
}
// remove potentially redundant headers
addr = deleteWebsocketHeader(deleteHttpHeader(addr));
// port and ip are separated
let daemonPort = null;
let onlyAddr = null;
if (addr.split(":").length === 2) {
onlyAddr = addr.split(":")[0];
daemonPort = parseInt(addr.split(":")[1]);
if (isNaN(daemonPort))
throw new Error(`The address ${addr} failed to resolve, the port is incorrect`);
} else {
onlyAddr = addr;
}
// Reassemble the address based on the separated port and ip
const checkAddr = onlyAddr.toLocaleLowerCase();
if (checkAddr.indexOf("localhost") === 0 || checkAddr.indexOf("127.0.0.") === 0) {
addr = `${protocol}${window.location.hostname}${daemonPort ? `:${daemonPort}` : ""}`;
} else {
addr = `${protocol}${onlyAddr}${daemonPort ? `:${daemonPort}` : ""}`;
}
return addr;
}
// The ws address on the Daemon side is converted into an http address
export function daemonWsAddressToHttp(wsAddr = "") {
if (wsAddr.toLocaleLowerCase().indexOf("ws://") === 0) {
return `http://${wsAddr.slice(5)}`;
} else if (wsAddr.toLocaleLowerCase().indexOf("wss://") === 0) {
return `https://${wsAddr.slice(6)}`;
}
return wsAddr;
}
export function deleteWebsocketHeader(wsAddr: string) {
if (wsAddr.toLocaleLowerCase().indexOf("ws://") === 0) {
return `${wsAddr.slice(5)}`;
} else if (wsAddr.toLocaleLowerCase().indexOf("wss://") === 0) {
return `${wsAddr.slice(6)}`;
}
return wsAddr;
}
export function deleteHttpHeader(addr: string) {
if (addr.toLocaleLowerCase().indexOf("http://") === 0) {
return `${addr.slice(7)}`;
} else if (addr.toLocaleLowerCase().indexOf("https://") === 0) {
return `${addr.slice(8)}`;
}
return addr;
}
// The ws address on the Daemon side is converted to the local ws address
export function daemonWsAddressToWs(wsAddr = "") {
if (
wsAddr.toLocaleLowerCase().indexOf("ws://") !== 0 &&
wsAddr.toLocaleLowerCase().indexOf("wss://") !== 0
) {
return `ws://${wsAddr}`;
}
return wsAddr;
}

View File

@ -5,6 +5,9 @@ import type { LayoutCard } from "@/types";
import { DownOutlined, PlaySquareOutlined } from "@ant-design/icons-vue";
import { arrayFilter } from "../../tools/array";
import { useRoute } from "vue-router";
import { useTerminal } from "../../hooks/useTerminal";
import { onMounted } from "vue";
import type { InstanceDetail } from "../../types/index";
const props = defineProps<{
card: LayoutCard;
@ -59,6 +62,19 @@ const instanceOperations = arrayFilter([
}
}
]);
const { execute, events, state } = useTerminal();
// events.on("detail", (v: InstanceDetail) => {
// console.debug("XZXZX:", v);
// });
onMounted(async () => {
await execute({
instanceId,
daemonId
});
});
</script>
<template>
@ -94,6 +110,10 @@ const instanceOperations = arrayFilter([
<p>实例ID: {{ instanceId }}</p>
<p>守护进程ID: {{ daemonId }}</p>
<p>
{{ state }}
</p>
</template>
</CardPanel>
</template>

View File

@ -12,8 +12,13 @@ export default defineConfig({
proxy: {
"/api": {
target: "http://localhost:23333",
changeOrigin: true
changeOrigin: true,
ws: true
// rewrite: (path) => path.replace(/^\/api/, "")
},
"/socket.io": {
target: "ws://localhost:23333",
ws: true
}
}
},

180
package-lock.json generated Normal file
View File

@ -0,0 +1,180 @@
{
"name": "MCSManager",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"dependencies": {
"socket.io-client": "^4.7.2"
}
},
"node_modules/@socket.io/component-emitter": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
"integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg=="
},
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/engine.io-client": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.2.tgz",
"integrity": "sha512-CQZqbrpEYnrpGqC07a9dJDz4gePZUgTPMU3NKJPSeQOyw27Tst4Pl3FemKoFGAlHzgZmKjoRmiJvbWfhCXUlIg==",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1",
"engine.io-parser": "~5.2.1",
"ws": "~8.11.0",
"xmlhttprequest-ssl": "~2.0.0"
}
},
"node_modules/engine.io-parser": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz",
"integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/socket.io-client": {
"version": "4.7.2",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.2.tgz",
"integrity": "sha512-vtA0uD4ibrYD793SOIAwlo8cj6haOeMHrGvwPxJsxH7CeIksqJ+3Zc06RvWTIFgiSqx4A3sOnTXpfAEE2Zyz6w==",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.2",
"engine.io-client": "~6.5.2",
"socket.io-parser": "~4.2.4"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/socket.io-parser": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/ws": {
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
"integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": "^5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/xmlhttprequest-ssl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz",
"integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==",
"engines": {
"node": ">=0.4.0"
}
}
},
"dependencies": {
"@socket.io/component-emitter": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
"integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg=="
},
"debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"requires": {
"ms": "2.1.2"
}
},
"engine.io-client": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.2.tgz",
"integrity": "sha512-CQZqbrpEYnrpGqC07a9dJDz4gePZUgTPMU3NKJPSeQOyw27Tst4Pl3FemKoFGAlHzgZmKjoRmiJvbWfhCXUlIg==",
"requires": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1",
"engine.io-parser": "~5.2.1",
"ws": "~8.11.0",
"xmlhttprequest-ssl": "~2.0.0"
}
},
"engine.io-parser": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz",
"integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ=="
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"socket.io-client": {
"version": "4.7.2",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.2.tgz",
"integrity": "sha512-vtA0uD4ibrYD793SOIAwlo8cj6haOeMHrGvwPxJsxH7CeIksqJ+3Zc06RvWTIFgiSqx4A3sOnTXpfAEE2Zyz6w==",
"requires": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.2",
"engine.io-client": "~6.5.2",
"socket.io-parser": "~4.2.4"
}
},
"socket.io-parser": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
"requires": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1"
}
},
"ws": {
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
"integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
"requires": {}
},
"xmlhttprequest-ssl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz",
"integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A=="
}
}
}

5
package.json Normal file
View File

@ -0,0 +1,5 @@
{
"dependencies": {
"socket.io-client": "^4.7.2"
}
}