This commit is contained in:
Denozordec committed 2026-05-03 11:16:07 +07:00
1 parent ce00c4c671
commit bdb9b72fac
66 files changed
+9553 -1547

No files matched your search

+53 -16
View File
@@ -79,6 +79,7 @@ function rosPost(
path: string,
body: Record<string, string>,
timeoutMs: number,
externalSignal?: AbortSignal,
): Promise<unknown> {
return new Promise((resolve, reject) => {
const basePath = params.apiPath ?? "/rest"
@@ -100,25 +101,55 @@ function rosPost(
const lib = params.useSsl ? https : http
const timer = setTimeout(() => {
req.destroy(new Error(`Connection to ${params.host}:${params.port} timed out after ${timeoutMs / 1000}s`))
}, timeoutMs)
let req: ReturnType<typeof lib.request> | undefined
const req = lib.request(options, (res) => {
let settled = false
const settle = (fn: () => void) => {
if (settled) return
settled = true
if (req) {
req.setTimeout(0)
req.removeListener("timeout", onSocketTimeout)
}
externalSignal?.removeEventListener("abort", onExternalAbort)
fn()
}
const onExternalAbort = () => {
req?.destroy(new DOMException("Aborted", "AbortError"))
}
const onSocketTimeout = () => {
req?.destroy()
settle(() => reject(new Error(`Connection to ${params.host}:${params.port} timed out after ${timeoutMs / 1000}s`)))
}
if (externalSignal?.aborted) {
settle(() => reject(new DOMException("Aborted", "AbortError")))
return
}
externalSignal?.addEventListener("abort", onExternalAbort)
req = lib.request(options, (res) => {
let buf = ""
res.setEncoding("utf8")
res.on("data", (chunk: string) => { buf += chunk })
res.on("end", () => {
clearTimeout(timer)
if (!res.statusCode || res.statusCode < 200 || res.statusCode >= 300) {
reject(new MikrotikError(res.statusCode ?? 0, path, buf)); return
}
try { resolve(JSON.parse(buf)) } catch {
reject(new Error(`Invalid JSON from RouterOS: ${buf.slice(0, 200)}`))
}
settle(() => {
if (!res.statusCode || res.statusCode < 200 || res.statusCode >= 300) {
reject(new MikrotikError(res.statusCode ?? 0, path, buf)); return
}
try { resolve(JSON.parse(buf)) } catch {
reject(new Error(`Invalid JSON from RouterOS: ${buf.slice(0, 200)}`))
}
})
})
})
req.on("error", (err) => { clearTimeout(timer); reject(err) })
req.setTimeout(timeoutMs)
req.on("timeout", onSocketTimeout)
req.on("error", (err) => {
settle(() => reject(err))
})
req.write(payload)
req.end()
})
@@ -189,8 +220,8 @@ export class MikrotikClient {
return rosRequest(this.params, path, timeoutMs) as Promise<T>
}
async post<T>(path: string, body: Record<string, string>, timeoutMs = 15_000): Promise<T> {
return rosPost(this.params, path, body, timeoutMs) as Promise<T>
async post<T>(path: string, body: Record<string, string>, timeoutMs = 15_000, signal?: AbortSignal): Promise<T> {
return rosPost(this.params, path, body, timeoutMs, signal) as Promise<T>
}
async delete(path: string, timeoutMs = 10_000): Promise<void> {
@@ -260,12 +291,18 @@ export class MikrotikClient {
return this.get<RosLogEntry[]>(`/log?limit=${limit}`)
}
async ping(address: string, count = 4, interfaceName?: string): Promise<RosPingResult[]> {
async ping(
address: string,
count = 4,
interfaceName?: string,
options?: { interval?: string },
): Promise<RosPingResult[]> {
const body: Record<string, string> = {
address,
count: String(count),
interval: "0.2s",
}
if (options?.interval && options.interval.trim()) body.interval = options.interval.trim()
else body.interval = "0.2s"
if (interfaceName && interfaceName.trim()) body.interface = interfaceName.trim()
return this.post<RosPingResult[]>("/tool/ping", body, 20_000)
}