Refactor: zip/unzip command

This commit is contained in:
unitwk 2024-02-06 18:58:18 +08:00
parent dc000d433e
commit 4d0c64df25
15 changed files with 252 additions and 258 deletions

1
.gitignore vendored
View File

@ -16,6 +16,7 @@ lib/
production/
.DS_Store
production-code/
test.js
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

View File

@ -1,90 +1,38 @@
import { $t } from "../i18n";
import fs from "fs-extra";
import path from "path";
import * as compressing from "compressing";
import child_process from "child_process";
import os from "os";
import archiver from "archiver";
import StreamZip, { async } from "node-stream-zip";
import { processWrapper } from "common";
import { PTY_PATH } from "../const";
// const StreamZip = require('node-stream-zip');
import { t } from "i18next";
import logger from "../service/log";
// Cross-platform high-efficiency/low-efficiency decompression scheme
const system = os.platform();
const COMPRESS_ERROR_MSG = {
invalidName: t("压缩或解压的文件名中包含非法字符,请重命名更改文件!"),
exitErr: t("解压/压缩程序执行结果不正确,请检查文件权限并重试!"),
startErr: t(
"解压/压缩程序启动失败,请确保系统已安装 zip 和 unzip 命令并作用到 MCSManager 的节点!"
),
timeoutErr: t("解压/压缩任务超时,已自动结束!")
};
function checkFileName(fileName: string) {
const disableList = ['"', "/", "\\", "?", "|"];
const disableList = ['"', "?", "|", "&"];
for (const iterator of disableList) {
if (fileName.includes(iterator)) return false;
}
return true;
}
function archiveZip(
zipPath: string,
files: string[],
fileCode: string = "utf-8"
): Promise<boolean> {
return new Promise((resolve, reject) => {
const output = fs.createWriteStream(zipPath);
const archive = archiver("zip", {
zlib: { level: 9 }
// encoding: fileCode
});
files.forEach((v) => {
const basename = path.normalize(path.basename(v));
if (!fs.existsSync(v)) return;
if (fs.statSync(v)?.isDirectory()) {
archive.directory(v, basename);
} else {
archive.file(v, { name: basename });
}
});
output.on("close", function () {
resolve(true);
});
archive.on("warning", function (err) {
reject(err);
});
archive.on("error", function (err) {
reject(err);
});
archive.pipe(output);
archive.finalize();
});
}
function archiveUnZip(
sourceZip: string,
destDir: string,
fileCode: string = "utf-8"
): Promise<boolean> {
return new Promise(async (resolve, reject) => {
const zip = new StreamZip.async({ file: sourceZip, nameEncoding: fileCode });
if (!fs.existsSync(destDir)) fs.mkdirsSync(destDir);
try {
await zip.extract(null, destDir);
return resolve(true);
} catch (error) {
reject(error);
}
zip
.close()
.then(() => {})
.catch(() => {});
});
}
export async function compress(
sourceZip: string,
files: string[],
fileCode?: string
): Promise<boolean> {
// if (system === "linux" && haveLinuxZip()) return await linuxZip(sourceZip, files);
// return await nodeCompress(sourceZip, files, fileCode);
if (hasGolangProcess()) return golangProcessZip(files, sourceZip, fileCode);
return await archiveZip(sourceZip, files, fileCode);
if (!checkFileName(sourceZip) || files.some((v) => !checkFileName(v)))
throw new Error(COMPRESS_ERROR_MSG.invalidName);
if (system === "win32") return await use7zipCompress(sourceZip, files);
return await useZip(sourceZip, files);
}
export async function decompress(
@ -92,155 +40,111 @@ export async function decompress(
dest: string,
fileCode?: string
): Promise<boolean> {
// if (system === "linux" && haveLinuxUnzip()) return await linuxUnzip(zipPath, dest);
// return await nodeDecompress(zipPath, dest, fileCode);
if (hasGolangProcess()) return await golangProcessUnzip(zipPath, dest, fileCode);
return await archiveUnZip(zipPath, dest, fileCode);
if (!checkFileName(zipPath) || !checkFileName(dest))
throw new Error(COMPRESS_ERROR_MSG.invalidName);
if (system === "win32") return await use7zipDecompress(zipPath, dest);
return await useUnzip(zipPath, dest);
}
async function _7zipCompress(zipPath: string, files: string[]) {
const cmd = `7z.exe a ${zipPath} ${files.join(" ")}`.split(" ");
console.log($t("TXT_CODE_common._7zip"), `${cmd.join(" ")}`);
function setTimeoutTask(
subProcess: child_process.ChildProcessWithoutNullStreams,
target: string | string[],
reject: (b: any) => void,
id: number | string
) {
setTimeout(() => {
if (!subProcess.exitCode && subProcess.exitCode !== 0) {
subProcess.kill("SIGKILL");
logger.error(
`[ZIP] ID: ${id} ${JSON.stringify(target)} Task timeout, exitCode: ${subProcess.exitCode}`
);
reject(new Error(COMPRESS_ERROR_MSG.timeoutErr));
} else {
reject(new Error(COMPRESS_ERROR_MSG.exitErr));
}
}, 1000 * 60);
}
async function useUnzip(sourceZip: string, destDir: string): Promise<boolean> {
const id = Date.now();
return new Promise((resolve, reject) => {
const p = cmd.splice(1);
const process = child_process.spawn(cmd[0], [...p], {
cwd: "./7zip/"
logger.info(
`ID: ${id} Function useUnzip(): Command: unzip ${["-o", sourceZip, "-d", destDir].join(" ")}}`
);
const subProcess = child_process.spawn("unzip", ["-o", sourceZip, "-d", destDir], {
cwd: path.normalize(path.dirname(sourceZip)),
stdio: "pipe",
windowsHide: true
});
if (!process || !process.pid) return reject(false);
process.on("exit", (code) => {
if (code) return reject(false);
if (!subProcess || !subProcess.pid) return reject(new Error(COMPRESS_ERROR_MSG.startErr));
subProcess.stdout.on("data", (text) => {});
subProcess.stderr.on("data", (text) => {});
subProcess.on("exit", (code) => {
logger.info(`ID: ${id} Function useUnzip() Done, return code: ${code}`);
if (code) return reject(new Error(COMPRESS_ERROR_MSG.exitErr));
return resolve(true);
});
});
}
async function _7zipDecompress(sourceZip: string, destDir: string) {
// ./7z.exe x archive.zip -oD:\7-Zip
const cmd = `7z.exe x ${sourceZip} -o${destDir}`.split(" ");
console.log($t("TXT_CODE_common._7unzip"), `${cmd.join(" ")}`);
return new Promise((resolve, reject) => {
const process = child_process.spawn(cmd[0], [cmd[1], cmd[2], cmd[3]], {
cwd: "./7zip/"
});
if (!process || !process.pid) return reject(false);
process.on("exit", (code) => {
if (code) return reject(false);
return resolve(true);
});
});
}
function haveLinuxUnzip() {
try {
const result = child_process.execSync("unzip -hh");
return result?.toString("utf-8").toLowerCase().includes("extended help for unzip");
} catch (error) {
return false;
}
}
function haveLinuxZip() {
try {
const result = child_process.execSync("zip -h2");
return result?.toString("utf-8").toLowerCase().includes("extended help for zip");
} catch (error) {
return false;
}
}
async function linuxUnzip(sourceZip: string, destDir: string) {
return new Promise((resolve, reject) => {
let end = false;
const process = child_process.spawn("unzip", ["-o", sourceZip, "-d", destDir], {
cwd: path.normalize(path.dirname(sourceZip))
});
if (!process || !process.pid) return reject(false);
process.on("exit", (code) => {
end = true;
if (code) return reject(false);
return resolve(true);
});
// timeout, terminate the task
setTimeout(() => {
if (end) return;
process.kill("SIGKILL");
reject(false);
}, 1000 * 60 * 60);
setTimeoutTask(subProcess, sourceZip, reject, id);
});
}
// zip -r a.zip css css_v1 js
// The ZIP file compressed by this function and the directory where the file is located must be in the same directory
async function linuxZip(sourceZip: string, files: string[]) {
async function useZip(distZip: string, files: string[]): Promise<boolean> {
const id = Date.now();
if (!files || files.length == 0) return false;
files = files.map((v) => path.basename(v));
return new Promise((resolve, reject) => {
let end = false;
files = files.map((v) => path.normalize(path.basename(v)));
const process = child_process.spawn("zip", ["-r", sourceZip, ...files], {
cwd: path.normalize(path.dirname(sourceZip))
logger.info(`ID: ${id} Function useZip(): Command: zip ${["-r", distZip, ...files].join(" ")}`);
const subProcess = child_process.spawn("zip", ["-r", distZip, ...files], {
cwd: path.normalize(path.dirname(distZip)),
stdio: "pipe",
windowsHide: true
});
if (!process || !process.pid) return reject(false);
process.on("exit", (code) => {
end = true;
if (code) return reject(false);
if (!subProcess || !subProcess.pid) return reject(new Error(COMPRESS_ERROR_MSG.startErr));
subProcess.stdout.on("data", (text) => {});
subProcess.stderr.on("data", (text) => {});
subProcess.on("exit", (code) => {
logger.info(`ID: ${id} Function useZip() Done, return code: ${code}`);
if (code) return reject(new Error(COMPRESS_ERROR_MSG.exitErr));
return resolve(true);
});
// timeout, terminate the task
setTimeout(() => {
if (end) return;
process.kill("SIGKILL");
reject(false);
}, 1000 * 60 * 60);
setTimeoutTask(subProcess, files, reject, id);
});
}
async function nodeCompress(zipPath: string, files: string[], fileCode: string = "utf-8") {
const stream = new compressing.zip.Stream();
files.forEach((v) => {
stream.addEntry(v, {});
});
const destStream = fs.createWriteStream(zipPath);
stream.pipe(destStream);
}
async function nodeDecompress(sourceZip: string, destDir: string, fileCode: string = "utf-8") {
return await compressing.zip.uncompress(sourceZip, destDir, {
zipFileNameEncoding: fileCode
async function use7zipCompress(zipPath: string, files: string[]): Promise<boolean> {
const cmd = `7z.exe a ${zipPath} ${files.join(" ")}`.split(" ");
console.log($t("TXT_CODE_common._7zip"), `${cmd.join(" ")}`);
return new Promise((resolve, reject) => {
const p = cmd.splice(1);
const subProcess = child_process.spawn(cmd[0], [...p], {
cwd: path.normalize(path.join(process.cwd(), "7zip")),
stdio: "pipe"
});
if (!subProcess || !subProcess.pid) return reject(new Error(COMPRESS_ERROR_MSG.startErr));
subProcess.on("exit", (code) => {
if (code) return reject(new Error(COMPRESS_ERROR_MSG.exitErr));
return resolve(true);
});
setTimeoutTask(subProcess, files, reject, "");
});
}
function hasGolangProcess() {
return fs.existsSync(PTY_PATH);
// ./7z.exe x archive.zip -oD:\7-Zip
async function use7zipDecompress(sourceZip: string, destDir: string): Promise<boolean> {
const cmd = `7z.exe x ${sourceZip} -o${destDir}`.split(" ");
console.log($t("TXT_CODE_common._7unzip"), `${cmd.join(" ")}`);
return new Promise((resolve, reject) => {
const subProcess = child_process.spawn(cmd[0], [cmd[1], cmd[2], cmd[3]], {
cwd: path.normalize(path.join(process.cwd(), "7zip")),
stdio: "pipe"
});
if (!subProcess || !subProcess.pid) return reject(new Error(COMPRESS_ERROR_MSG.startErr));
subProcess.on("exit", (code) => {
if (code) return reject(new Error(COMPRESS_ERROR_MSG.exitErr));
return resolve(true);
});
setTimeoutTask(subProcess, sourceZip, reject, "");
});
}
// ./pty_linux_arm64 -m unzip /Users/wangkun/Documents/OtherWork/MCSM-Daemon/data/InstanceData/3832159255b042da8cb3fd2012b0a996/tmp.zip /Users/wangkun/Documents/OtherWork/MCSM-Daemon/data/InstanceData/3832159255b042da8cb3fd2012b0a996
async function golangProcessUnzip(zipPath: string, destDir: string, fileCode: string = "utf-8") {
console.log("GO Zip Params", zipPath, destDir, fileCode);
return await new processWrapper(
PTY_PATH,
["-coder", fileCode, "-m", "unzip", zipPath, destDir],
".",
60 * 30
).start();
}
async function golangProcessZip(files: string[], destZip: string, fileCode: string = "utf-8") {
let p = ["-coder", fileCode, "-m", "zip"];
p = p.concat(files);
p.push(destZip);
console.log("GO Unzip Params", p);
return await new processWrapper(PTY_PATH, p, ".", 60 * 30).start();
}
// async function test() {
// console.log(
// "UNZIP::",
// await golangProcessUnzip(
// "/Users/wangkun/Documents/OtherWork/MCSM-Daemon/data/InstanceData/3832159d255b042da8cb3fd2012b0a996/tmp.zip",
// "/Users/wangkun/Documents/OtherWork/MCSM-Daemon/data/InstanceData/3832159255b042da8cb3fd2012b0a996",
// "utf-8"
// )
// );
// }
// test();

View File

@ -180,16 +180,15 @@ routerApp.on("file/compress", async (ctx, data) => {
globalEnv.fileTaskCount--;
}
protocol.response(ctx, true);
// start decompressing or compressing the file
fileTaskStart();
try {
if (type === 1) {
await fileManager.promiseZip(source, targets, code);
await fileManager.zip(source, targets, code);
} else {
await fileManager.promiseUnzip(source, targets, code);
await fileManager.unzip(source, targets, code);
}
protocol.response(ctx, true);
} catch (error) {
throw error;
} finally {
@ -197,6 +196,5 @@ routerApp.on("file/compress", async (ctx, data) => {
}
} catch (error) {
protocol.responseError(ctx, error);
} finally {
}
});

View File

@ -67,7 +67,7 @@ export class QuickInstallTask extends AsyncTask {
const fileManager = getFileManager(this.instance.instanceUuid);
try {
let result = await this.download();
result = await fileManager.promiseUnzip(this.TMP_ZIP_NAME, ".", "UTF-8");
result = await fileManager.unzip(this.TMP_ZIP_NAME, ".", "UTF-8");
if (!result) throw new Error($t("TXT_CODE_quick_install.unzipError"));
const config = JSON.parse(await fileManager.readFile(this.ZIP_CONFIG_JSON)) as InstanceConfig;

View File

@ -187,16 +187,14 @@ export default class FileManager {
throw new Error($t("TXT_CODE_system_file.unzipLimit", { max: MAX_ZIP_GB }));
}
unzip(sourceZip: string, destDir: string, code?: string) {
async unzip(sourceZip: string, destDir: string, code?: string) {
if (!code) code = this.fileCode;
if (!this.check(sourceZip) || !this.checkPath(destDir)) throw new Error(ERROR_MSG_01);
this.zipFileCheck(this.toAbsolutePath(sourceZip));
decompress(this.toAbsolutePath(sourceZip), this.toAbsolutePath(destDir), code)
.then(() => {})
.catch(() => {});
return await decompress(this.toAbsolutePath(sourceZip), this.toAbsolutePath(destDir), code);
}
zip(sourceZip: string, files: string[], code?: string, callback = (err: any, v?: boolean) => {}) {
async zip(sourceZip: string, files: string[], code?: string) {
if (!code) code = this.fileCode;
if (!this.checkPath(sourceZip)) throw new Error(ERROR_MSG_01);
const MAX_ZIP_GB = globalConfiguration.config.maxZipFileSize;
@ -214,30 +212,7 @@ export default class FileManager {
}
if (totalSize > MAX_TOTAL_FIELS_SIZE)
throw new Error($t("TXT_CODE_system_file.unzipLimit", { max: MAX_ZIP_GB }));
compress(sourceZipPath, filesPath, code)
.then((v) => {
callback(null, v);
})
.catch((err) => {
callback(err);
});
}
async promiseUnzip(sourceZip: string, destDir: string, code?: string): Promise<boolean> {
if (!code) code = this.fileCode;
if (!this.check(sourceZip) || !this.checkPath(destDir)) throw new Error(ERROR_MSG_01);
this.zipFileCheck(this.toAbsolutePath(sourceZip));
return await decompress(this.toAbsolutePath(sourceZip), this.toAbsolutePath(destDir), code);
}
async promiseZip(sourceZip: string, files: string[], code?: string): Promise<boolean> {
return new Promise((resolve, reject) => {
// The path is checked in the function.
this.zip(sourceZip, files, code, (err, v) => {
if (err) return reject(err);
resolve(v);
});
});
return await compress(sourceZipPath, filesPath, code);
}
async edit(target: string, data?: string) {

View File

@ -97,6 +97,7 @@ declare module 'vue' {
SelectInstances: typeof import('./src/components/fc/SelectInstances.vue')['default']
Style1: typeof import('./src/components/time/Style1.vue')['default']
Style2: typeof import('./src/components/time/Style2.vue')['default']
TaskLoadingDialog: typeof import('./src/components/fc/TaskLoadingDialog.vue')['default']
UploadFileDialog: typeof import('./src/components/fc/UploadFileDialog.vue')['default']
}
}

View File

@ -144,4 +144,3 @@ const operation = (type: "add" | "del", index = 0) => {
</template>
<style lang="scss" scoped></style>
../../tools/formV

View File

@ -0,0 +1,61 @@
<script setup lang="ts">
import { ref } from "vue";
import type { MountComponent } from "@/types";
import { LoadingOutlined } from "@ant-design/icons-vue";
interface Props extends MountComponent {
title: string;
text: string;
subTitle?: string;
}
const props = defineProps<Props>();
const open = ref(true);
const cancel = async () => {
open.value = false;
if (props.destroyComponent) props.destroyComponent();
};
defineExpose({
cancel
});
</script>
<template>
<a-modal
v-model:open="open"
centered
width="600px"
:title="props.title"
:mask-closable="false"
:closable="false"
:footer="null"
>
<div class="dialog-overflow-container">
<div class="flex flex-center">
<div>
<a-typography-paragraph>
<a-typography-title :level="5">
<div class="flex flex-center mb-20">
<LoadingOutlined style="font-size: 60px" />
</div>
<div class="flex flex-center" style="gap: 10px">
{{ props.text }}
</div>
</a-typography-title>
<a-typography-text v-if="props.subTitle">
<div
class="flex-center"
style="font-size: 12px; opacity: 0.8; max-width: 300px; text-align: center"
>
{{ props.subTitle }}
</div>
</a-typography-text>
</a-typography-paragraph>
</div>
</div>
</div>
</a-modal>
</template>

View File

@ -7,6 +7,7 @@ import KvOptionsDialogVue from "@/components/fc/KvOptionsDialog.vue";
import { t } from "@/lang/i18n";
import type { AntColumnsType } from "@/types/ant";
import UploadFileDialogVue from "./UploadFileDialog.vue";
import TaskLoadingDialog from "./TaskLoadingDialog.vue";
interface DockerConfigItem {
host: string;
@ -90,3 +91,14 @@ export async function useVolumeEditDialog(data: DockerConfigItem[] = []) {
}).mount<DockerConfigItem[]>(KvOptionsDialogVue)) || []
);
}
export async function openLoadingDialog(title: string, text: string, subTitle?: string) {
const component = (
await useMountComponent({
title,
text,
subTitle
})
).load<InstanceType<typeof TaskLoadingDialog>>(TaskLoadingDialog);
return component;
}

View File

@ -1,7 +1,7 @@
import { message, Modal } from "ant-design-vue";
import type { UploadProps } from "ant-design-vue";
import type { Key } from "ant-design-vue/es/table/interface";
import { ref, createVNode, reactive, type VNodeRef } from "vue";
import { ref, createVNode, reactive, type VNodeRef, onMounted } from "vue";
import { ExclamationCircleOutlined } from "@ant-design/icons-vue";
import { parseForwardAddress } from "@/tools/protocol";
import { number2permission, permission2number } from "@/tools/permission";
@ -27,6 +27,8 @@ import type {
FileStatus,
Permission
} from "@/types/fileManager";
import { reportError } from "@/tools/validator";
import { openLoadingDialog } from "@/components/fc";
export const useFileManager = (instanceId?: string, daemonId?: string) => {
const dataSource = ref<DataType[]>();
@ -273,6 +275,11 @@ export const useFileManager = (instanceId?: string, daemonId?: string) => {
return reportError(t("TXT_CODE_b152cd75"));
const filename = await openDialog(t("TXT_CODE_f8a15a94"), t("TXT_CODE_366bad15"), "", "zip");
const { execute } = compressFileApi();
const loadingDialog = await openLoadingDialog(
t("处理中.."),
t("正在压缩文件,请耐心等待..."),
t("我们正在全力处理文件,但是解压缩程序最多运行运行 40 分钟,超时将自动终止解压缩子进程。")
);
try {
await execute({
params: {
@ -286,17 +293,24 @@ export const useFileManager = (instanceId?: string, daemonId?: string) => {
targets: selectionData.value.map((e) => breadcrumbs[breadcrumbs.length - 1].path + e.name)
}
});
message.success(t("TXT_CODE_377e142"));
message.success(t("任务执行完毕!"));
await getFileList();
} catch (error: any) {
message.error(t("压缩任务执行失败!"));
reportError(error.message);
} finally {
loadingDialog.cancel();
}
};
const unzipFile = async (name: string) => {
const dirname = await openDialog(t("TXT_CODE_7669fd3f"), "", "", "unzip");
const { execute } = compressFileApi();
const loadingDialog = await openLoadingDialog(
t("处理中.."),
t("正在解压文件,请耐心等待..."),
t("我们正在全力处理文件,但是解压缩程序最多运行运行 40 分钟,超时将自动终止解压缩子进程。")
);
try {
await execute({
params: {
@ -313,10 +327,13 @@ export const useFileManager = (instanceId?: string, daemonId?: string) => {
: breadcrumbs[breadcrumbs.length - 1].path + dirname
}
});
message.success(t("TXT_CODE_16f55a9b"));
message.success(t("任务执行完毕!"));
await getFileList();
} catch (error: any) {
message.error(t("解压任务执行失败!"));
reportError(error.message);
} finally {
loadingDialog.cancel();
}
};

View File

@ -1,6 +1,6 @@
/* eslint-disable vue/one-component-per-file */
import { createApp, type Component } from "vue";
import { createApp, type Component, type App } from "vue";
import { sleep } from "@/tools/common";
export function useMountComponent(data: Record<string, any> = {}) {
@ -28,7 +28,23 @@ export function useMountComponent(data: Record<string, any> = {}) {
});
};
const load = <T extends Component>(component: Component): T => {
const div = document.createElement("div");
document.body.appendChild(div);
const app = createApp(component, {
...data,
async destroyComponent(delay = 1000) {
await sleep(delay);
app.unmount();
div.remove();
}
});
const mountedComponent = app.mount(div);
return mountedComponent as any;
};
return {
mount
mount,
load
};
}

View File

@ -144,7 +144,8 @@ export const compressFile = useDefineApi<
boolean
>({
url: "/api/files/compress",
method: "POST"
method: "POST",
timeout: Number.MAX_SAFE_INTEGER
});
export const uploadAddress = useDefineApi<

View File

@ -370,7 +370,7 @@ onUnmounted(() => {
</a-menu-item>
</a-menu>
</template>
<a-button size="large">
<a-button size="middle">
{{ t("TXT_CODE_fe731dfc") }}
<DownOutlined />
</a-button>
@ -439,9 +439,9 @@ onUnmounted(() => {
{{ t("TXT_CODE_76a82338") }}
</a-typography-text>
<a-radio-group v-model:value="dialog.code">
<a-radio-button value="utf-8">utf-8</a-radio-button>
<a-radio-button value="gbk">gbk</a-radio-button>
<a-radio-button value="big5">big5</a-radio-button>
<a-radio-button value="utf-8">UTF-8</a-radio-button>
<a-radio-button value="gbk">GBK</a-radio-button>
<a-radio-button value="big5">BIG5</a-radio-button>
</a-radio-group>
</a-space>

View File

@ -251,14 +251,18 @@ router.post(
const type = Number(ctx.request.body.type);
const code = String(ctx.request.body.code);
const remoteService = RemoteServiceSubsystem.getInstance(daemonId);
await new RemoteRequest(remoteService).request("file/compress", {
instanceUuid,
targets,
source,
type,
code
});
ctx.body = true;
const res = await new RemoteRequest(remoteService).request(
"file/compress",
{
instanceUuid,
targets,
source,
type,
code
},
0
);
ctx.body = res;
} catch (err) {
ctx.body = err;
}

View File

@ -32,16 +32,21 @@ export default class RemoteRequest {
const protocolData: IRequestPacket = { uuid, data };
// Start countdown
const countdownTask = setTimeout(
() =>
reject(new RemoteError(`Request daemon:(${this.rService.config.ip}) [${event}] timeout`)),
timeout
);
let countdownTask: NodeJS.Timeout;
if (timeout) {
countdownTask = setTimeout(
() =>
reject(
new RemoteError(`Request daemon:(${this.rService.config.ip}) [${event}] timeout`)
),
timeout
);
}
// define event function
const fn = (msg: IPacket) => {
if (msg.uuid === uuid) {
clearTimeout(countdownTask);
if (countdownTask) clearTimeout(countdownTask);
// Whenever a message is returned, match the ID to ensure that the response corresponds to the request,
// then delete its own event listener
this.rService.socket.removeListener(event, fn);