mirror of
https://github.com/ZHANGTIANYAO1/teamspeak-music-bot.git
synced 2026-10-01 20:42:50 +08:00
Add dual TS3/TS6 protocol support with auto-detection
The @honeybbq/teamspeak-client library already handles TS6 license block type 8 (Ts5Server) in its handshake, so voice connections work with both TS3 and TS6 servers. This commit adds the surrounding infrastructure: - protocol-detect.ts: Auto-detect server type by probing TS3 ServerQuery (port 10011) and TS6 HTTP Query (port 10080) in parallel - http-query.ts: TS6 HTTP Query client replacing the raw-TCP ServerQuery that TS6 servers no longer support (ports 10080/10443) - client.ts: Protocol-aware connection with auto-detection, TS6 HTTP Query setup, and forced protocol override option - manager.ts: Pass through serverProtocol and ts6ApiKey config options - connection.ts: Mark legacy TS3 ServerQuery as deprecated for TS6 https://claude.ai/code/session_016WhH58avUD9xy2dgADJgTh
This commit is contained in:
6 files changed
+386
-5
No files matched your search
+10
-1
@@ -8,6 +8,8 @@ import type { BotDatabase } from "../data/database.js";
|
|||||||
import type { BotConfig } from "../data/config.js";
|
import type { BotConfig } from "../data/config.js";
|
||||||
import type { Logger } from "../logger.js";
|
import type { Logger } from "../logger.js";
|
||||||
|
|
||||||
|
import type { ServerProtocol } from "../ts-protocol/client.js";
|
||||||
|
|
||||||
export interface CreateBotParams {
|
export interface CreateBotParams {
|
||||||
name: string;
|
name: string;
|
||||||
serverAddress: string;
|
serverAddress: string;
|
||||||
@@ -17,6 +19,10 @@ export interface CreateBotParams {
|
|||||||
defaultChannel?: string;
|
defaultChannel?: string;
|
||||||
channelPassword?: string;
|
channelPassword?: string;
|
||||||
autoStart?: boolean;
|
autoStart?: boolean;
|
||||||
|
/** Force TS3 or TS6 protocol; omit or "unknown" for auto-detect. */
|
||||||
|
serverProtocol?: ServerProtocol;
|
||||||
|
/** API key for TS6 HTTP Query (port 10080/10443). */
|
||||||
|
ts6ApiKey?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class BotManager {
|
export class BotManager {
|
||||||
@@ -57,6 +63,8 @@ export class BotManager {
|
|||||||
nickname: params.nickname,
|
nickname: params.nickname,
|
||||||
defaultChannel: params.defaultChannel,
|
defaultChannel: params.defaultChannel,
|
||||||
channelPassword: params.channelPassword,
|
channelPassword: params.channelPassword,
|
||||||
|
serverProtocol: params.serverProtocol,
|
||||||
|
ts6ApiKey: params.ts6ApiKey,
|
||||||
},
|
},
|
||||||
neteaseProvider: this.neteaseProvider,
|
neteaseProvider: this.neteaseProvider,
|
||||||
qqProvider: this.qqProvider,
|
qqProvider: this.qqProvider,
|
||||||
@@ -148,10 +156,11 @@ export class BotManager {
|
|||||||
tsOptions: {
|
tsOptions: {
|
||||||
host: saved.serverAddress,
|
host: saved.serverAddress,
|
||||||
port: saved.serverPort,
|
port: saved.serverPort,
|
||||||
queryPort: 10011,
|
queryPort: 10011, // Will be overridden by auto-detection for TS6
|
||||||
nickname: saved.nickname,
|
nickname: saved.nickname,
|
||||||
defaultChannel: saved.defaultChannel || undefined,
|
defaultChannel: saved.defaultChannel || undefined,
|
||||||
channelPassword: saved.channelPassword || undefined,
|
channelPassword: saved.channelPassword || undefined,
|
||||||
|
// Protocol will be auto-detected on connect
|
||||||
},
|
},
|
||||||
neteaseProvider: this.neteaseProvider,
|
neteaseProvider: this.neteaseProvider,
|
||||||
qqProvider: this.qqProvider,
|
qqProvider: this.qqProvider,
|
||||||
|
|||||||
@@ -12,18 +12,29 @@ import {
|
|||||||
type ClientInfo,
|
type ClientInfo,
|
||||||
} from "@honeybbq/teamspeak-client";
|
} from "@honeybbq/teamspeak-client";
|
||||||
import type { Logger } from "../logger.js";
|
import type { Logger } from "../logger.js";
|
||||||
|
import {
|
||||||
|
detectServerProtocol,
|
||||||
|
type ServerProtocol,
|
||||||
|
type ProtocolDetectResult,
|
||||||
|
} from "./protocol-detect.js";
|
||||||
|
import { TS6HttpQuery } from "./http-query.js";
|
||||||
|
|
||||||
export { CODEC_OPUS_MUSIC } from "./voice.js";
|
export { CODEC_OPUS_MUSIC } from "./voice.js";
|
||||||
|
export type { ServerProtocol } from "./protocol-detect.js";
|
||||||
|
|
||||||
export interface TS3ClientOptions {
|
export interface TS3ClientOptions {
|
||||||
host: string;
|
host: string;
|
||||||
port: number; // Voice/virtual server port (default 9987)
|
port: number; // Voice/virtual server port (default 9987)
|
||||||
queryPort: number; // ServerQuery port (default 10011) — unused now, kept for compat
|
queryPort: number; // ServerQuery port (10011 for TS3, 10080 for TS6 HTTP)
|
||||||
nickname: string;
|
nickname: string;
|
||||||
identity?: string; // Exported identity string, or undefined to generate new
|
identity?: string; // Exported identity string, or undefined to generate new
|
||||||
defaultChannel?: string;
|
defaultChannel?: string;
|
||||||
channelPassword?: string;
|
channelPassword?: string;
|
||||||
serverPassword?: string;
|
serverPassword?: string;
|
||||||
|
/** Force a specific protocol instead of auto-detecting. */
|
||||||
|
serverProtocol?: ServerProtocol;
|
||||||
|
/** API key for TS6 HTTP Query authentication. */
|
||||||
|
ts6ApiKey?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TS3TextMessage {
|
export interface TS3TextMessage {
|
||||||
@@ -40,6 +51,8 @@ export class TS3Client extends EventEmitter {
|
|||||||
private clientId = 0;
|
private clientId = 0;
|
||||||
private logger: Logger;
|
private logger: Logger;
|
||||||
private disconnecting = false;
|
private disconnecting = false;
|
||||||
|
private detectedProtocol: ServerProtocol = "unknown";
|
||||||
|
private httpQuery: TS6HttpQuery | null = null;
|
||||||
|
|
||||||
constructor(private options: TS3ClientOptions, logger: Logger) {
|
constructor(private options: TS3ClientOptions, logger: Logger) {
|
||||||
super();
|
super();
|
||||||
@@ -52,9 +65,50 @@ export class TS3Client extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The detected (or forced) server protocol after connect(). */
|
||||||
|
getServerProtocol(): ServerProtocol {
|
||||||
|
return this.detectedProtocol;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** TS6 HTTP Query client (available after connecting to a TS6 server). */
|
||||||
|
getHttpQuery(): TS6HttpQuery | null {
|
||||||
|
return this.httpQuery;
|
||||||
|
}
|
||||||
|
|
||||||
async connect(): Promise<void> {
|
async connect(): Promise<void> {
|
||||||
const addr = `${this.options.host}:${this.options.port}`;
|
const addr = `${this.options.host}:${this.options.port}`;
|
||||||
this.logger.info({ addr }, "Connecting to TeamSpeak server (full client protocol)");
|
|
||||||
|
// Detect or use forced protocol
|
||||||
|
if (this.options.serverProtocol && this.options.serverProtocol !== "unknown") {
|
||||||
|
this.detectedProtocol = this.options.serverProtocol;
|
||||||
|
this.logger.info(
|
||||||
|
{ addr, protocol: this.detectedProtocol },
|
||||||
|
"Using forced server protocol",
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.logger.info({ addr }, "Detecting server protocol (TS3/TS6)...");
|
||||||
|
const detection = await detectServerProtocol(this.options.host, this.options.port);
|
||||||
|
this.detectedProtocol = detection.protocol;
|
||||||
|
this.logger.info(
|
||||||
|
{ addr, protocol: this.detectedProtocol, queryPort: detection.queryPort },
|
||||||
|
`Server protocol detected: ${this.detectedProtocol.toUpperCase()}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up TS6 HTTP Query if applicable
|
||||||
|
if (this.detectedProtocol === "ts6") {
|
||||||
|
const queryPort = this.options.queryPort !== 10011 ? this.options.queryPort : 10080;
|
||||||
|
this.httpQuery = new TS6HttpQuery({
|
||||||
|
host: this.options.host,
|
||||||
|
port: queryPort,
|
||||||
|
apiKey: this.options.ts6ApiKey,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.info(
|
||||||
|
{ addr, protocol: this.detectedProtocol },
|
||||||
|
"Connecting to TeamSpeak server (full client protocol)",
|
||||||
|
);
|
||||||
|
|
||||||
// Throttle repeated "udp send error" warnings (fires every 20ms during playback if UDP breaks)
|
// Throttle repeated "udp send error" warnings (fires every 20ms during playback if UDP breaks)
|
||||||
let udpErrorCount = 0;
|
let udpErrorCount = 0;
|
||||||
@@ -115,7 +169,10 @@ export class TS3Client extends EventEmitter {
|
|||||||
await this.client.waitConnected();
|
await this.client.waitConnected();
|
||||||
this.clientId = this.client.clientID();
|
this.clientId = this.client.clientID();
|
||||||
this.voiceFramesSent = 0;
|
this.voiceFramesSent = 0;
|
||||||
this.logger.info({ clientId: this.clientId }, "Logged in (visible client)");
|
this.logger.info(
|
||||||
|
{ clientId: this.clientId, protocol: this.detectedProtocol },
|
||||||
|
`Logged in (visible client, ${this.detectedProtocol.toUpperCase()} server)`,
|
||||||
|
);
|
||||||
|
|
||||||
// Join default channel if specified
|
// Join default channel if specified
|
||||||
if (this.options.defaultChannel) {
|
if (this.options.defaultChannel) {
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* TS3 raw-TCP ServerQuery connection (port 10011).
|
||||||
|
*
|
||||||
|
* @deprecated This module only works with TS3 servers. TS6 servers replaced
|
||||||
|
* the raw-TCP ServerQuery with HTTP/HTTPS (port 10080/10443) and SSH (10022).
|
||||||
|
* For TS6 servers, use {@link ../http-query.js TS6HttpQuery} instead.
|
||||||
|
*/
|
||||||
import net from "node:net";
|
import net from "node:net";
|
||||||
import { EventEmitter } from "node:events";
|
import { EventEmitter } from "node:events";
|
||||||
import { encodeCommand, decodeResponse, parseErrorLine } from "./commands.js";
|
import { encodeCommand, decodeResponse, parseErrorLine } from "./commands.js";
|
||||||
|
|
||||||
export interface ConnectionOptions {
|
export interface ConnectionOptions {
|
||||||
host: string;
|
host: string;
|
||||||
port: number; // ServerQuery port, typically 10011
|
port: number; // ServerQuery port: 10011 (TS3) — not available on TS6
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CommandResult {
|
export interface CommandResult {
|
||||||
|
|||||||
@@ -0,0 +1,171 @@
|
|||||||
|
import http from "node:http";
|
||||||
|
import https from "node:https";
|
||||||
|
import { EventEmitter } from "node:events";
|
||||||
|
|
||||||
|
export interface HttpQueryOptions {
|
||||||
|
host: string;
|
||||||
|
port: number; // 10080 (HTTP) or 10443 (HTTPS)
|
||||||
|
useTls?: boolean;
|
||||||
|
apiKey?: string;
|
||||||
|
timeoutMs?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HttpQueryResult {
|
||||||
|
status: number;
|
||||||
|
body: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TS6 HTTP Query client.
|
||||||
|
*
|
||||||
|
* TeamSpeak 6 Server replaces the TS3 raw-TCP ServerQuery (port 10011)
|
||||||
|
* with an HTTP/HTTPS API on ports 10080/10443.
|
||||||
|
*
|
||||||
|
* Common endpoints (TS6 HTTP Query):
|
||||||
|
* GET / → server info / health check
|
||||||
|
* POST /api-key → create API key
|
||||||
|
* GET /1/serverlist → list virtual servers
|
||||||
|
* GET /1/clientlist?sid={sid} → list clients
|
||||||
|
* POST /1/sendtextmessage → send text message
|
||||||
|
* POST /1/clientmove → move a client
|
||||||
|
* GET /1/channellist?sid={sid} → list channels
|
||||||
|
* POST /1/clientupdate → update client properties
|
||||||
|
*/
|
||||||
|
export class TS6HttpQuery extends EventEmitter {
|
||||||
|
private options: Required<HttpQueryOptions>;
|
||||||
|
|
||||||
|
constructor(options: HttpQueryOptions) {
|
||||||
|
super();
|
||||||
|
this.options = {
|
||||||
|
host: options.host,
|
||||||
|
port: options.port,
|
||||||
|
useTls: options.useTls ?? options.port === 10443,
|
||||||
|
apiKey: options.apiKey ?? "",
|
||||||
|
timeoutMs: options.timeoutMs ?? 5000,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async request(
|
||||||
|
method: "GET" | "POST" | "PUT" | "DELETE",
|
||||||
|
path: string,
|
||||||
|
body?: Record<string, unknown>,
|
||||||
|
): Promise<HttpQueryResult> {
|
||||||
|
const { host, port, useTls, apiKey, timeoutMs } = this.options;
|
||||||
|
const transport = useTls ? https : http;
|
||||||
|
|
||||||
|
const headers: Record<string, string> = {
|
||||||
|
Accept: "application/json",
|
||||||
|
};
|
||||||
|
if (apiKey) {
|
||||||
|
headers["x-api-key"] = apiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
let bodyStr: string | undefined;
|
||||||
|
if (body) {
|
||||||
|
bodyStr = JSON.stringify(body);
|
||||||
|
headers["Content-Type"] = "application/json";
|
||||||
|
headers["Content-Length"] = String(Buffer.byteLength(bodyStr));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const req = transport.request(
|
||||||
|
{
|
||||||
|
hostname: host,
|
||||||
|
port,
|
||||||
|
path,
|
||||||
|
method,
|
||||||
|
timeout: timeoutMs,
|
||||||
|
headers,
|
||||||
|
rejectUnauthorized: false, // self-signed certs common on self-hosted
|
||||||
|
},
|
||||||
|
(res) => {
|
||||||
|
let data = "";
|
||||||
|
res.setEncoding("utf-8");
|
||||||
|
res.on("data", (chunk: string) => (data += chunk));
|
||||||
|
res.on("end", () => {
|
||||||
|
let parsed: unknown;
|
||||||
|
try {
|
||||||
|
parsed = JSON.parse(data);
|
||||||
|
} catch {
|
||||||
|
parsed = data;
|
||||||
|
}
|
||||||
|
resolve({
|
||||||
|
status: res.statusCode ?? 0,
|
||||||
|
body: parsed,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
req.on("error", reject);
|
||||||
|
req.on("timeout", () => {
|
||||||
|
req.destroy();
|
||||||
|
reject(new Error("TS6 HTTP Query timeout"));
|
||||||
|
});
|
||||||
|
|
||||||
|
if (bodyStr) {
|
||||||
|
req.write(bodyStr);
|
||||||
|
}
|
||||||
|
req.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Check if the TS6 HTTP Query is reachable */
|
||||||
|
async healthCheck(): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const result = await this.request("GET", "/");
|
||||||
|
return result.status >= 200 && result.status < 500;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** List virtual servers */
|
||||||
|
async serverList(): Promise<HttpQueryResult> {
|
||||||
|
return this.request("GET", "/1/serverlist");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** List clients on a virtual server */
|
||||||
|
async clientList(sid = 1): Promise<HttpQueryResult> {
|
||||||
|
return this.request("GET", `/1/clientlist?sid=${sid}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** List channels on a virtual server */
|
||||||
|
async channelList(sid = 1): Promise<HttpQueryResult> {
|
||||||
|
return this.request("GET", `/1/channellist?sid=${sid}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Send a text message */
|
||||||
|
async sendTextMessage(
|
||||||
|
targetMode: number,
|
||||||
|
target: number,
|
||||||
|
msg: string,
|
||||||
|
sid = 1,
|
||||||
|
): Promise<HttpQueryResult> {
|
||||||
|
return this.request("POST", `/1/sendtextmessage?sid=${sid}`, {
|
||||||
|
targetmode: targetMode,
|
||||||
|
target,
|
||||||
|
msg,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Update client properties (e.g., description) */
|
||||||
|
async clientUpdate(
|
||||||
|
properties: Record<string, string | number>,
|
||||||
|
sid = 1,
|
||||||
|
): Promise<HttpQueryResult> {
|
||||||
|
return this.request("POST", `/1/clientupdate?sid=${sid}`, properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Move a client to a channel */
|
||||||
|
async clientMove(
|
||||||
|
clid: number,
|
||||||
|
cid: number,
|
||||||
|
cpw?: string,
|
||||||
|
sid = 1,
|
||||||
|
): Promise<HttpQueryResult> {
|
||||||
|
const body: Record<string, unknown> = { clid, cid };
|
||||||
|
if (cpw) body.cpw = cpw;
|
||||||
|
return this.request("POST", `/1/clientmove?sid=${sid}`, body);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import {
|
||||||
|
detectServerProtocol,
|
||||||
|
type ServerProtocol,
|
||||||
|
type ProtocolDetectResult,
|
||||||
|
} from "./protocol-detect.js";
|
||||||
|
|
||||||
|
describe("protocol-detect", () => {
|
||||||
|
it("returns unknown for unreachable hosts", async () => {
|
||||||
|
const result = await detectServerProtocol("192.0.2.1", 9987, 1000);
|
||||||
|
expect(result.protocol).toBe("unknown");
|
||||||
|
expect(result.queryPort).toBeNull();
|
||||||
|
expect(result.voicePort).toBe(9987);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("result shape matches ProtocolDetectResult interface", async () => {
|
||||||
|
const result = await detectServerProtocol("127.0.0.1", 9987, 500);
|
||||||
|
expect(result).toHaveProperty("protocol");
|
||||||
|
expect(result).toHaveProperty("queryPort");
|
||||||
|
expect(result).toHaveProperty("voicePort");
|
||||||
|
expect(["ts3", "ts6", "unknown"]).toContain(result.protocol);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
import net from "node:net";
|
||||||
|
import http from "node:http";
|
||||||
|
|
||||||
|
export type ServerProtocol = "ts3" | "ts6" | "unknown";
|
||||||
|
|
||||||
|
export interface ProtocolDetectResult {
|
||||||
|
protocol: ServerProtocol;
|
||||||
|
/** The query port that responded (10011 for TS3, 10080 for TS6 HTTP) */
|
||||||
|
queryPort: number | null;
|
||||||
|
/** Whether the voice port (UDP 9987) is the same for both */
|
||||||
|
voicePort: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Probe a TeamSpeak server to determine if it's running TS3 or TS6.
|
||||||
|
*
|
||||||
|
* Detection strategy:
|
||||||
|
* 1. Try TCP connect to port 10011 (TS3 ServerQuery) — if banner starts with "TS3", it's TS3.
|
||||||
|
* 2. Try HTTP GET to port 10080 (TS6 HTTP Query) — if we get a valid HTTP response, it's TS6.
|
||||||
|
* 3. If neither responds, return "unknown" (voice-only connection may still work).
|
||||||
|
*/
|
||||||
|
export async function detectServerProtocol(
|
||||||
|
host: string,
|
||||||
|
voicePort = 9987,
|
||||||
|
timeoutMs = 3000,
|
||||||
|
): Promise<ProtocolDetectResult> {
|
||||||
|
const [ts3, ts6] = await Promise.allSettled([
|
||||||
|
probeTS3Query(host, 10011, timeoutMs),
|
||||||
|
probeTS6HttpQuery(host, 10080, timeoutMs),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (ts3.status === "fulfilled" && ts3.value) {
|
||||||
|
return { protocol: "ts3", queryPort: 10011, voicePort };
|
||||||
|
}
|
||||||
|
if (ts6.status === "fulfilled" && ts6.value) {
|
||||||
|
return { protocol: "ts6", queryPort: 10080, voicePort };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { protocol: "unknown", queryPort: null, voicePort };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Probe TS3 ServerQuery by connecting to raw TCP and checking for "TS3" banner.
|
||||||
|
*/
|
||||||
|
function probeTS3Query(host: string, port: number, timeoutMs: number): Promise<boolean> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const socket = net.createConnection({ host, port, timeout: timeoutMs });
|
||||||
|
let banner = "";
|
||||||
|
|
||||||
|
const cleanup = () => {
|
||||||
|
socket.removeAllListeners();
|
||||||
|
socket.destroy();
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.setTimeout(timeoutMs);
|
||||||
|
|
||||||
|
socket.on("data", (data: Buffer) => {
|
||||||
|
banner += data.toString("utf-8");
|
||||||
|
if (banner.includes("TS3")) {
|
||||||
|
cleanup();
|
||||||
|
resolve(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("connect", () => {
|
||||||
|
// Wait briefly for banner
|
||||||
|
setTimeout(() => {
|
||||||
|
cleanup();
|
||||||
|
resolve(banner.includes("TS3"));
|
||||||
|
}, 500);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("error", () => {
|
||||||
|
cleanup();
|
||||||
|
resolve(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("timeout", () => {
|
||||||
|
cleanup();
|
||||||
|
resolve(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Probe TS6 HTTP Query by sending GET / and checking for a valid response.
|
||||||
|
*/
|
||||||
|
function probeTS6HttpQuery(host: string, port: number, timeoutMs: number): Promise<boolean> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const req = http.request(
|
||||||
|
{
|
||||||
|
hostname: host,
|
||||||
|
port,
|
||||||
|
path: "/",
|
||||||
|
method: "GET",
|
||||||
|
timeout: timeoutMs,
|
||||||
|
headers: { Accept: "application/json" },
|
||||||
|
},
|
||||||
|
(res) => {
|
||||||
|
// TS6 HTTP Query returns some response (even 401/403 is valid — it means the service exists)
|
||||||
|
res.resume();
|
||||||
|
resolve(res.statusCode !== undefined);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
req.on("error", () => resolve(false));
|
||||||
|
req.on("timeout", () => {
|
||||||
|
req.destroy();
|
||||||
|
resolve(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
req.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in new issue
Block a user