@honeybbq/teamspeak 1.0.0-beta.10 → 1.0.0-beta.12
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 +17 -7
- package/dist/client/api/voice.d.ts +13 -0
- package/dist/client/api/voice.d.ts.map +1 -1
- package/dist/client/api/voice.js +20 -1
- package/dist/client/api/voice.js.map +1 -1
- package/dist/client/client.d.ts +9 -0
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/client.js +105 -12
- package/dist/client/client.js.map +1 -1
- package/dist/client/crypto/crypt.d.ts +25 -4
- package/dist/client/crypto/crypt.d.ts.map +1 -1
- package/dist/client/crypto/crypt.js +41 -9
- package/dist/client/crypto/crypt.js.map +1 -1
- package/dist/client/errors.d.ts +14 -0
- package/dist/client/errors.d.ts.map +1 -1
- package/dist/client/errors.js +28 -0
- package/dist/client/errors.js.map +1 -1
- package/dist/client/handshake/crypt-handshake.d.ts +8 -2
- package/dist/client/handshake/crypt-handshake.d.ts.map +1 -1
- package/dist/client/handshake/crypt-handshake.js +26 -20
- package/dist/client/handshake/crypt-handshake.js.map +1 -1
- package/dist/client/index.d.ts +2 -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/types.d.ts +35 -0
- package/dist/client/types.d.ts.map +1 -1
- package/dist/client/types.js +4 -0
- package/dist/client/types.js.map +1 -1
- package/dist/client/view.d.ts +229 -0
- package/dist/client/view.d.ts.map +1 -0
- package/dist/client/view.js +388 -0
- package/dist/client/view.js.map +1 -0
- package/dist/core/api/clients.d.ts +4 -0
- package/dist/core/api/clients.d.ts.map +1 -1
- package/dist/core/api/clients.js +4 -0
- package/dist/core/api/clients.js.map +1 -1
- package/dist/core/api/group-rows.d.ts +12 -0
- package/dist/core/api/group-rows.d.ts.map +1 -0
- package/dist/core/api/group-rows.js +34 -0
- package/dist/core/api/group-rows.js.map +1 -0
- package/dist/core/api/groups.d.ts.map +1 -1
- package/dist/core/api/groups.js +2 -13
- package/dist/core/api/groups.js.map +1 -1
- package/dist/core/api/server.d.ts.map +1 -1
- package/dist/core/api/server.js +2 -13
- package/dist/core/api/server.js.map +1 -1
- package/dist/core/channel-tree.d.ts +87 -0
- package/dist/core/channel-tree.d.ts.map +1 -0
- package/dist/core/channel-tree.js +378 -0
- package/dist/core/channel-tree.js.map +1 -0
- package/dist/core/command/responses.d.ts +4 -1
- package/dist/core/command/responses.d.ts.map +1 -1
- package/dist/core/command/responses.js +10 -1
- package/dist/core/command/responses.js.map +1 -1
- package/dist/core/errorcode.d.ts +16 -0
- package/dist/core/errorcode.d.ts.map +1 -1
- package/dist/core/errorcode.js +16 -0
- package/dist/core/errorcode.js.map +1 -1
- package/dist/core/events.d.ts +34 -2
- package/dist/core/events.d.ts.map +1 -1
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/internal.d.ts +1 -0
- package/dist/core/internal.d.ts.map +1 -1
- package/dist/core/internal.js +1 -0
- package/dist/core/internal.js.map +1 -1
- package/dist/query/client.d.ts +4 -4
- package/dist/query/client.d.ts.map +1 -1
- package/dist/query/client.js +64 -7
- package/dist/query/client.js.map +1 -1
- package/dist/query/errors.d.ts +21 -0
- package/dist/query/errors.d.ts.map +1 -1
- package/dist/query/errors.js +34 -0
- package/dist/query/errors.js.map +1 -1
- package/dist/query/index.d.ts +3 -2
- package/dist/query/index.d.ts.map +1 -1
- package/dist/query/index.js +3 -2
- package/dist/query/index.js.map +1 -1
- package/dist/query/options.d.ts +20 -15
- package/dist/query/options.d.ts.map +1 -1
- package/dist/query/options.js +20 -16
- package/dist/query/options.js.map +1 -1
- package/dist/query/ssh.d.ts +3 -1
- package/dist/query/ssh.d.ts.map +1 -1
- package/dist/query/ssh.js.map +1 -1
- package/dist/query/stream.d.ts +8 -0
- package/dist/query/stream.d.ts.map +1 -1
- package/dist/query/stream.js +31 -3
- package/dist/query/stream.js.map +1 -1
- package/dist/query/transport.d.ts +4 -3
- package/dist/query/transport.d.ts.map +1 -1
- package/dist/query/web.d.ts +8 -2
- package/dist/query/web.d.ts.map +1 -1
- package/dist/query/web.js +2 -1
- package/dist/query/web.js.map +1 -1
- package/package.json +3 -3
- package/src/client/api/voice.ts +25 -1
- package/src/client/client.ts +119 -12
- package/src/client/crypto/crypt.ts +46 -9
- package/src/client/errors.ts +31 -0
- package/src/client/handshake/crypt-handshake.ts +51 -16
- package/src/client/index.ts +3 -0
- package/src/client/types.ts +35 -0
- package/src/client/view.ts +592 -0
- package/src/core/api/clients.ts +4 -0
- package/src/core/api/group-rows.ts +38 -0
- package/src/core/api/groups.ts +1 -14
- package/src/core/api/server.ts +1 -15
- package/src/core/channel-tree.ts +398 -0
- package/src/core/command/responses.ts +14 -2
- package/src/core/errorcode.ts +16 -0
- package/src/core/events.ts +34 -2
- package/src/core/index.ts +1 -0
- package/src/core/internal.ts +1 -0
- package/src/query/client.ts +66 -7
- package/src/query/errors.ts +37 -0
- package/src/query/index.ts +8 -2
- package/src/query/options.ts +39 -29
- package/src/query/ssh.ts +3 -1
- package/src/query/stream.ts +34 -3
- package/src/query/transport.ts +4 -3
- package/src/query/web.ts +8 -2
package/README.md
CHANGED
|
@@ -537,11 +537,18 @@ the way the voice protocol cannot: the same typed commands the core exports,
|
|
|
537
537
|
plus the ones only an administrator may run, over the three transports a
|
|
538
538
|
ServerQuery speaks.
|
|
539
539
|
|
|
540
|
-
| Transport | Where | Server | Login
|
|
541
|
-
| --------- | ------------ | ---------------- |
|
|
542
|
-
| `tcp` | 10011 | TeamSpeak 3 only |
|
|
543
|
-
| `ssh` | 10022 | both | the SSH login
|
|
544
|
-
| `web` | 10080 (HTTP) |
|
|
540
|
+
| Transport | Where | Server | Login | Notifications |
|
|
541
|
+
| --------- | ------------ | ---------------- | ----------------- | ------------- |
|
|
542
|
+
| `tcp` | 10011 | TeamSpeak 3 only | `transport.login` | yes |
|
|
543
|
+
| `ssh` | 10022 | both | the SSH login | yes |
|
|
544
|
+
| `web` | 10080 (HTTP) | both | the api key | **no** |
|
|
545
|
+
|
|
546
|
+
TeamSpeak 3 (3.12 and later for WebQuery, 3.3 and later for SSH) serves SSH and
|
|
547
|
+
WebQuery as well as raw TCP, but only the raw port is on until
|
|
548
|
+
`query_protocols` lists the others; the official Docker image takes it as
|
|
549
|
+
`TS3SERVER_QUERY_PROTOCOLS=raw,ssh,http`. TeamSpeak 6 has no raw port. Its
|
|
550
|
+
WebQuery also answers a request with no api key as a guest; TeamSpeak 3 refuses
|
|
551
|
+
one.
|
|
545
552
|
|
|
546
553
|
Only raw TCP and SSH carry notifications: the server pushes them down the same
|
|
547
554
|
connection. WebQuery has no session to push to and refuses to register, so the
|
|
@@ -554,8 +561,11 @@ import { QueryClient } from "@honeybbq/teamspeak/query";
|
|
|
554
561
|
import { listChannels } from "@honeybbq/teamspeak";
|
|
555
562
|
|
|
556
563
|
const client = new QueryClient({
|
|
557
|
-
transport: {
|
|
558
|
-
|
|
564
|
+
transport: {
|
|
565
|
+
kind: "tcp", // 10011
|
|
566
|
+
host: "ts.example.com",
|
|
567
|
+
login: { name: "serveradmin", password: process.env.QUERY_PASSWORD! },
|
|
568
|
+
},
|
|
559
569
|
server: { id: 1 },
|
|
560
570
|
nickname: "bot",
|
|
561
571
|
events: { categories: ["textserver", "textprivate"] },
|
|
@@ -14,6 +14,19 @@ import type { ChannelID, ClientID, CommandOptions } from "@honeybbq/teamspeak";
|
|
|
14
14
|
* a `channelEdited` event, since it is no edit.
|
|
15
15
|
*/
|
|
16
16
|
export declare function getChannelDescription(client: Client, cid: ChannelID, options?: CommandOptions): Promise<string>;
|
|
17
|
+
/**
|
|
18
|
+
* The virtual server's properties as the server sends them on request, which
|
|
19
|
+
* is a different set from `getServerInfo`'s: it has the settings, limits and
|
|
20
|
+
* counters (clients online, uptime, traffic) and lacks the identity ones such as
|
|
21
|
+
* the unique identifier. It is the whole row, counters included.
|
|
22
|
+
*
|
|
23
|
+
* The client's `view.server` takes every answer in, whoever asked, less the
|
|
24
|
+
* counters, so this is also how to bring it up to date after an edit the
|
|
25
|
+
* server did not spell out. The answer goes to the asker alone.
|
|
26
|
+
*
|
|
27
|
+
* @experimental New in 1.x and may change in a minor release.
|
|
28
|
+
*/
|
|
29
|
+
export declare function getServerVariables(client: Client, options?: CommandOptions): Promise<Readonly<Record<string, string>>>;
|
|
17
30
|
/**
|
|
18
31
|
* Subscribe to a channel: receive its clients' events and voice without
|
|
19
32
|
* joining it.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"voice.d.ts","sourceRoot":"","sources":["../../../src/client/api/voice.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3C,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE/E;;;;GAIG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,SAAS,EACd,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,MAAM,CAAC,CAUjB;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,SAAS,EACd,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,8DAA8D;AAC9D,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,SAAS,EACd,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,gDAAgD;AAChD,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,oDAAoD;AACpD,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,IAAI,CAAC,CAEf;AAaD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,OAAO,CAAC,CAalB;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,SAAS,EACd,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,OAAO,CAAC,CAgBlB;AAaD;;;;;;;GAOG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,SAAS,QAAQ,EAAE,EAC9B,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,IAAI,CAAC,CAEf;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,SAAS,QAAQ,EAAE,EAC9B,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,IAAI,CAAC,CAEf"}
|
|
1
|
+
{"version":3,"file":"voice.d.ts","sourceRoot":"","sources":["../../../src/client/api/voice.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3C,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE/E;;;;GAIG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,SAAS,EACd,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,MAAM,CAAC,CAUjB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAO3C;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,SAAS,EACd,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,8DAA8D;AAC9D,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,SAAS,EACd,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,gDAAgD;AAChD,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,oDAAoD;AACpD,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,IAAI,CAAC,CAEf;AAaD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,OAAO,CAAC,CAalB;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,SAAS,EACd,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,OAAO,CAAC,CAgBlB;AAaD;;;;;;;GAOG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,SAAS,QAAQ,EAAE,EAC9B,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,IAAI,CAAC,CAEf;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,SAAS,QAAQ,EAAE,EAC9B,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,IAAI,CAAC,CAEf"}
|
package/dist/client/api/voice.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* `channelinfo` already carries the description. These run on the voice
|
|
7
7
|
* client alone, which is why they take one rather than any CommandExecutor.
|
|
8
8
|
*/
|
|
9
|
-
import { ErrorCode, ServerError } from "@honeybbq/teamspeak";
|
|
9
|
+
import { ErrorCode, NoDataError, ServerError } from "@honeybbq/teamspeak";
|
|
10
10
|
/**
|
|
11
11
|
* The channel's description, which `listChannels` omits for bandwidth. The
|
|
12
12
|
* server answers with a `notifychanneledited` that the client does not report as
|
|
@@ -20,6 +20,25 @@ export async function getChannelDescription(client, cid, options) {
|
|
|
20
20
|
const answer = rows.find((row) => row["cid"] === String(cid) && "channel_description" in row);
|
|
21
21
|
return answer?.["channel_description"] ?? "";
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* The virtual server's properties as the server sends them on request, which
|
|
25
|
+
* is a different set from `getServerInfo`'s: it has the settings, limits and
|
|
26
|
+
* counters (clients online, uptime, traffic) and lacks the identity ones such as
|
|
27
|
+
* the unique identifier. It is the whole row, counters included.
|
|
28
|
+
*
|
|
29
|
+
* The client's `view.server` takes every answer in, whoever asked, less the
|
|
30
|
+
* counters, so this is also how to bring it up to date after an edit the
|
|
31
|
+
* server did not spell out. The answer goes to the asker alone.
|
|
32
|
+
*
|
|
33
|
+
* @experimental New in 1.x and may change in a minor release.
|
|
34
|
+
*/
|
|
35
|
+
export async function getServerVariables(client, options) {
|
|
36
|
+
const rows = await client.send({ command: "servergetvariables" }, options);
|
|
37
|
+
const answer = rows.find((row) => Object.keys(row).some((key) => key.startsWith("virtualserver_")));
|
|
38
|
+
if (answer === undefined)
|
|
39
|
+
throw new NoDataError("servergetvariables");
|
|
40
|
+
return answer;
|
|
41
|
+
}
|
|
23
42
|
/**
|
|
24
43
|
* Subscribe to a channel: receive its clients' events and voice without
|
|
25
44
|
* joining it.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"voice.js","sourceRoot":"","sources":["../../../src/client/api/voice.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"voice.js","sourceRoot":"","sources":["../../../src/client/api/voice.ts"],"names":[],"mappings":"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"}
|
package/dist/client/client.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type { ClientVersion, CommandExecutor, CommandRequest } from "@honeybbq/t
|
|
|
6
6
|
import type { FileUploadInfo, FileDownloadInfo } from "@honeybbq/teamspeak";
|
|
7
7
|
import type { Readable, Writable } from "node:stream";
|
|
8
8
|
import { type ServerCapabilities } from "./capabilities.js";
|
|
9
|
+
import { type ServerView } from "./view.js";
|
|
9
10
|
/**
|
|
10
11
|
* What clientinit sends about the connection, with the passwords already in
|
|
11
12
|
* their wire form, `base64(sha1(password))`: hashed once, when the client is
|
|
@@ -32,6 +33,14 @@ export interface ClientInitOptions {
|
|
|
32
33
|
export declare class Client implements CommandExecutor, AsyncDisposable {
|
|
33
34
|
#private;
|
|
34
35
|
constructor(identity: Identity, addr: string, nickname: string, options?: ClientOptions);
|
|
36
|
+
/**
|
|
37
|
+
* What this client knows of the server: the channel tree, the clients it
|
|
38
|
+
* has been told about, and itself. Always there, and `"empty"` until the
|
|
39
|
+
* first `connect()`.
|
|
40
|
+
*
|
|
41
|
+
* @experimental New in 1.x and may change in a minor release.
|
|
42
|
+
*/
|
|
43
|
+
get view(): ServerView;
|
|
35
44
|
/** Where the client is in its connection's life. */
|
|
36
45
|
get status(): ClientStatus;
|
|
37
46
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client/client.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,QAAQ,EACb,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,aAAa,EAOnB,MAAM,YAAY,CAAC;AAIpB,OAAO,EACL,KAAK,SAAS,EAEd,KAAK,QAAQ,EACb,KAAK,KAAK,EAGV,KAAK,cAAc,EACnB,KAAK,GAAG,EAGR,KAAK,QAAQ,EAGb,KAAK,WAAW,EAGhB,YAAY,EAKb,MAAM,qBAAqB,CAAC;AAG7B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAclD,OAAO,EAAoC,KAAK,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAW3F,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client/client.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,QAAQ,EACb,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,aAAa,EAOnB,MAAM,YAAY,CAAC;AAIpB,OAAO,EACL,KAAK,SAAS,EAEd,KAAK,QAAQ,EACb,KAAK,KAAK,EAGV,KAAK,cAAc,EACnB,KAAK,GAAG,EAGR,KAAK,QAAQ,EAGb,KAAK,WAAW,EAGhB,YAAY,EAKb,MAAM,qBAAqB,CAAC;AAG7B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAclD,OAAO,EAAoC,KAAK,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAW3F,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAa1F,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAG5E,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEtD,OAAO,EAA6B,KAAK,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAIvF,OAAO,EAAa,KAAK,UAAU,EAAE,MAAM,WAAW,CAAC;AAEvD;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,0BAA0B,EAAE,MAAM,CAAC;IAC5C,+FAA+F;IAC/F,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,mDAAmD;IACnD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,iGAAiG;IACjG,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;CACjC;AA8ND;;;;;;GAMG;AACH,qBAAa,MAAO,YAAW,eAAe,EAAE,eAAe;;IA2F7D,YAAY,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,EAyE1F;IAED;;;;;;OAMG;IACH,IAAI,IAAI,IAAI,UAAU,CAErB;IAED,oDAAoD;IACpD,IAAI,MAAM,IAAI,YAAY,CAEzB;IAID;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACG,OAAO,CAAC,OAAO,GAAE;QAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAA;KAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAExF;IAoGD;;;;;;;;;;;;;;;;;;OAkBG;IACG,UAAU,CACd,OAAO,GAAE;QACP,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;QAC1C,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;KACxC,GACL,OAAO,CAAC,IAAI,CAAC,CA4Bf;IA+CD,gEAAgE;IAChE,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAErC;IAID;;;;;;;;OAQG;IAEH,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,CAErF;IAED;;;;;;;;OAQG;IACH,UAAU,CACR,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;QAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAA;KAAO,GAC1D,OAAO,CAAC,IAAI,CAAC,CAEf;IAwBD;;;;;;;OAOG;IAEH,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,CAE7F;IAuED;;;;;;;;;;;;;;;;OAgBG;IACH,EAAE,CAAC,CAAC,SAAS,MAAM,QAAQ,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC,GAAG,WAAW,CAmBnF;IAED,uDAAuD;IACvD,IAAI,CAAC,CAAC,SAAS,MAAM,QAAQ,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC,GAAG,WAAW,CAMrF;IAED;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,MAAM,CAAC,CAAC,SAAS,MAAM,QAAQ,EAC7B,KAAK,EAAE,CAAC,EACR,OAAO,GAAE;QAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAA;KAAO,GAC1D,qBAAqB,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CA2EpC;IAID,8EAA8E;IAC9E,IAAI,QAAQ,IAAI,QAAQ,CAEvB;IAED;;;;OAIG;IACH,IAAI,kBAAkB,IAAI,kBAAkB,CAE3C;IAED;;;;;;;OAOG;IACH,IAAI,GAAG,IAAI,GAAG,CAEb;IAED;;;;OAIG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,8EAA8E;IAC9E,IAAI,SAAS,IAAI,SAAS,CAEzB;IAED;;;;;;;OAOG;IACH,WAAW,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,KAAK,GAAG,IAAI,CAKzE;IAED;;;;;;;OAOG;IACH,gBAAgB,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,kBAAkB,EAAE,KAAK,EAAE,KAAK,GAAG,IAAI,CAIjF;IAED;;;OAGG;IACH,SAAS,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,IAAI,CAKtE;IAID;;;OAGG;IACH,cAAc,CACZ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,cAAc,EACpB,IAAI,EAAE,QAAQ,EACd,OAAO,GAAE,IAAI,CAAC,mBAAmB,EAAE,QAAQ,GAAG,OAAO,CAAM,GAC1D,OAAO,CAAC,IAAI,CAAC,CAKf;IAED;;;OAGG;IACH,gBAAgB,CACd,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,gBAAgB,EACtB,IAAI,EAAE,QAAQ,EACd,OAAO,GAAE,IAAI,CAAC,mBAAmB,EAAE,QAAQ,GAAG,OAAO,CAAM,GAC1D,OAAO,CAAC,IAAI,CAAC,CAKf;CAm3BF"}
|
package/dist/client/client.js
CHANGED
|
@@ -17,7 +17,7 @@ import { buildEventChain } from "./events.js";
|
|
|
17
17
|
import { processInit1 } from "./handshake/crypt-handshake.js";
|
|
18
18
|
import { handleHandshakeInitIV, handleHandshakeExpand2, defaultClientVersion, handleInitServer, prepareClientPassword, } from "./handshake.js";
|
|
19
19
|
import { handleNotification } from "../core/internal.js";
|
|
20
|
-
import { parseCommand } from "../core/internal.js";
|
|
20
|
+
import { parseChannel, parseChannelGroup, parseCommand, parseServerGroup, parseUint16, } from "../core/internal.js";
|
|
21
21
|
import { CommandThrottle } from "./throttle.js";
|
|
22
22
|
import { splitCommandRows, sleep } from "../core/internal.js";
|
|
23
23
|
import { isAutoNicknameMatch } from "./helpers.js";
|
|
@@ -27,6 +27,7 @@ import { unknownServerCapabilities } from "./capabilities.js";
|
|
|
27
27
|
import { joinHostPort, parseConnectAddress, splitHostPort } from "./address.js";
|
|
28
28
|
import { commandChannel, incomingChannel } from "./diagnostics.js";
|
|
29
29
|
import { defaultShouldRetry } from "./reconnect.js";
|
|
30
|
+
import { ViewStore } from "./view.js";
|
|
30
31
|
/** The signature is Ed25519, 64 bytes; the server verifies it, so only its shape is checked. */
|
|
31
32
|
function normalizeClientVersion(version) {
|
|
32
33
|
if (version === undefined)
|
|
@@ -213,12 +214,15 @@ export class Client {
|
|
|
213
214
|
#status = ClientStatus.Disconnected;
|
|
214
215
|
#throttle = new CommandThrottle();
|
|
215
216
|
#cmdTrack = new CommandTracker(() => this.#clid);
|
|
216
|
-
#
|
|
217
|
+
#viewStore;
|
|
218
|
+
#clients;
|
|
217
219
|
#attempt = newConnectAttempt();
|
|
218
220
|
#listeners = new Map();
|
|
219
221
|
/** Deliveries waiting for their turn; see #emit. */
|
|
220
222
|
#queue = [];
|
|
221
223
|
#draining = false;
|
|
224
|
+
/** The group list rows of the command line being handled, by the name of the line. */
|
|
225
|
+
#groupLine;
|
|
222
226
|
#voiceDropped = 0;
|
|
223
227
|
#voiceDropWarnedAt = -Infinity;
|
|
224
228
|
#eventMiddlewares;
|
|
@@ -235,6 +239,8 @@ export class Client {
|
|
|
235
239
|
this.#fileTransferSocketFactory = options.fileTransferSocketFactory;
|
|
236
240
|
this.#reconnectOptions = options.reconnect;
|
|
237
241
|
this.#trace = options.trace === undefined ? undefined : { ...options.trace };
|
|
242
|
+
this.#viewStore = new ViewStore(this.#logger, () => this.#clid, () => this.#scheduleDrain());
|
|
243
|
+
this.#clients = this.#viewStore.roster;
|
|
238
244
|
this.#crypt = new Crypt(identity);
|
|
239
245
|
this.#handler = this.#newHandler();
|
|
240
246
|
// oxlint-disable-next-line no-this-alias -- accessors cannot be arrow functions
|
|
@@ -285,6 +291,16 @@ export class Client {
|
|
|
285
291
|
this.#send = buildCommandChain(cmdMiddlewares, (request, commandOptions = {}) => this.#exec(encodeRequest(request, prepareClientPassword), commandOptions, false));
|
|
286
292
|
this.#eventChain = buildEventChain([...this.#eventMiddlewares], (event) => this.#deliver(event.name, event.payload));
|
|
287
293
|
}
|
|
294
|
+
/**
|
|
295
|
+
* What this client knows of the server: the channel tree, the clients it
|
|
296
|
+
* has been told about, and itself. Always there, and `"empty"` until the
|
|
297
|
+
* first `connect()`.
|
|
298
|
+
*
|
|
299
|
+
* @experimental New in 1.x and may change in a minor release.
|
|
300
|
+
*/
|
|
301
|
+
get view() {
|
|
302
|
+
return this.#viewStore.view;
|
|
303
|
+
}
|
|
288
304
|
/** Where the client is in its connection's life. */
|
|
289
305
|
get status() {
|
|
290
306
|
return this.#status;
|
|
@@ -955,6 +971,7 @@ export class Client {
|
|
|
955
971
|
}
|
|
956
972
|
/** Report the end of the session in progress, once. */
|
|
957
973
|
#endSession(error) {
|
|
974
|
+
this.#viewStore.end(error);
|
|
958
975
|
const session = this.#session;
|
|
959
976
|
if (session === undefined)
|
|
960
977
|
return;
|
|
@@ -1004,7 +1021,7 @@ export class Client {
|
|
|
1004
1021
|
this.#handler = this.#newHandler();
|
|
1005
1022
|
this.#attempt = newConnectAttempt();
|
|
1006
1023
|
this.#cmdTrack.reset(new NotConnectedError());
|
|
1007
|
-
this.#
|
|
1024
|
+
this.#viewStore.reset();
|
|
1008
1025
|
this.#clid = 0;
|
|
1009
1026
|
this.#capabilities = unknownServerCapabilities();
|
|
1010
1027
|
}
|
|
@@ -1061,9 +1078,23 @@ export class Client {
|
|
|
1061
1078
|
if (pType === 8 /* Init1 */) {
|
|
1062
1079
|
if (this.#crypt.cryptoInitComplete)
|
|
1063
1080
|
return;
|
|
1064
|
-
const
|
|
1065
|
-
|
|
1066
|
-
|
|
1081
|
+
const signal = this.#attempt.signal;
|
|
1082
|
+
const handler = this.#handler;
|
|
1083
|
+
const response = processInit1(this.#crypt, p.data, signal);
|
|
1084
|
+
if (response instanceof Promise) {
|
|
1085
|
+
// The puzzle: solved in slices, so the loop keeps running meanwhile.
|
|
1086
|
+
response.then((answer) => {
|
|
1087
|
+
if (answer && !signal.aborted)
|
|
1088
|
+
handler.sendPacket(PacketType.Init1, answer, 0);
|
|
1089
|
+
}, (error) => {
|
|
1090
|
+
// An attempt that ended did so for its own reason; the abandoned
|
|
1091
|
+
// solve has nothing more to report.
|
|
1092
|
+
if (!signal.aborted)
|
|
1093
|
+
this.#handleReceiveError(error);
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
else if (response) {
|
|
1097
|
+
handler.sendPacket(PacketType.Init1, response, 0);
|
|
1067
1098
|
}
|
|
1068
1099
|
return;
|
|
1069
1100
|
}
|
|
@@ -1111,6 +1142,23 @@ export class Client {
|
|
|
1111
1142
|
for (const row of splitCommandRows(trimmed)) {
|
|
1112
1143
|
this.#handleCommandStr(row);
|
|
1113
1144
|
}
|
|
1145
|
+
this.#applyGroupLine();
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* A group list is a whole set, and the server sends it as one command line,
|
|
1150
|
+
* so it is applied once its last row has been handled.
|
|
1151
|
+
*/
|
|
1152
|
+
#applyGroupLine() {
|
|
1153
|
+
const line = this.#groupLine;
|
|
1154
|
+
if (line === undefined)
|
|
1155
|
+
return;
|
|
1156
|
+
this.#groupLine = undefined;
|
|
1157
|
+
if (line.name === "notifyservergrouplist") {
|
|
1158
|
+
this.#viewStore.setServerGroups(line.rows.map(parseServerGroup).filter((group) => group.id !== 0n));
|
|
1159
|
+
}
|
|
1160
|
+
else {
|
|
1161
|
+
this.#viewStore.setChannelGroups(line.rows.map(parseChannelGroup).filter((group) => group.id !== 0n));
|
|
1114
1162
|
}
|
|
1115
1163
|
}
|
|
1116
1164
|
#handleCommandStr(s) {
|
|
@@ -1143,14 +1191,24 @@ export class Client {
|
|
|
1143
1191
|
// buffered as if they were the response to the caller's first command,
|
|
1144
1192
|
// which is how a channellist could come back with duplicate and empty rows.
|
|
1145
1193
|
if (cmd.name === "channellistfinished") {
|
|
1194
|
+
// The tree and the clients of the own channel are complete here, so the
|
|
1195
|
+
// view is too; the group lists and permissions the server sends next
|
|
1196
|
+
// are not part of it.
|
|
1197
|
+
const revision = this.#status === ClientStatus.Connected ? this.#viewStore.finish() : undefined;
|
|
1198
|
+
if (revision !== undefined)
|
|
1199
|
+
this.#emit("viewReady", { revision });
|
|
1146
1200
|
this.#cmdTrack.signalWelcomeComplete();
|
|
1147
1201
|
this.#markWelcomeDone();
|
|
1148
1202
|
return;
|
|
1149
1203
|
}
|
|
1150
1204
|
if (cmd.name === "channellist") {
|
|
1151
1205
|
const trace = this.#attempt.trace;
|
|
1152
|
-
if (trace !== undefined && !trace.welcomeDone)
|
|
1206
|
+
if (trace !== undefined && !trace.welcomeDone) {
|
|
1153
1207
|
trace.channels++;
|
|
1208
|
+
if (this.#status === ClientStatus.Connected) {
|
|
1209
|
+
this.#viewStore.collect(parseChannel(cmd.params));
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1154
1212
|
}
|
|
1155
1213
|
// The server answers our goodbye by announcing that we left, not with an
|
|
1156
1214
|
// error line; that announcement is what disconnect() waits for.
|
|
@@ -1169,8 +1227,21 @@ export class Client {
|
|
|
1169
1227
|
}
|
|
1170
1228
|
if (cmd.name === "notifyconnectioninforequest")
|
|
1171
1229
|
this.#answerConnectionInfoRequest();
|
|
1230
|
+
if (cmd.name === "notifyservergrouplist" || cmd.name === "notifychannelgrouplist") {
|
|
1231
|
+
if (this.#groupLine?.name !== cmd.name)
|
|
1232
|
+
this.#applyGroupLine();
|
|
1233
|
+
this.#groupLine ??= { name: cmd.name, rows: [] };
|
|
1234
|
+
this.#groupLine.rows.push(cmd.params);
|
|
1235
|
+
}
|
|
1236
|
+
else if (cmd.name === "notifyserverupdated") {
|
|
1237
|
+
this.#viewStore.mergeServer(cmd.params);
|
|
1238
|
+
}
|
|
1239
|
+
// The roster entry goes with the notification, so it is read first.
|
|
1240
|
+
const leaving = cmd.name === "notifyclientleftview"
|
|
1241
|
+
? this.#clients.get(parseUint16(cmd.params["clid"] ?? ""))
|
|
1242
|
+
: undefined;
|
|
1172
1243
|
const result = handleNotification(cmd, this.#clid, this.#clients);
|
|
1173
|
-
this.#processNotificationResult(result);
|
|
1244
|
+
this.#processNotificationResult(result, leaving);
|
|
1174
1245
|
if (this.#listeners.get("notification")?.length || this.#eventMiddlewares.length > 0) {
|
|
1175
1246
|
this.#emitServerEvent({
|
|
1176
1247
|
name: "notification",
|
|
@@ -1196,6 +1267,7 @@ export class Client {
|
|
|
1196
1267
|
// report a connection whose next command — clientinit with its password
|
|
1197
1268
|
// hashes among them — goes out under that dummy key.
|
|
1198
1269
|
if (this.#status === ClientStatus.Connecting && this.#crypt.cryptoInitComplete) {
|
|
1270
|
+
this.#viewStore.seedServer(cmd.params);
|
|
1199
1271
|
handleInitServer(this.#internals, cmd.params);
|
|
1200
1272
|
}
|
|
1201
1273
|
break;
|
|
@@ -1248,7 +1320,7 @@ export class Client {
|
|
|
1248
1320
|
});
|
|
1249
1321
|
}
|
|
1250
1322
|
}
|
|
1251
|
-
#processNotificationResult(result) {
|
|
1323
|
+
#processNotificationResult(result, leaving) {
|
|
1252
1324
|
switch (result.kind) {
|
|
1253
1325
|
case "clientEnter": {
|
|
1254
1326
|
const info = result.info;
|
|
@@ -1278,7 +1350,7 @@ export class Client {
|
|
|
1278
1350
|
break;
|
|
1279
1351
|
}
|
|
1280
1352
|
case "clientLeave": {
|
|
1281
|
-
const event = result.event;
|
|
1353
|
+
const event = leaving === undefined ? result.event : { ...result.event, previous: leaving };
|
|
1282
1354
|
this.#emitServerEvent({ name: "clientLeave", payload: event });
|
|
1283
1355
|
if (!result.isSelf)
|
|
1284
1356
|
break;
|
|
@@ -1317,17 +1389,26 @@ export class Client {
|
|
|
1317
1389
|
this.#emitServerEvent({ name: "clientChannelGroupChanged", payload: result.event });
|
|
1318
1390
|
break;
|
|
1319
1391
|
case "channelCreated":
|
|
1392
|
+
this.#viewStore.applyCreated(result.event);
|
|
1320
1393
|
this.#emitServerEvent({ name: "channelCreated", payload: result.event });
|
|
1321
1394
|
break;
|
|
1322
1395
|
case "channelEdited":
|
|
1396
|
+
this.#viewStore.applyEdited(result.event);
|
|
1323
1397
|
this.#emitServerEvent({ name: "channelEdited", payload: result.event });
|
|
1324
1398
|
break;
|
|
1325
1399
|
case "channelMoved":
|
|
1400
|
+
this.#viewStore.applyMoved(result.event);
|
|
1326
1401
|
this.#emitServerEvent({ name: "channelMoved", payload: result.event });
|
|
1327
1402
|
break;
|
|
1328
|
-
case "channelDeleted":
|
|
1329
|
-
this.#
|
|
1403
|
+
case "channelDeleted": {
|
|
1404
|
+
const previous = this.#viewStore.channelBeforeDeletion(result.event.channelID);
|
|
1405
|
+
this.#viewStore.applyDeleted(result.event);
|
|
1406
|
+
this.#emitServerEvent({
|
|
1407
|
+
name: "channelDeleted",
|
|
1408
|
+
payload: previous === undefined ? result.event : { ...result.event, previous },
|
|
1409
|
+
});
|
|
1330
1410
|
break;
|
|
1411
|
+
}
|
|
1331
1412
|
case "channelDescriptionChanged":
|
|
1332
1413
|
this.#emitServerEvent({ name: "channelDescriptionChanged", payload: result.event });
|
|
1333
1414
|
break;
|
|
@@ -1335,12 +1416,15 @@ export class Client {
|
|
|
1335
1416
|
this.#emitServerEvent({ name: "channelPasswordChanged", payload: result.event });
|
|
1336
1417
|
break;
|
|
1337
1418
|
case "serverEdited":
|
|
1419
|
+
this.#viewStore.patchServer(result.event.changes);
|
|
1338
1420
|
this.#emitServerEvent({ name: "serverEdited", payload: result.event });
|
|
1339
1421
|
break;
|
|
1340
1422
|
case "channelSubscribed":
|
|
1341
1423
|
this.#emitServerEvent({ name: "channelSubscribed", payload: result.event });
|
|
1342
1424
|
break;
|
|
1343
1425
|
case "channelUnsubscribed":
|
|
1426
|
+
// The server does not say the clients of the channel left.
|
|
1427
|
+
this.#viewStore.dropChannel(result.event.channelID);
|
|
1344
1428
|
this.#emitServerEvent({ name: "channelUnsubscribed", payload: result.event });
|
|
1345
1429
|
break;
|
|
1346
1430
|
case "pluginCommand":
|
|
@@ -1437,6 +1521,9 @@ export class Client {
|
|
|
1437
1521
|
*/
|
|
1438
1522
|
#enqueue(delivery) {
|
|
1439
1523
|
this.#queue.push(delivery);
|
|
1524
|
+
this.#scheduleDrain();
|
|
1525
|
+
}
|
|
1526
|
+
#scheduleDrain() {
|
|
1440
1527
|
if (this.#draining)
|
|
1441
1528
|
return;
|
|
1442
1529
|
this.#draining = true;
|
|
@@ -1446,6 +1533,11 @@ export class Client {
|
|
|
1446
1533
|
try {
|
|
1447
1534
|
while (this.#queue.length > 0)
|
|
1448
1535
|
this.#queue.shift()();
|
|
1536
|
+
// Everything handled so far is delivered: say once that the view moved.
|
|
1537
|
+
// Listeners cannot change the view, so this is the last word of the run.
|
|
1538
|
+
const revision = this.#viewStore.announce();
|
|
1539
|
+
if (revision !== undefined)
|
|
1540
|
+
this.#deliver("viewChanged", { revision });
|
|
1449
1541
|
}
|
|
1450
1542
|
finally {
|
|
1451
1543
|
if (this.#queue.length > 0)
|
|
@@ -1612,6 +1704,7 @@ export class Client {
|
|
|
1612
1704
|
if (this.#status === ClientStatus.Connected)
|
|
1613
1705
|
return;
|
|
1614
1706
|
this.#attempt.reject(error);
|
|
1707
|
+
this.#viewStore.end(error);
|
|
1615
1708
|
if (this.#status === ClientStatus.Disconnected)
|
|
1616
1709
|
return;
|
|
1617
1710
|
this.#status = ClientStatus.Disconnected;
|