@honeybbq/teamspeak 1.0.0-beta.12 → 1.0.0-beta.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -5
- package/dist/client/capabilities.d.ts +4 -4
- package/dist/client/capabilities.d.ts.map +1 -1
- package/dist/client/capabilities.js +3 -8
- package/dist/client/capabilities.js.map +1 -1
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/client.js +50 -9
- package/dist/client/client.js.map +1 -1
- package/dist/client/crypto/identity.d.ts +2 -0
- package/dist/client/crypto/identity.d.ts.map +1 -1
- package/dist/client/crypto/identity.js +2 -2
- package/dist/client/crypto/identity.js.map +1 -1
- package/dist/client/crypto/index.d.ts +1 -1
- package/dist/client/crypto/index.d.ts.map +1 -1
- package/dist/client/crypto/index.js +1 -1
- package/dist/client/crypto/index.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/trace.d.ts +7 -0
- package/dist/client/trace.d.ts.map +1 -1
- package/dist/client/transport/handler.d.ts +2 -0
- package/dist/client/transport/handler.d.ts.map +1 -1
- package/dist/client/transport/handler.js +4 -0
- package/dist/client/transport/handler.js.map +1 -1
- package/dist/client/types.d.ts +9 -2
- package/dist/client/types.d.ts.map +1 -1
- package/dist/client/types.js +2 -0
- package/dist/client/types.js.map +1 -1
- package/dist/client/view.d.ts +3 -3
- package/dist/client/view.d.ts.map +1 -1
- package/dist/client/view.js.map +1 -1
- package/dist/core/api/channels.d.ts +7 -3
- package/dist/core/api/channels.d.ts.map +1 -1
- package/dist/core/api/channels.js +5 -3
- package/dist/core/api/channels.js.map +1 -1
- package/dist/core/api/clients.d.ts +38 -1
- package/dist/core/api/clients.d.ts.map +1 -1
- package/dist/core/api/clients.js +33 -2
- package/dist/core/api/clients.js.map +1 -1
- package/dist/core/api/command.d.ts +18 -1
- package/dist/core/api/command.d.ts.map +1 -1
- package/dist/core/api/command.js +7 -1
- package/dist/core/api/command.js.map +1 -1
- package/dist/core/api/instance.d.ts +10 -2
- package/dist/core/api/instance.d.ts.map +1 -1
- package/dist/core/api/instance.js +6 -2
- package/dist/core/api/instance.js.map +1 -1
- package/dist/core/api/moderation.d.ts +7 -0
- package/dist/core/api/moderation.d.ts.map +1 -1
- package/dist/core/api/moderation.js +7 -0
- package/dist/core/api/moderation.js.map +1 -1
- package/dist/core/api/parse.d.ts.map +1 -1
- package/dist/core/api/parse.js +1 -0
- package/dist/core/api/parse.js.map +1 -1
- package/dist/core/api/server.d.ts +9 -0
- package/dist/core/api/server.d.ts.map +1 -1
- package/dist/core/api/server.js +3 -0
- package/dist/core/api/server.js.map +1 -1
- package/dist/core/command/responses.d.ts.map +1 -1
- package/dist/core/command/responses.js +1 -0
- package/dist/core/command/responses.js.map +1 -1
- package/dist/core/events.d.ts +47 -5
- package/dist/core/events.d.ts.map +1 -1
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/notification-name.d.ts +4 -0
- package/dist/core/notification-name.d.ts.map +1 -1
- package/dist/core/notification-name.js +4 -0
- package/dist/core/notification-name.js.map +1 -1
- package/dist/core/notifications.d.ts +6 -2
- package/dist/core/notifications.d.ts.map +1 -1
- package/dist/core/notifications.js +32 -1
- package/dist/core/notifications.js.map +1 -1
- package/dist/core/types.d.ts +7 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +16 -0
- package/dist/core/types.js.map +1 -1
- package/dist/query/client.d.ts +12 -3
- package/dist/query/client.d.ts.map +1 -1
- package/dist/query/client.js +22 -15
- package/dist/query/client.js.map +1 -1
- package/dist/query/index.d.ts +1 -1
- package/dist/query/index.d.ts.map +1 -1
- package/dist/query/options.d.ts +17 -5
- package/dist/query/options.d.ts.map +1 -1
- package/dist/query/options.js +1 -0
- package/dist/query/options.js.map +1 -1
- package/dist/query/ssh.d.ts.map +1 -1
- package/dist/query/ssh.js +4 -1
- package/dist/query/ssh.js.map +1 -1
- package/dist/query/stream.d.ts +8 -3
- package/dist/query/stream.d.ts.map +1 -1
- package/dist/query/stream.js +26 -3
- package/dist/query/stream.js.map +1 -1
- package/dist/query/tcp.js +1 -1
- package/dist/query/tcp.js.map +1 -1
- package/dist/query/transport.d.ts +35 -0
- package/dist/query/transport.d.ts.map +1 -1
- package/dist/query/web.d.ts +17 -5
- package/dist/query/web.d.ts.map +1 -1
- package/dist/query/web.js +38 -9
- package/dist/query/web.js.map +1 -1
- package/package.json +6 -5
- package/src/client/capabilities.ts +14 -11
- package/src/client/client.ts +47 -6
- package/src/client/crypto/identity.ts +2 -2
- package/src/client/crypto/index.ts +1 -0
- package/src/client/index.ts +2 -0
- package/src/client/trace.ts +7 -0
- package/src/client/transport/handler.ts +5 -0
- package/src/client/types.ts +9 -0
- package/src/client/view.ts +2 -3
- package/src/core/api/channels.ts +9 -3
- package/src/core/api/clients.ts +73 -2
- package/src/core/api/command.ts +22 -1
- package/src/core/api/instance.ts +20 -2
- package/src/core/api/moderation.ts +7 -0
- package/src/core/api/parse.ts +1 -0
- package/src/core/api/server.ts +12 -0
- package/src/core/command/responses.ts +1 -0
- package/src/core/events.ts +48 -5
- package/src/core/index.ts +2 -1
- package/src/core/notification-name.ts +4 -0
- package/src/core/notifications.ts +48 -17
- package/src/core/types.ts +14 -0
- package/src/query/client.ts +28 -15
- package/src/query/index.ts +1 -1
- package/src/query/options.ts +19 -5
- package/src/query/ssh.ts +6 -1
- package/src/query/stream.ts +31 -4
- package/src/query/tcp.ts +1 -1
- package/src/query/testing/live.ts +311 -0
- package/src/query/testing/ssh-keys.ts +67 -0
- package/src/query/transport.ts +36 -0
- package/src/query/web.ts +57 -14
|
@@ -24,6 +24,8 @@ import { Reason } from "./reason.js";
|
|
|
24
24
|
import { applyClientChanges } from "./api/clients.js";
|
|
25
25
|
import { parseInt10, parseUint16, parseUint64 } from "./helpers.js";
|
|
26
26
|
import type { Command } from "./command/command.js";
|
|
27
|
+
import type { ChannelID } from "./command/ids.js";
|
|
28
|
+
import type { ClientEntry } from "./api/clients.js";
|
|
27
29
|
import type {
|
|
28
30
|
ChannelCreatedEvent,
|
|
29
31
|
ChannelDeletedEvent,
|
|
@@ -33,6 +35,7 @@ import type {
|
|
|
33
35
|
ChannelPasswordChangedEvent,
|
|
34
36
|
ChannelSubscribedEvent,
|
|
35
37
|
ChannelUnsubscribedEvent,
|
|
38
|
+
ClientChatClosedEvent,
|
|
36
39
|
ClientChatComposingEvent,
|
|
37
40
|
ClientEnterEvent,
|
|
38
41
|
ClientChannelGroupChangedEvent,
|
|
@@ -70,6 +73,7 @@ export type NotificationResult =
|
|
|
70
73
|
| { kind: "channelUnsubscribed"; event: ChannelUnsubscribedEvent }
|
|
71
74
|
| { kind: "pluginCommand"; event: PluginCommandEvent }
|
|
72
75
|
| { kind: "clientChatComposing"; event: ClientChatComposingEvent }
|
|
76
|
+
| { kind: "clientChatClosed"; event: ClientChatClosedEvent }
|
|
73
77
|
| { kind: "unknown" };
|
|
74
78
|
|
|
75
79
|
/**
|
|
@@ -87,7 +91,7 @@ export type NotificationResult =
|
|
|
87
91
|
export function handleNotification(
|
|
88
92
|
cmd: Command,
|
|
89
93
|
selfCLID: number,
|
|
90
|
-
clients: Map<number,
|
|
94
|
+
clients: Map<number, ClientEntry>,
|
|
91
95
|
): NotificationResult {
|
|
92
96
|
switch (cmd.name) {
|
|
93
97
|
case "notifycliententerview":
|
|
@@ -137,6 +141,8 @@ export function handleNotification(
|
|
|
137
141
|
};
|
|
138
142
|
case "notifyclientchatcomposing":
|
|
139
143
|
return handleChatComposing(cmd);
|
|
144
|
+
case "notifyclientchatclosed":
|
|
145
|
+
return handleChatClosed(cmd);
|
|
140
146
|
default:
|
|
141
147
|
return { kind: "unknown" };
|
|
142
148
|
}
|
|
@@ -144,19 +150,41 @@ export function handleNotification(
|
|
|
144
150
|
|
|
145
151
|
function handleClientEnterView(
|
|
146
152
|
cmd: Command,
|
|
147
|
-
clients: Map<number,
|
|
153
|
+
clients: Map<number, ClientEntry>,
|
|
148
154
|
): NotificationResult {
|
|
149
155
|
const info = parseClient(cmd.params);
|
|
150
156
|
// No client has id 0: a row without one describes nobody.
|
|
151
157
|
if (info.id === 0) return { kind: "unknown" };
|
|
158
|
+
// The roster keeps the record alone; the event adds where it came from and why.
|
|
152
159
|
clients.set(info.id, info);
|
|
153
|
-
return {
|
|
160
|
+
return {
|
|
161
|
+
kind: "clientEnter",
|
|
162
|
+
info: {
|
|
163
|
+
...info,
|
|
164
|
+
...channelOf(cmd.params, "cfid", "fromChannelID"),
|
|
165
|
+
...(cmd.params["reasonid"] === undefined
|
|
166
|
+
? {}
|
|
167
|
+
: { reason: parseInt10(cmd.params["reasonid"]) }),
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** A channel id the row names under `key`, as `{ [name]: id }`; nothing when the row lacks the key. */
|
|
173
|
+
function channelOf<N extends string>(
|
|
174
|
+
params: Readonly<Record<string, string>>,
|
|
175
|
+
key: string,
|
|
176
|
+
name: N,
|
|
177
|
+
): { [K in N]?: ChannelID } {
|
|
178
|
+
const raw = params[key];
|
|
179
|
+
return raw === undefined
|
|
180
|
+
? {}
|
|
181
|
+
: ({ [name]: asChannelID(parseUint64(raw)) } as { [K in N]?: ChannelID });
|
|
154
182
|
}
|
|
155
183
|
|
|
156
184
|
function handleClientLeftView(
|
|
157
185
|
cmd: Command,
|
|
158
186
|
selfCLID: number,
|
|
159
|
-
clients: Map<number,
|
|
187
|
+
clients: Map<number, ClientEntry>,
|
|
160
188
|
): NotificationResult {
|
|
161
189
|
const clid = asClientID(parseUint16(cmd.params["clid"] ?? ""));
|
|
162
190
|
if (clid === 0) return { kind: "unknown" };
|
|
@@ -166,6 +194,8 @@ function handleClientLeftView(
|
|
|
166
194
|
kind: "clientLeave",
|
|
167
195
|
event: {
|
|
168
196
|
clientID: clid,
|
|
197
|
+
...channelOf(cmd.params, "cfid", "fromChannelID"),
|
|
198
|
+
...channelOf(cmd.params, "ctid", "targetChannelID"),
|
|
169
199
|
reason: parseInt10(cmd.params["reasonid"] ?? ""),
|
|
170
200
|
reasonMessage: cmd.params["reasonmsg"] ?? "",
|
|
171
201
|
invokerID: asClientID(parseUint16(cmd.params["invokerid"] ?? "")),
|
|
@@ -176,10 +206,7 @@ function handleClientLeftView(
|
|
|
176
206
|
};
|
|
177
207
|
}
|
|
178
208
|
|
|
179
|
-
function handleClientMoved(
|
|
180
|
-
cmd: Command,
|
|
181
|
-
clients: Map<number, ClientEnterEvent>,
|
|
182
|
-
): NotificationResult {
|
|
209
|
+
function handleClientMoved(cmd: Command, clients: Map<number, ClientEntry>): NotificationResult {
|
|
183
210
|
const clid = asClientID(parseUint16(cmd.params["clid"] ?? ""));
|
|
184
211
|
const ctid = asChannelID(parseUint64(cmd.params["ctid"] ?? ""));
|
|
185
212
|
if (clid === 0) return { kind: "unknown" };
|
|
@@ -197,6 +224,7 @@ function handleClientMoved(
|
|
|
197
224
|
event: {
|
|
198
225
|
clientID: clid,
|
|
199
226
|
targetChannelID: ctid,
|
|
227
|
+
...channelOf(cmd.params, "cfid", "fromChannelID"),
|
|
200
228
|
reason: parseInt10(cmd.params["reasonid"] ?? ""),
|
|
201
229
|
reasonMessage: cmd.params["reasonmsg"] ?? "",
|
|
202
230
|
invokerID,
|
|
@@ -206,10 +234,7 @@ function handleClientMoved(
|
|
|
206
234
|
};
|
|
207
235
|
}
|
|
208
236
|
|
|
209
|
-
function handleTextMessage(
|
|
210
|
-
cmd: Command,
|
|
211
|
-
clients: Map<number, ClientEnterEvent>,
|
|
212
|
-
): NotificationResult {
|
|
237
|
+
function handleTextMessage(cmd: Command, clients: Map<number, ClientEntry>): NotificationResult {
|
|
213
238
|
const invokerID = asClientID(parseUint16(cmd.params["invokerid"] ?? ""));
|
|
214
239
|
const invokerInfo = clients.get(invokerID);
|
|
215
240
|
|
|
@@ -246,10 +271,7 @@ function invoked(params: Readonly<Record<string, string>>): Invoked {
|
|
|
246
271
|
};
|
|
247
272
|
}
|
|
248
273
|
|
|
249
|
-
function handleClientUpdated(
|
|
250
|
-
cmd: Command,
|
|
251
|
-
clients: Map<number, ClientEnterEvent>,
|
|
252
|
-
): NotificationResult {
|
|
274
|
+
function handleClientUpdated(cmd: Command, clients: Map<number, ClientEntry>): NotificationResult {
|
|
253
275
|
const clid = asClientID(parseUint16(cmd.params["clid"] ?? ""));
|
|
254
276
|
if (clid === 0) return { kind: "unknown" };
|
|
255
277
|
const changes = parseClientChanges(cmd.params);
|
|
@@ -282,7 +304,7 @@ function handleServerGroupMember(
|
|
|
282
304
|
|
|
283
305
|
function handleClientChannelGroupChanged(
|
|
284
306
|
cmd: Command,
|
|
285
|
-
clients: Map<number,
|
|
307
|
+
clients: Map<number, ClientEntry>,
|
|
286
308
|
): NotificationResult {
|
|
287
309
|
const clid = asClientID(parseUint16(cmd.params["clid"] ?? ""));
|
|
288
310
|
if (clid === 0) return { kind: "unknown" };
|
|
@@ -402,3 +424,12 @@ function handleChatComposing(cmd: Command): NotificationResult {
|
|
|
402
424
|
event: { clientID: clid, uid: asUID(cmd.params["cluid"] ?? "") },
|
|
403
425
|
};
|
|
404
426
|
}
|
|
427
|
+
|
|
428
|
+
function handleChatClosed(cmd: Command): NotificationResult {
|
|
429
|
+
const clid = asClientID(parseUint16(cmd.params["clid"] ?? ""));
|
|
430
|
+
if (clid === 0) return { kind: "unknown" };
|
|
431
|
+
return {
|
|
432
|
+
kind: "clientChatClosed",
|
|
433
|
+
event: { clientID: clid, uid: asUID(cmd.params["cluid"] ?? "") },
|
|
434
|
+
};
|
|
435
|
+
}
|
package/src/core/types.ts
CHANGED
|
@@ -148,3 +148,17 @@ export type ClientStatus = (typeof ClientStatus)[keyof typeof ClientStatus];
|
|
|
148
148
|
* default case.
|
|
149
149
|
*/
|
|
150
150
|
export type ServerFamily = "ts3" | "ts6" | "unknown";
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Pick the server generation from a `virtualserver_version` string such as
|
|
154
|
+
* "6.0.0-beta13.1" or "3.13.8 [Build: 1779874471]": major version 3 is TS3,
|
|
155
|
+
* 6 and later TS6, anything else, including a string with no number, unknown.
|
|
156
|
+
* Both clients use it, so they cannot disagree about the same server.
|
|
157
|
+
*/
|
|
158
|
+
export function serverFamilyForVersion(version: string): ServerFamily {
|
|
159
|
+
const major = Number.parseInt(version, 10);
|
|
160
|
+
if (Number.isNaN(major)) return "unknown";
|
|
161
|
+
if (major === 3) return "ts3";
|
|
162
|
+
if (major >= 6) return "ts6";
|
|
163
|
+
return "unknown";
|
|
164
|
+
}
|
package/src/query/client.ts
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
TeamspeakError,
|
|
10
10
|
backoffDelay,
|
|
11
11
|
noopLogger,
|
|
12
|
-
type
|
|
12
|
+
type ClientEntry,
|
|
13
13
|
type CommandExecutor,
|
|
14
14
|
type CommandMiddleware,
|
|
15
15
|
type CommandOptions,
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
type Listener,
|
|
19
19
|
type Logger,
|
|
20
20
|
type ReconnectOptions,
|
|
21
|
+
serverFamilyForVersion,
|
|
21
22
|
type ServerEventMap,
|
|
22
23
|
type ServerFamily,
|
|
23
24
|
type ServerID,
|
|
@@ -33,7 +34,7 @@ import {
|
|
|
33
34
|
parseServerError,
|
|
34
35
|
resolveBackoffPolicy,
|
|
35
36
|
} from "../core/internal.js";
|
|
36
|
-
import type { QueryTransport } from "./transport.js";
|
|
37
|
+
import type { QueryTransport, QueryTransportStats } from "./transport.js";
|
|
37
38
|
import { connectTcp } from "./tcp.js";
|
|
38
39
|
import { connectSsh } from "./ssh.js";
|
|
39
40
|
import { WebQueryTransport } from "./web.js";
|
|
@@ -154,7 +155,7 @@ export class QueryClient implements CommandExecutor, AsyncDisposable {
|
|
|
154
155
|
* 0 until it is known.
|
|
155
156
|
*/
|
|
156
157
|
#clid = 0;
|
|
157
|
-
#clients = new Map<number,
|
|
158
|
+
#clients = new Map<number, ClientEntry>();
|
|
158
159
|
|
|
159
160
|
#reconnectOptions: ReconnectOptions | undefined;
|
|
160
161
|
#reconnecting = false;
|
|
@@ -231,12 +232,32 @@ export class QueryClient implements CommandExecutor, AsyncDisposable {
|
|
|
231
232
|
return this.#version;
|
|
232
233
|
}
|
|
233
234
|
|
|
235
|
+
/**
|
|
236
|
+
* What the current connection reports about itself: its kind, where it
|
|
237
|
+
* points, when it opened and how many bytes went each way, for a metrics
|
|
238
|
+
* exporter. Read-only. A client with no connection, and a `custom` transport
|
|
239
|
+
* that keeps no counts, report no kind, an empty address, no open time and
|
|
240
|
+
* zero bytes. A reconnection starts the counts again.
|
|
241
|
+
*
|
|
242
|
+
* @experimental Part of the entry, which may change in a minor release.
|
|
243
|
+
*/
|
|
244
|
+
get stats(): QueryTransportStats {
|
|
245
|
+
return (
|
|
246
|
+
this.#transport?.stats?.() ?? {
|
|
247
|
+
connectedAt: undefined,
|
|
248
|
+
transport: undefined,
|
|
249
|
+
address: "",
|
|
250
|
+
bytesSent: 0,
|
|
251
|
+
bytesReceived: 0,
|
|
252
|
+
}
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
234
256
|
/**
|
|
235
257
|
* The transport carrying this connection, or `undefined` when there is none.
|
|
236
258
|
*
|
|
237
259
|
* Read-only, and the transport is the interface the client itself talks
|
|
238
|
-
* over
|
|
239
|
-
* can be read from it for metrics. The socket underneath is not exposed:
|
|
260
|
+
* over. The socket underneath is not exposed:
|
|
240
261
|
* a caller that needs it supplies the seam instead (the `connect` option of
|
|
241
262
|
* a `tcp` transport, or `connection` of an `ssh` one), where it is theirs to
|
|
242
263
|
* keep.
|
|
@@ -384,7 +405,7 @@ export class QueryClient implements CommandExecutor, AsyncDisposable {
|
|
|
384
405
|
// Not `use()`: this session learns who it is, and who is there, once, below.
|
|
385
406
|
if (server !== undefined) await this.#select(server, { nickname, signal });
|
|
386
407
|
this.#version = await this.#getVersion(signal);
|
|
387
|
-
this.#serverFamily =
|
|
408
|
+
this.#serverFamily = serverFamilyForVersion(this.#version);
|
|
388
409
|
// Like the version, a failure here fails the connect: a client that cannot
|
|
389
410
|
// tell which one it is would miss its own kick without saying so.
|
|
390
411
|
await this.#learnSelf(signal);
|
|
@@ -1020,6 +1041,7 @@ export class QueryClient implements CommandExecutor, AsyncDisposable {
|
|
|
1020
1041
|
case "channelUnsubscribed":
|
|
1021
1042
|
case "pluginCommand":
|
|
1022
1043
|
case "clientChatComposing":
|
|
1044
|
+
case "clientChatClosed":
|
|
1023
1045
|
// Neither a TeamSpeak 3 ServerQuery connection nor a TeamSpeak 6 one over
|
|
1024
1046
|
// SSH is sent these (measured), so a query client does not offer them.
|
|
1025
1047
|
// Should a server send one, the clients it knows are still kept current
|
|
@@ -1269,15 +1291,6 @@ function serverIDOf(server: ResolvedQueryOptions["server"]): ServerID | number |
|
|
|
1269
1291
|
return server !== undefined && "id" in server ? server.id : undefined;
|
|
1270
1292
|
}
|
|
1271
1293
|
|
|
1272
|
-
/** Which server generation a `version` string names. */
|
|
1273
|
-
function serverFamilyOf(version: string): ServerFamily {
|
|
1274
|
-
const major = Number.parseInt(version, 10);
|
|
1275
|
-
if (Number.isNaN(major)) return "unknown";
|
|
1276
|
-
if (major === 3) return "ts3";
|
|
1277
|
-
if (major >= 6) return "ts6";
|
|
1278
|
-
return "unknown";
|
|
1279
|
-
}
|
|
1280
|
-
|
|
1281
1294
|
/** Sleep, or wake early once `signal` aborts. Resolves either way, never rejects. */
|
|
1282
1295
|
function sleep(ms: number, signal: AbortSignal): Promise<void> {
|
|
1283
1296
|
if (ms <= 0 || signal.aborted) return Promise.resolve();
|
package/src/query/index.ts
CHANGED
|
@@ -31,7 +31,7 @@ export type {
|
|
|
31
31
|
QueryTransportOptions,
|
|
32
32
|
} from "./client.js";
|
|
33
33
|
|
|
34
|
-
export type { QueryTransport } from "./transport.js";
|
|
34
|
+
export type { QueryTransport, QueryTransportStats } from "./transport.js";
|
|
35
35
|
|
|
36
36
|
// Reconnection: the client's default retry decision, and the shared options.
|
|
37
37
|
export { defaultShouldRetry } from "./reconnect.js";
|
package/src/query/options.ts
CHANGED
|
@@ -46,9 +46,17 @@ export type QueryTransportOptions =
|
|
|
46
46
|
| ({
|
|
47
47
|
/** WebQuery, the HTTP interface of TeamSpeak 3 (3.12 and later) and TeamSpeak 6. */
|
|
48
48
|
readonly kind: "web";
|
|
49
|
-
/**
|
|
49
|
+
/**
|
|
50
|
+
* The server's base URL, such as `http://127.0.0.1:10080` or
|
|
51
|
+
* `https://ts.example.com:10443`.
|
|
52
|
+
*/
|
|
50
53
|
readonly url: string;
|
|
51
|
-
/**
|
|
54
|
+
/**
|
|
55
|
+
* The `fetch` to use; by default a small one over `node:http` or
|
|
56
|
+
* `node:https` by the URL's scheme, verifying an HTTPS certificate
|
|
57
|
+
* against Node's trust store. For a private CA, pass a `fetch` that
|
|
58
|
+
* trusts it, or start Node with `NODE_EXTRA_CA_CERTS`.
|
|
59
|
+
*/
|
|
52
60
|
readonly fetch?: typeof fetch | undefined;
|
|
53
61
|
} & WebCredentials)
|
|
54
62
|
| {
|
|
@@ -67,7 +75,10 @@ export type QueryTransportOptions =
|
|
|
67
75
|
* `servernotifyregister`.
|
|
68
76
|
*
|
|
69
77
|
* `bans` is not registered by default: TeamSpeak 3 refuses it with 1539,
|
|
70
|
-
* which would fail the registration of every category after it.
|
|
78
|
+
* which would fail the registration of every category after it. `tokenused`
|
|
79
|
+
* is not registered by default either: both servers accept it (measured) and
|
|
80
|
+
* then send `notifytokenused`, which carries a privilege key and has no typed
|
|
81
|
+
* event, so it reaches `notification` only.
|
|
71
82
|
*/
|
|
72
83
|
export type QueryEventCategory =
|
|
73
84
|
| "server"
|
|
@@ -75,7 +86,8 @@ export type QueryEventCategory =
|
|
|
75
86
|
| "textserver"
|
|
76
87
|
| "textchannel"
|
|
77
88
|
| "textprivate"
|
|
78
|
-
| "bans"
|
|
89
|
+
| "bans"
|
|
90
|
+
| "tokenused";
|
|
79
91
|
|
|
80
92
|
/**
|
|
81
93
|
* Which notification categories to register for.
|
|
@@ -95,7 +107,8 @@ export type QueryEventCategory =
|
|
|
95
107
|
* | `server` | `serverEdited` |
|
|
96
108
|
* | `channel` (registered for every channel) | `channelCreated`, `channelEdited`, `channelMoved`, `channelDeleted`, `channelDescriptionChanged`, `channelPasswordChanged`, `clientEnter`, `clientLeave`, `clientMoved` |
|
|
97
109
|
* | `textserver`, `textchannel`, `textprivate` | `textMessage`, for the server, the channel and private messages |
|
|
98
|
-
* | `bans` |
|
|
110
|
+
* | `bans` | `banUpdated` |
|
|
111
|
+
* | `tokenused` | none (`notification` only) |
|
|
99
112
|
*
|
|
100
113
|
* Leaving a category out leaves its events out: `{ categories: ["textprivate"] }`
|
|
101
114
|
* delivers no channel event. The voice client's events about clients' updates,
|
|
@@ -158,6 +171,7 @@ const CATEGORIES: ReadonlySet<string> = new Set([
|
|
|
158
171
|
"textchannel",
|
|
159
172
|
"textprivate",
|
|
160
173
|
"bans",
|
|
174
|
+
"tokenused",
|
|
161
175
|
]);
|
|
162
176
|
|
|
163
177
|
/** A transport with its defaults filled, and its fields copied. */
|
package/src/query/ssh.ts
CHANGED
|
@@ -538,7 +538,12 @@ export async function connectSsh(options: SshOptions): Promise<StreamTransport>
|
|
|
538
538
|
// stream closes with it.
|
|
539
539
|
ssh.on("error", (failure) => channel.emit("error", failure));
|
|
540
540
|
channel.once("close", () => ssh.end());
|
|
541
|
-
resolve(
|
|
541
|
+
resolve(
|
|
542
|
+
new StreamTransport(channel, () => ssh.end(), {
|
|
543
|
+
transport: "ssh",
|
|
544
|
+
address: `${login === undefined ? "" : `${login.username}@`}${host}:${port}`,
|
|
545
|
+
}),
|
|
546
|
+
);
|
|
542
547
|
});
|
|
543
548
|
};
|
|
544
549
|
|
package/src/query/stream.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { LineReader } from "./lines.js";
|
|
2
|
-
import type { QueryTransport } from "./transport.js";
|
|
2
|
+
import type { QueryTransport, QueryTransportStats } from "./transport.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* The part of a duplex byte stream a line transport uses. A `node:net` socket
|
|
@@ -43,14 +43,24 @@ export class StreamTransport implements QueryTransport {
|
|
|
43
43
|
/** Wakes a `close()` that is waiting for the server to end the session. */
|
|
44
44
|
#onEnded: (() => void) | undefined;
|
|
45
45
|
#error: Error | undefined;
|
|
46
|
+
#identity: { readonly transport: "tcp" | "ssh"; readonly address: string } | undefined;
|
|
47
|
+
#openedAt = new Date();
|
|
48
|
+
#bytesSent = 0;
|
|
49
|
+
#bytesReceived = 0;
|
|
46
50
|
|
|
47
51
|
/**
|
|
48
52
|
* `onEnd` runs once the stream is closed, to release what the stream sits
|
|
49
|
-
* on: an SSH session's connection, say.
|
|
53
|
+
* on: an SSH session's connection, say. `identity` says which kind of
|
|
54
|
+
* connection this is and where it points, for {@link StreamTransport.stats}.
|
|
50
55
|
*/
|
|
51
|
-
constructor(
|
|
56
|
+
constructor(
|
|
57
|
+
stream: QueryStream,
|
|
58
|
+
onEnd?: () => void,
|
|
59
|
+
identity?: { readonly transport: "tcp" | "ssh"; readonly address: string },
|
|
60
|
+
) {
|
|
52
61
|
this.#stream = stream;
|
|
53
62
|
this.#onEnd = onEnd;
|
|
63
|
+
this.#identity = identity;
|
|
54
64
|
stream.on("data", (chunk) => this.#handleData(chunk));
|
|
55
65
|
stream.on("error", (error) => {
|
|
56
66
|
this.#error = error;
|
|
@@ -61,10 +71,26 @@ export class StreamTransport implements QueryTransport {
|
|
|
61
71
|
send(line: string): Promise<void> {
|
|
62
72
|
if (this.#ended) return Promise.reject(new Error("the query connection is closed"));
|
|
63
73
|
return new Promise((resolve, reject) => {
|
|
64
|
-
this.#stream.write(`${line}\n`, (error) =>
|
|
74
|
+
this.#stream.write(`${line}\n`, (error) => {
|
|
75
|
+
if (error) reject(error);
|
|
76
|
+
else {
|
|
77
|
+
this.#bytesSent += Buffer.byteLength(line) + 1;
|
|
78
|
+
resolve();
|
|
79
|
+
}
|
|
80
|
+
});
|
|
65
81
|
});
|
|
66
82
|
}
|
|
67
83
|
|
|
84
|
+
stats(): QueryTransportStats {
|
|
85
|
+
return {
|
|
86
|
+
connectedAt: this.#openedAt,
|
|
87
|
+
transport: this.#identity?.transport,
|
|
88
|
+
address: this.#identity?.address ?? "",
|
|
89
|
+
bytesSent: this.#bytesSent,
|
|
90
|
+
bytesReceived: this.#bytesReceived,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
68
94
|
/**
|
|
69
95
|
* Say `quit`, give the server a moment to end the session, and close.
|
|
70
96
|
*
|
|
@@ -103,6 +129,7 @@ export class StreamTransport implements QueryTransport {
|
|
|
103
129
|
}
|
|
104
130
|
|
|
105
131
|
#handleData(chunk: Uint8Array): void {
|
|
132
|
+
this.#bytesReceived += chunk.byteLength;
|
|
106
133
|
for (const line of this.#reader.push(chunk)) {
|
|
107
134
|
if (this.#banner > 0) {
|
|
108
135
|
this.#banner--;
|
package/src/query/tcp.ts
CHANGED
|
@@ -43,7 +43,7 @@ export async function connectTcp(options: TcpOptions): Promise<StreamTransport>
|
|
|
43
43
|
signal?.throwIfAborted();
|
|
44
44
|
const stream = await connect(host, port);
|
|
45
45
|
await opened(stream, signal);
|
|
46
|
-
return new StreamTransport(stream);
|
|
46
|
+
return new StreamTransport(stream, undefined, { transport: "tcp", address: `${host}:${port}` });
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
/** A connected `node:net` socket, with whole lines sent as they are written. */
|