@honeybbq/teamspeak 0.0.1 → 1.0.0-beta.7
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/LICENSE +21 -0
- package/README.md +726 -2
- package/dist/client/abort.d.ts +5 -0
- package/dist/client/abort.d.ts.map +1 -0
- package/dist/client/abort.js +23 -0
- package/dist/client/abort.js.map +1 -0
- package/dist/client/address.d.ts +17 -0
- package/dist/client/address.d.ts.map +1 -0
- package/dist/client/address.js +66 -0
- package/dist/client/address.js.map +1 -0
- package/dist/client/api/voice.d.ts +24 -0
- package/dist/client/api/voice.d.ts.map +1 -0
- package/dist/client/api/voice.js +33 -0
- package/dist/client/api/voice.js.map +1 -0
- package/dist/client/capabilities.d.ts +68 -0
- package/dist/client/capabilities.d.ts.map +1 -0
- package/dist/client/capabilities.js +59 -0
- package/dist/client/capabilities.js.map +1 -0
- package/dist/client/client.d.ts +196 -0
- package/dist/client/client.d.ts.map +1 -0
- package/dist/client/client.js +1523 -0
- package/dist/client/client.js.map +1 -0
- package/dist/client/commands.d.ts +66 -0
- package/dist/client/commands.d.ts.map +1 -0
- package/dist/client/commands.js +162 -0
- package/dist/client/commands.js.map +1 -0
- package/dist/client/crypto/crypt-ops.d.ts +7 -0
- package/dist/client/crypto/crypt-ops.d.ts.map +1 -0
- package/dist/client/crypto/crypt-ops.js +2 -0
- package/dist/client/crypto/crypt-ops.js.map +1 -0
- package/dist/client/crypto/crypt.d.ts +37 -0
- package/dist/client/crypto/crypt.d.ts.map +1 -0
- package/dist/client/crypto/crypt.js +203 -0
- package/dist/client/crypto/crypt.js.map +1 -0
- package/dist/client/crypto/eax.d.ts +12 -0
- package/dist/client/crypto/eax.d.ts.map +1 -0
- package/dist/client/crypto/eax.js +118 -0
- package/dist/client/crypto/eax.js.map +1 -0
- package/dist/client/crypto/identity.d.ts +97 -0
- package/dist/client/crypto/identity.d.ts.map +1 -0
- package/dist/client/crypto/identity.js +412 -0
- package/dist/client/crypto/identity.js.map +1 -0
- package/dist/client/crypto/index.d.ts +7 -0
- package/dist/client/crypto/index.d.ts.map +1 -0
- package/dist/client/crypto/index.js +5 -0
- package/dist/client/crypto/index.js.map +1 -0
- package/dist/client/crypto/primitives.d.ts +43 -0
- package/dist/client/crypto/primitives.d.ts.map +1 -0
- package/dist/client/crypto/primitives.js +116 -0
- package/dist/client/crypto/primitives.js.map +1 -0
- package/dist/client/diagnostics.d.ts +45 -0
- package/dist/client/diagnostics.d.ts.map +1 -0
- package/dist/client/diagnostics.js +30 -0
- package/dist/client/diagnostics.js.map +1 -0
- package/dist/client/discovery/index.d.ts +2 -0
- package/dist/client/discovery/index.d.ts.map +1 -0
- package/dist/client/discovery/index.js +2 -0
- package/dist/client/discovery/index.js.map +1 -0
- package/dist/client/discovery/resolver.d.ts +26 -0
- package/dist/client/discovery/resolver.d.ts.map +1 -0
- package/dist/client/discovery/resolver.js +275 -0
- package/dist/client/discovery/resolver.js.map +1 -0
- package/dist/client/errors.d.ts +82 -0
- package/dist/client/errors.d.ts.map +1 -0
- package/dist/client/errors.js +201 -0
- package/dist/client/errors.js.map +1 -0
- package/dist/client/events.d.ts +3 -0
- package/dist/client/events.d.ts.map +1 -0
- package/dist/client/events.js +8 -0
- package/dist/client/events.js.map +1 -0
- package/dist/client/handshake/crypt-handshake.d.ts +17 -0
- package/dist/client/handshake/crypt-handshake.d.ts.map +1 -0
- package/dist/client/handshake/crypt-handshake.js +128 -0
- package/dist/client/handshake/crypt-handshake.js.map +1 -0
- package/dist/client/handshake/crypt-init2.d.ts +7 -0
- package/dist/client/handshake/crypt-init2.d.ts.map +1 -0
- package/dist/client/handshake/crypt-init2.js +37 -0
- package/dist/client/handshake/crypt-init2.js.map +1 -0
- package/dist/client/handshake/index.d.ts +4 -0
- package/dist/client/handshake/index.d.ts.map +1 -0
- package/dist/client/handshake/index.js +4 -0
- package/dist/client/handshake/index.js.map +1 -0
- package/dist/client/handshake/license.d.ts +35 -0
- package/dist/client/handshake/license.d.ts.map +1 -0
- package/dist/client/handshake/license.js +169 -0
- package/dist/client/handshake/license.js.map +1 -0
- package/dist/client/handshake.d.ts +27 -0
- package/dist/client/handshake.d.ts.map +1 -0
- package/dist/client/handshake.js +126 -0
- package/dist/client/handshake.js.map +1 -0
- package/dist/client/helpers.d.ts +6 -0
- package/dist/client/helpers.d.ts.map +1 -0
- package/dist/client/helpers.js +13 -0
- package/dist/client/helpers.js.map +1 -0
- package/dist/client/hooks.d.ts +7 -0
- package/dist/client/hooks.d.ts.map +1 -0
- package/dist/client/hooks.js +28 -0
- package/dist/client/hooks.js.map +1 -0
- package/dist/client/index.d.ts +26 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +28 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/reconnect.d.ts +12 -0
- package/dist/client/reconnect.d.ts.map +1 -0
- package/dist/client/reconnect.js +44 -0
- package/dist/client/reconnect.js.map +1 -0
- package/dist/client/throttle.d.ts +20 -0
- package/dist/client/throttle.d.ts.map +1 -0
- package/dist/client/throttle.js +83 -0
- package/dist/client/throttle.js.map +1 -0
- package/dist/client/trace.d.ts +223 -0
- package/dist/client/trace.d.ts.map +1 -0
- package/dist/client/trace.js +13 -0
- package/dist/client/trace.js.map +1 -0
- package/dist/client/transfer.d.ts +111 -0
- package/dist/client/transfer.d.ts.map +1 -0
- package/dist/client/transfer.js +243 -0
- package/dist/client/transfer.js.map +1 -0
- package/dist/client/transport/generation-window.d.ts +30 -0
- package/dist/client/transport/generation-window.d.ts.map +1 -0
- package/dist/client/transport/generation-window.js +97 -0
- package/dist/client/transport/generation-window.js.map +1 -0
- package/dist/client/transport/handler.d.ts +61 -0
- package/dist/client/transport/handler.d.ts.map +1 -0
- package/dist/client/transport/handler.js +793 -0
- package/dist/client/transport/handler.js.map +1 -0
- package/dist/client/transport/index.d.ts +6 -0
- package/dist/client/transport/index.d.ts.map +1 -0
- package/dist/client/transport/index.js +5 -0
- package/dist/client/transport/index.js.map +1 -0
- package/dist/client/transport/packet.d.ts +36 -0
- package/dist/client/transport/packet.d.ts.map +1 -0
- package/dist/client/transport/packet.js +55 -0
- package/dist/client/transport/packet.js.map +1 -0
- package/dist/client/transport/quicklz.d.ts +24 -0
- package/dist/client/transport/quicklz.d.ts.map +1 -0
- package/dist/client/transport/quicklz.js +179 -0
- package/dist/client/transport/quicklz.js.map +1 -0
- package/dist/client/types.d.ts +226 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client/types.js +39 -0
- package/dist/client/types.js.map +1 -0
- package/dist/core/api/channels.d.ts +182 -0
- package/dist/core/api/channels.d.ts.map +1 -0
- package/dist/core/api/channels.js +155 -0
- package/dist/core/api/channels.js.map +1 -0
- package/dist/core/api/clients.d.ts +304 -0
- package/dist/core/api/clients.d.ts.map +1 -0
- package/dist/core/api/clients.js +260 -0
- package/dist/core/api/clients.js.map +1 -0
- package/dist/core/api/command.d.ts +305 -0
- package/dist/core/api/command.d.ts.map +1 -0
- package/dist/core/api/command.js +363 -0
- package/dist/core/api/command.js.map +1 -0
- package/dist/core/api/files.d.ts +153 -0
- package/dist/core/api/files.d.ts.map +1 -0
- package/dist/core/api/files.js +181 -0
- package/dist/core/api/files.js.map +1 -0
- package/dist/core/api/groups.d.ts +248 -0
- package/dist/core/api/groups.d.ts.map +1 -0
- package/dist/core/api/groups.js +271 -0
- package/dist/core/api/groups.js.map +1 -0
- package/dist/core/api/moderation.d.ts +205 -0
- package/dist/core/api/moderation.d.ts.map +1 -0
- package/dist/core/api/moderation.js +209 -0
- package/dist/core/api/moderation.js.map +1 -0
- package/dist/core/api/parse.d.ts +16 -0
- package/dist/core/api/parse.d.ts.map +1 -0
- package/dist/core/api/parse.js +40 -0
- package/dist/core/api/parse.js.map +1 -0
- package/dist/core/api/server.d.ts +310 -0
- package/dist/core/api/server.d.ts.map +1 -0
- package/dist/core/api/server.js +290 -0
- package/dist/core/api/server.js.map +1 -0
- package/dist/core/command/command.d.ts +20 -0
- package/dist/core/command/command.d.ts.map +1 -0
- package/dist/core/command/command.js +112 -0
- package/dist/core/command/command.js.map +1 -0
- package/dist/core/command/ids.d.ts +125 -0
- package/dist/core/command/ids.d.ts.map +1 -0
- package/dist/core/command/ids.js +85 -0
- package/dist/core/command/ids.js.map +1 -0
- package/dist/core/command/parser.d.ts +17 -0
- package/dist/core/command/parser.d.ts.map +1 -0
- package/dist/core/command/parser.js +65 -0
- package/dist/core/command/parser.js.map +1 -0
- package/dist/core/command/request.d.ts +61 -0
- package/dist/core/command/request.d.ts.map +1 -0
- package/dist/core/command/request.js +64 -0
- package/dist/core/command/request.js.map +1 -0
- package/dist/core/command/responses.d.ts +59 -0
- package/dist/core/command/responses.d.ts.map +1 -0
- package/dist/core/command/responses.js +74 -0
- package/dist/core/command/responses.js.map +1 -0
- package/dist/core/command-name.d.ts +333 -0
- package/dist/core/command-name.d.ts.map +1 -0
- package/dist/core/command-name.js +331 -0
- package/dist/core/command-name.js.map +1 -0
- package/dist/core/enums.d.ts +146 -0
- package/dist/core/enums.d.ts.map +1 -0
- package/dist/core/enums.js +132 -0
- package/dist/core/enums.js.map +1 -0
- package/dist/core/errorcode.d.ts +413 -0
- package/dist/core/errorcode.d.ts.map +1 -0
- package/dist/core/errorcode.js +410 -0
- package/dist/core/errorcode.js.map +1 -0
- package/dist/core/errors.d.ts +111 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +233 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/events.d.ts +207 -0
- package/dist/core/events.d.ts.map +1 -0
- package/dist/core/events.js +6 -0
- package/dist/core/events.js.map +1 -0
- package/dist/core/helpers.d.ts +42 -0
- package/dist/core/helpers.d.ts.map +1 -0
- package/dist/core/helpers.js +156 -0
- package/dist/core/helpers.js.map +1 -0
- package/dist/core/index.d.ts +35 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +34 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/internal.d.ts +23 -0
- package/dist/core/internal.d.ts.map +1 -0
- package/dist/core/internal.js +20 -0
- package/dist/core/internal.js.map +1 -0
- package/dist/core/middleware.d.ts +14 -0
- package/dist/core/middleware.d.ts.map +1 -0
- package/dist/core/middleware.js +18 -0
- package/dist/core/middleware.js.map +1 -0
- package/dist/core/notification-name.d.ts +154 -0
- package/dist/core/notification-name.d.ts.map +1 -0
- package/dist/core/notification-name.js +152 -0
- package/dist/core/notification-name.js.map +1 -0
- package/dist/core/notifications.d.ts +43 -0
- package/dist/core/notifications.d.ts.map +1 -0
- package/dist/core/notifications.js +116 -0
- package/dist/core/notifications.js.map +1 -0
- package/dist/core/permission-name.d.ts +533 -0
- package/dist/core/permission-name.d.ts.map +1 -0
- package/dist/core/permission-name.js +531 -0
- package/dist/core/permission-name.js.map +1 -0
- package/dist/core/permission.d.ts +31 -0
- package/dist/core/permission.d.ts.map +1 -0
- package/dist/core/permission.js +20 -0
- package/dist/core/permission.js.map +1 -0
- package/dist/core/property-name.d.ts +403 -0
- package/dist/core/property-name.d.ts.map +1 -0
- package/dist/core/property-name.js +405 -0
- package/dist/core/property-name.js.map +1 -0
- package/dist/core/reason.d.ts +39 -0
- package/dist/core/reason.d.ts.map +1 -0
- package/dist/core/reason.js +36 -0
- package/dist/core/reason.js.map +1 -0
- package/dist/core/reconnect.d.ts +59 -0
- package/dist/core/reconnect.d.ts.map +1 -0
- package/dist/core/reconnect.js +55 -0
- package/dist/core/reconnect.js.map +1 -0
- package/dist/core/types.d.ts +110 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +27 -0
- package/dist/core/types.js.map +1 -0
- package/dist/query/client.d.ts +194 -0
- package/dist/query/client.d.ts.map +1 -0
- package/dist/query/client.js +967 -0
- package/dist/query/client.js.map +1 -0
- package/dist/query/errors.d.ts +61 -0
- package/dist/query/errors.d.ts.map +1 -0
- package/dist/query/errors.js +100 -0
- package/dist/query/errors.js.map +1 -0
- package/dist/query/index.d.ts +36 -0
- package/dist/query/index.d.ts.map +1 -0
- package/dist/query/index.js +34 -0
- package/dist/query/index.js.map +1 -0
- package/dist/query/knownhosts.d.ts +61 -0
- package/dist/query/knownhosts.d.ts.map +1 -0
- package/dist/query/knownhosts.js +223 -0
- package/dist/query/knownhosts.js.map +1 -0
- package/dist/query/lines.d.ts +14 -0
- package/dist/query/lines.d.ts.map +1 -0
- package/dist/query/lines.js +29 -0
- package/dist/query/lines.js.map +1 -0
- package/dist/query/options.d.ts +187 -0
- package/dist/query/options.d.ts.map +1 -0
- package/dist/query/options.js +213 -0
- package/dist/query/options.js.map +1 -0
- package/dist/query/reconnect.d.ts +29 -0
- package/dist/query/reconnect.d.ts.map +1 -0
- package/dist/query/reconnect.js +87 -0
- package/dist/query/reconnect.js.map +1 -0
- package/dist/query/ssh.d.ts +345 -0
- package/dist/query/ssh.d.ts.map +1 -0
- package/dist/query/ssh.js +241 -0
- package/dist/query/ssh.js.map +1 -0
- package/dist/query/stream.d.ts +38 -0
- package/dist/query/stream.d.ts.map +1 -0
- package/dist/query/stream.js +70 -0
- package/dist/query/stream.js.map +1 -0
- package/dist/query/tcp.d.ts +38 -0
- package/dist/query/tcp.d.ts.map +1 -0
- package/dist/query/tcp.js +53 -0
- package/dist/query/tcp.js.map +1 -0
- package/dist/query/transport.d.ts +36 -0
- package/dist/query/transport.d.ts.map +1 -0
- package/dist/query/transport.js +2 -0
- package/dist/query/transport.js.map +1 -0
- package/dist/query/web.d.ts +54 -0
- package/dist/query/web.d.ts.map +1 -0
- package/dist/query/web.js +146 -0
- package/dist/query/web.js.map +1 -0
- package/dist/versions/index.d.ts +3094 -0
- package/dist/versions/index.d.ts.map +1 -0
- package/dist/versions/index.js +9237 -0
- package/dist/versions/index.js.map +1 -0
- package/package.json +118 -4
- package/src/client/abort.ts +24 -0
- package/src/client/address.ts +74 -0
- package/src/client/api/voice.ts +61 -0
- package/src/client/capabilities.ts +106 -0
- package/src/client/client.ts +1809 -0
- package/src/client/commands.ts +193 -0
- package/src/client/crypto/crypt-ops.ts +6 -0
- package/src/client/crypto/crypt.ts +275 -0
- package/src/client/crypto/eax.ts +152 -0
- package/src/client/crypto/identity.ts +484 -0
- package/src/client/crypto/index.ts +21 -0
- package/src/client/crypto/primitives.ts +141 -0
- package/src/client/diagnostics.ts +58 -0
- package/src/client/discovery/index.ts +1 -0
- package/src/client/discovery/resolver.ts +325 -0
- package/src/client/errors.ts +217 -0
- package/src/client/events.ts +12 -0
- package/src/client/handshake/crypt-handshake.ts +157 -0
- package/src/client/handshake/crypt-init2.ts +48 -0
- package/src/client/handshake/index.ts +3 -0
- package/src/client/handshake/license.ts +208 -0
- package/src/client/handshake.ts +150 -0
- package/src/client/helpers.ts +10 -0
- package/src/client/hooks.ts +30 -0
- package/src/client/index.ts +93 -0
- package/src/client/reconnect.ts +43 -0
- package/src/client/throttle.ts +92 -0
- package/src/client/trace.ts +237 -0
- package/src/client/transfer.ts +436 -0
- package/src/client/transport/generation-window.ts +108 -0
- package/src/client/transport/handler.ts +926 -0
- package/src/client/transport/index.ts +14 -0
- package/src/client/transport/packet.ts +69 -0
- package/src/client/transport/quicklz.ts +209 -0
- package/src/client/types.ts +268 -0
- package/src/core/api/channels.ts +396 -0
- package/src/core/api/clients.ts +654 -0
- package/src/core/api/command.ts +563 -0
- package/src/core/api/files.ts +405 -0
- package/src/core/api/groups.ts +622 -0
- package/src/core/api/moderation.ts +525 -0
- package/src/core/api/parse.ts +52 -0
- package/src/core/api/server.ts +709 -0
- package/src/core/command/command.ts +125 -0
- package/src/core/command/ids.ts +149 -0
- package/src/core/command/parser.ts +68 -0
- package/src/core/command/request.ts +119 -0
- package/src/core/command/responses.ts +114 -0
- package/src/core/command-name.ts +334 -0
- package/src/core/enums.ts +167 -0
- package/src/core/errorcode.ts +412 -0
- package/src/core/errors.ts +258 -0
- package/src/core/events.ts +219 -0
- package/src/core/helpers.ts +161 -0
- package/src/core/index.ts +126 -0
- package/src/core/internal.ts +23 -0
- package/src/core/middleware.ts +24 -0
- package/src/core/notification-name.ts +155 -0
- package/src/core/notifications.ts +157 -0
- package/src/core/permission-name.ts +538 -0
- package/src/core/permission.ts +34 -0
- package/src/core/property-name.ts +411 -0
- package/src/core/reason.ts +38 -0
- package/src/core/reconnect.ts +98 -0
- package/src/core/types.ts +138 -0
- package/src/query/client.ts +1132 -0
- package/src/query/errors.ts +108 -0
- package/src/query/index.ts +65 -0
- package/src/query/knownhosts.ts +257 -0
- package/src/query/lines.ts +28 -0
- package/src/query/options.ts +388 -0
- package/src/query/reconnect.ts +84 -0
- package/src/query/ssh.ts +614 -0
- package/src/query/stream.ts +91 -0
- package/src/query/tcp.ts +85 -0
- package/src/query/transport.ts +35 -0
- package/src/query/web.ts +195 -0
- package/src/versions/index.ts +10774 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { LineReader } from "./lines.js";
|
|
2
|
+
import type { QueryTransport } from "./transport.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The part of a duplex byte stream a line transport uses. A `node:net` socket
|
|
6
|
+
* is one; so is an SSH shell channel, or a test double.
|
|
7
|
+
*/
|
|
8
|
+
export interface QueryStream {
|
|
9
|
+
/** Write bytes to the peer; `callback` runs once they are flushed, with the failure if any. */
|
|
10
|
+
write(data: string, callback: (error?: Error | null) => void): unknown;
|
|
11
|
+
/** Close the stream. */
|
|
12
|
+
end(): unknown;
|
|
13
|
+
/** Listen for each chunk received. */
|
|
14
|
+
on(event: "data", listener: (chunk: Uint8Array) => void): unknown;
|
|
15
|
+
/** Listen for a failure, which ends the connection. */
|
|
16
|
+
on(event: "error", listener: (error: Error) => void): unknown;
|
|
17
|
+
/** Listen for the stream closing. */
|
|
18
|
+
on(event: "close", listener: () => void): unknown;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A {@link QueryTransport} over a byte stream: raw TCP, or an SSH shell.
|
|
23
|
+
*
|
|
24
|
+
* Both greet with a two-line banner, `TS3` and a welcome sentence, before any
|
|
25
|
+
* answer. It is skipped, so the client sees only answers and notifications.
|
|
26
|
+
*/
|
|
27
|
+
export class StreamTransport implements QueryTransport {
|
|
28
|
+
/** A raw TCP connection and an SSH session both carry pushed notifications. */
|
|
29
|
+
readonly supportsNotifications = true;
|
|
30
|
+
onLine: ((line: string) => void) | null = null;
|
|
31
|
+
onClose: ((error: Error | undefined) => void) | null = null;
|
|
32
|
+
|
|
33
|
+
#stream: QueryStream;
|
|
34
|
+
#reader = new LineReader();
|
|
35
|
+
#onEnd: (() => void) | undefined;
|
|
36
|
+
/** How many of the two banner lines are still to come. */
|
|
37
|
+
#banner = 2;
|
|
38
|
+
#closing = false;
|
|
39
|
+
#ended = false;
|
|
40
|
+
#error: Error | undefined;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* `onEnd` runs once the stream is closed, to release what the stream sits
|
|
44
|
+
* on: an SSH session's connection, say.
|
|
45
|
+
*/
|
|
46
|
+
constructor(stream: QueryStream, onEnd?: () => void) {
|
|
47
|
+
this.#stream = stream;
|
|
48
|
+
this.#onEnd = onEnd;
|
|
49
|
+
stream.on("data", (chunk) => this.#handleData(chunk));
|
|
50
|
+
stream.on("error", (error) => {
|
|
51
|
+
this.#error = error;
|
|
52
|
+
});
|
|
53
|
+
stream.on("close", () => this.#handleClose());
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
send(line: string): Promise<void> {
|
|
57
|
+
if (this.#ended) return Promise.reject(new Error("the query connection is closed"));
|
|
58
|
+
return new Promise((resolve, reject) => {
|
|
59
|
+
this.#stream.write(`${line}\n`, (error) => (error ? reject(error) : resolve()));
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
close(): Promise<void> {
|
|
64
|
+
if (this.#ended) return Promise.resolve();
|
|
65
|
+
this.#closing = true;
|
|
66
|
+
// Ending the stream is not enough to hear it close. An SSH session channel
|
|
67
|
+
// is half-open, so `end()` sends EOF and stops there without reporting
|
|
68
|
+
// 'close', and the hook that releases the connection underneath would never
|
|
69
|
+
// run; the close is owed here as well as from the stream's own event.
|
|
70
|
+
this.#stream.end();
|
|
71
|
+
this.#handleClose();
|
|
72
|
+
return Promise.resolve();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
#handleData(chunk: Uint8Array): void {
|
|
76
|
+
for (const line of this.#reader.push(chunk)) {
|
|
77
|
+
if (this.#banner > 0) {
|
|
78
|
+
this.#banner--;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
this.onLine?.(line);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
#handleClose(): void {
|
|
86
|
+
if (this.#ended) return;
|
|
87
|
+
this.#ended = true;
|
|
88
|
+
this.#onEnd?.();
|
|
89
|
+
this.onClose?.(this.#closing ? undefined : this.#error);
|
|
90
|
+
}
|
|
91
|
+
}
|
package/src/query/tcp.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { createConnection } from "node:net";
|
|
2
|
+
import type { Duplex } from "node:stream";
|
|
3
|
+
import { StreamTransport } from "./stream.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Opens the byte stream a raw ServerQuery connection runs over.
|
|
7
|
+
*
|
|
8
|
+
* Returns a `Duplex` rather than a `node:net` socket: naming a concrete
|
|
9
|
+
* platform type here would bind the interface to Node's socket class for good,
|
|
10
|
+
* and a caller supplying a TLS socket, a SOCKS proxy's socket or a test double
|
|
11
|
+
* has no reason to hand back that particular class.
|
|
12
|
+
*/
|
|
13
|
+
export type TcpConnector = (host: string, port: number) => Duplex | Promise<Duplex>;
|
|
14
|
+
|
|
15
|
+
/** Where a raw ServerQuery listens. */
|
|
16
|
+
export interface TcpOptions {
|
|
17
|
+
/** The server's host. */
|
|
18
|
+
readonly host: string;
|
|
19
|
+
/** The raw query port; 10011 by default. */
|
|
20
|
+
readonly port?: number | undefined;
|
|
21
|
+
/** Abandons the connection attempt. */
|
|
22
|
+
readonly signal?: AbortSignal | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* Opens the connection. `net.createConnection` by default.
|
|
25
|
+
*
|
|
26
|
+
* The seam into the transport: supplying one lets an application route the
|
|
27
|
+
* session through a TLS terminator or a SOCKS proxy, or drive it from
|
|
28
|
+
* something that is not a socket at all. It is deliberately narrow, so that
|
|
29
|
+
* a later release can widen what it hands back without breaking an
|
|
30
|
+
* implementation written today.
|
|
31
|
+
*/
|
|
32
|
+
readonly connect?: TcpConnector | undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Open a raw ServerQuery connection (`node:net`, 10011 by default).
|
|
37
|
+
*
|
|
38
|
+
* Resolves once the stream is connected; the server's banner is read and
|
|
39
|
+
* dropped by the transport.
|
|
40
|
+
*/
|
|
41
|
+
export async function connectTcp(options: TcpOptions): Promise<StreamTransport> {
|
|
42
|
+
const { host, port = 10011, signal, connect = defaultConnect } = options;
|
|
43
|
+
signal?.throwIfAborted();
|
|
44
|
+
const stream = await connect(host, port);
|
|
45
|
+
await opened(stream, signal);
|
|
46
|
+
return new StreamTransport(stream);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** A connected `node:net` socket, with whole lines sent as they are written. */
|
|
50
|
+
function defaultConnect(host: string, port: number): Duplex {
|
|
51
|
+
const socket = createConnection({ host, port });
|
|
52
|
+
// Whole lines are sent, and a query's answers are small: no waiting to
|
|
53
|
+
// coalesce.
|
|
54
|
+
socket.setNoDelay(true);
|
|
55
|
+
return socket;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Wait until the stream is connected, if it is not already.
|
|
60
|
+
*
|
|
61
|
+
* A `Duplex` a caller handed back may be open, still opening, or a test double
|
|
62
|
+
* that is neither — `connecting` says which, and a stream without it counts as
|
|
63
|
+
* open, since nothing else could report the moment it becomes usable.
|
|
64
|
+
*/
|
|
65
|
+
function opened(stream: Duplex, signal: AbortSignal | undefined): Promise<void> {
|
|
66
|
+
const connecting = (stream as { readonly connecting?: boolean }).connecting;
|
|
67
|
+
if (connecting !== true) return Promise.resolve();
|
|
68
|
+
return new Promise((resolve, reject) => {
|
|
69
|
+
const failed = (error: Error): void => {
|
|
70
|
+
signal?.removeEventListener("abort", onAbort);
|
|
71
|
+
reject(error);
|
|
72
|
+
};
|
|
73
|
+
const onAbort = (): void => {
|
|
74
|
+
stream.destroy();
|
|
75
|
+
failed(signal!.reason as Error);
|
|
76
|
+
};
|
|
77
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
78
|
+
stream.once("error", failed);
|
|
79
|
+
stream.once("connect", () => {
|
|
80
|
+
stream.off("error", failed);
|
|
81
|
+
signal?.removeEventListener("abort", onAbort);
|
|
82
|
+
resolve();
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The connection a ServerQuery client talks over.
|
|
3
|
+
*
|
|
4
|
+
* ServerQuery is a line protocol on three transports: raw TCP (10011), SSH
|
|
5
|
+
* (10022) and, on TeamSpeak 6, an HTTP WebQuery (10080) that frames each
|
|
6
|
+
* command as a request. The client depends on this interface only, so each
|
|
7
|
+
* transport is a separate piece, and a test or a proxy can supply its own.
|
|
8
|
+
*/
|
|
9
|
+
export interface QueryTransport {
|
|
10
|
+
/**
|
|
11
|
+
* Whether this connection can receive notifications at all.
|
|
12
|
+
*
|
|
13
|
+
* Raw TCP and SSH can: the server pushes `notify…` lines down the same
|
|
14
|
+
* connection whenever it likes. WebQuery cannot — it has no session for the
|
|
15
|
+
* server to push to, and refuses `servernotifyregister` with 5120 — so it
|
|
16
|
+
* reports false.
|
|
17
|
+
*
|
|
18
|
+
* A custom transport declares its own truth here rather than leaving the
|
|
19
|
+
* caller to guess: a client uses it to decide whether to register for
|
|
20
|
+
* notifications at all, and whether a listener for a server event can ever
|
|
21
|
+
* run.
|
|
22
|
+
*/
|
|
23
|
+
readonly supportsNotifications: boolean;
|
|
24
|
+
/** Send one command line; the transport adds the line terminator. */
|
|
25
|
+
send(line: string): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Called with each line the server sends, without its terminator, in the
|
|
28
|
+
* order received.
|
|
29
|
+
*/
|
|
30
|
+
onLine: ((line: string) => void) | null;
|
|
31
|
+
/** Called once when the connection ends, with the cause unless it was `close()`. */
|
|
32
|
+
onClose: ((error: Error | undefined) => void) | null;
|
|
33
|
+
/** End the connection. */
|
|
34
|
+
close(): Promise<void>;
|
|
35
|
+
}
|
package/src/query/web.ts
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { request } from "node:http";
|
|
2
|
+
import { TeamspeakError } from "@honeybbq/teamspeak";
|
|
3
|
+
import { buildCommandOrdered, parseCommand, splitCommandRows } from "../core/internal.js";
|
|
4
|
+
import type { QueryTransport } from "./transport.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A WebQuery request that failed with an HTTP status and no TeamSpeak error in
|
|
8
|
+
* its answer: a wrong or missing api key, a server that is not a WebQuery. A
|
|
9
|
+
* refusal from the server itself carries a TeamSpeak code and is a
|
|
10
|
+
* `ServerError` instead.
|
|
11
|
+
*/
|
|
12
|
+
export class WebQueryHttpError extends TeamspeakError {
|
|
13
|
+
/** {@inheritDoc @honeybbq/teamspeak!TeamspeakError.kind} */
|
|
14
|
+
static override readonly kind: string = "WebQueryHttpError";
|
|
15
|
+
static {
|
|
16
|
+
Object.defineProperty(this.prototype, "name", {
|
|
17
|
+
value: "WebQueryHttpError",
|
|
18
|
+
writable: true,
|
|
19
|
+
configurable: true,
|
|
20
|
+
enumerable: false,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** The HTTP status. */
|
|
25
|
+
readonly status: number;
|
|
26
|
+
|
|
27
|
+
constructor(status: number, statusText: string) {
|
|
28
|
+
super(`WebQuery answered HTTP ${status}${statusText === "" ? "" : ` ${statusText}`}`);
|
|
29
|
+
this.status = status;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** How a WebQuery is reached. */
|
|
34
|
+
export interface WebQueryOptions {
|
|
35
|
+
/** The server's base URL, such as `http://127.0.0.1:10080`. */
|
|
36
|
+
readonly url: string;
|
|
37
|
+
/** The api key, sent as `x-api-key`. */
|
|
38
|
+
readonly apiKey: string;
|
|
39
|
+
/** The virtual server to address; `use` changes it. Unset addresses the instance. */
|
|
40
|
+
readonly serverID?: number | bigint | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* The `fetch` to use. By default a small one over `node:http`: the global
|
|
43
|
+
* `fetch` refuses port 10080, WebQuery's own default, as a port the Fetch
|
|
44
|
+
* standard blocks.
|
|
45
|
+
*/
|
|
46
|
+
readonly fetch?: typeof fetch | undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** The answer WebQuery gives: rows, and the status of the command. */
|
|
50
|
+
interface WebAnswer {
|
|
51
|
+
readonly body?: unknown;
|
|
52
|
+
readonly status?: {
|
|
53
|
+
readonly code?: unknown;
|
|
54
|
+
readonly message?: unknown;
|
|
55
|
+
readonly extra_message?: unknown;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* A {@link QueryTransport} over WebQuery, TeamSpeak 6's HTTP interface.
|
|
61
|
+
*
|
|
62
|
+
* Each command line becomes one request, `POST /<server>/<command>` with the
|
|
63
|
+
* parameters as JSON, and the JSON answer is turned back into what a raw
|
|
64
|
+
* connection would have sent: one line per row, then `error id=… msg=…`. The
|
|
65
|
+
* client above sees one answer shape.
|
|
66
|
+
*
|
|
67
|
+
* WebQuery has no session, so two things differ. The api key replaces `login`,
|
|
68
|
+
* which the server refuses; and `use` is answered here, by changing the server
|
|
69
|
+
* the following requests address, since the server has no `use` either. It
|
|
70
|
+
* also sends no notifications, so there is nothing to register for.
|
|
71
|
+
*/
|
|
72
|
+
export class WebQueryTransport implements QueryTransport {
|
|
73
|
+
/** WebQuery has no session for the server to push a notification to. */
|
|
74
|
+
readonly supportsNotifications = false;
|
|
75
|
+
onLine: ((line: string) => void) | null = null;
|
|
76
|
+
onClose: ((error: Error | undefined) => void) | null = null;
|
|
77
|
+
|
|
78
|
+
#base: string;
|
|
79
|
+
#apiKey: string;
|
|
80
|
+
#prefix: string;
|
|
81
|
+
#fetch: typeof fetch;
|
|
82
|
+
#closed = false;
|
|
83
|
+
|
|
84
|
+
constructor(options: WebQueryOptions) {
|
|
85
|
+
this.#base = options.url.replace(/\/+$/, "");
|
|
86
|
+
this.#apiKey = options.apiKey;
|
|
87
|
+
this.#prefix = options.serverID === undefined ? "" : `/${options.serverID}`;
|
|
88
|
+
this.#fetch = options.fetch ?? httpFetch;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async send(line: string): Promise<void> {
|
|
92
|
+
if (this.#closed) throw new Error("the query connection is closed");
|
|
93
|
+
const rows = splitCommandRows(line)
|
|
94
|
+
.map((row) => parseCommand(row))
|
|
95
|
+
.filter((row) => row !== null);
|
|
96
|
+
const name = rows[0]?.name ?? "";
|
|
97
|
+
|
|
98
|
+
if (name === "use") {
|
|
99
|
+
this.#use(rows[0]!.params);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const params = rows.map((row) => ({ ...row.params }));
|
|
104
|
+
const response = await this.#fetch(`${this.#base}${this.#prefix}/${name}`, {
|
|
105
|
+
method: "POST",
|
|
106
|
+
headers: { "x-api-key": this.#apiKey, "content-type": "application/json" },
|
|
107
|
+
body: JSON.stringify(params.length === 1 ? params[0] : params),
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
let answer: WebAnswer | undefined;
|
|
111
|
+
try {
|
|
112
|
+
answer = (await response.json()) as WebAnswer;
|
|
113
|
+
} catch {
|
|
114
|
+
// Not JSON: nothing the server said about the command.
|
|
115
|
+
}
|
|
116
|
+
const code = answer?.status?.code;
|
|
117
|
+
if (typeof code !== "number") throw new WebQueryHttpError(response.status, response.statusText);
|
|
118
|
+
|
|
119
|
+
const body = answer?.body;
|
|
120
|
+
for (const row of Array.isArray(body) ? body : []) {
|
|
121
|
+
if (typeof row === "object" && row !== null) this.onLine?.(rowLine(row));
|
|
122
|
+
}
|
|
123
|
+
const message = answer?.status?.message;
|
|
124
|
+
const extra = answer?.status?.extra_message;
|
|
125
|
+
const tail = typeof extra === "string" && extra !== "" ? { extra_msg: extra } : {};
|
|
126
|
+
this.onLine?.(
|
|
127
|
+
buildCommandOrdered("error", [
|
|
128
|
+
["id", String(code)],
|
|
129
|
+
["msg", typeof message === "string" ? message : ""],
|
|
130
|
+
...Object.entries(tail),
|
|
131
|
+
]),
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
close(): Promise<void> {
|
|
136
|
+
if (this.#closed) return Promise.resolve();
|
|
137
|
+
this.#closed = true;
|
|
138
|
+
this.onClose?.(undefined);
|
|
139
|
+
return Promise.resolve();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** `use sid=1`, or `use port=9987`, which WebQuery addresses as `/byport/9987`. */
|
|
143
|
+
#use(params: Readonly<Record<string, string>>): void {
|
|
144
|
+
const sid = params["sid"];
|
|
145
|
+
const port = params["port"];
|
|
146
|
+
if (sid !== undefined && /^\d+$/.test(sid)) this.#prefix = `/${sid}`;
|
|
147
|
+
else if (port !== undefined && /^\d+$/.test(port)) this.#prefix = `/byport/${port}`;
|
|
148
|
+
else {
|
|
149
|
+
this.onLine?.(
|
|
150
|
+
buildCommandOrdered("error", [
|
|
151
|
+
["id", "1538"],
|
|
152
|
+
["msg", "invalid parameter"],
|
|
153
|
+
]),
|
|
154
|
+
);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
this.onLine?.("error id=0 msg=ok");
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** One answer row as a line of `key=value` fields; a field that is not text is written as text. */
|
|
162
|
+
function rowLine(row: object): string {
|
|
163
|
+
const fields = Object.entries(row).map(
|
|
164
|
+
([key, value]) => [key, typeof value === "string" ? value : String(value)] as const,
|
|
165
|
+
);
|
|
166
|
+
// buildCommandOrdered writes a name first; a row has none.
|
|
167
|
+
return buildCommandOrdered("", fields).slice(1);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* The part of `fetch` this transport uses, over `node:http`: a POST with a
|
|
172
|
+
* string body, answered with a `Response`. Plain HTTP only, as WebQuery is.
|
|
173
|
+
*/
|
|
174
|
+
const httpFetch = ((url: string, init: RequestInit) =>
|
|
175
|
+
new Promise<Response>((resolve, reject) => {
|
|
176
|
+
const req = request(
|
|
177
|
+
url,
|
|
178
|
+
{ method: init.method, headers: init.headers as Record<string, string> },
|
|
179
|
+
(res) => {
|
|
180
|
+
const chunks: Buffer[] = [];
|
|
181
|
+
res.on("data", (chunk: Buffer) => chunks.push(chunk));
|
|
182
|
+
res.on("error", reject);
|
|
183
|
+
res.on("end", () =>
|
|
184
|
+
resolve(
|
|
185
|
+
new Response(Buffer.concat(chunks), {
|
|
186
|
+
status: res.statusCode ?? 0,
|
|
187
|
+
statusText: res.statusMessage ?? "",
|
|
188
|
+
}),
|
|
189
|
+
),
|
|
190
|
+
);
|
|
191
|
+
},
|
|
192
|
+
);
|
|
193
|
+
req.on("error", reject);
|
|
194
|
+
req.end(init.body as string);
|
|
195
|
+
})) as unknown as typeof fetch;
|