@honeybbq/teamspeak 1.0.0-beta.12 → 1.0.0-beta.14
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 +59 -6
- package/dist/client/api/files.d.ts +89 -0
- package/dist/client/api/files.d.ts.map +1 -0
- package/dist/client/api/files.js +125 -0
- package/dist/client/api/files.js.map +1 -0
- 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 +2 -2
- 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 +2 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +2 -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 +60 -4
- package/dist/core/api/channels.d.ts.map +1 -1
- package/dist/core/api/channels.js +69 -4
- package/dist/core/api/channels.js.map +1 -1
- package/dist/core/api/clients.d.ts +125 -2
- package/dist/core/api/clients.d.ts.map +1 -1
- package/dist/core/api/clients.js +127 -2
- package/dist/core/api/clients.js.map +1 -1
- package/dist/core/api/command.d.ts +58 -2
- package/dist/core/api/command.d.ts.map +1 -1
- package/dist/core/api/command.js +33 -2
- package/dist/core/api/command.js.map +1 -1
- package/dist/core/api/files.d.ts +39 -1
- package/dist/core/api/files.d.ts.map +1 -1
- package/dist/core/api/files.js +46 -1
- package/dist/core/api/files.js.map +1 -1
- package/dist/core/api/groups.d.ts +28 -3
- package/dist/core/api/groups.d.ts.map +1 -1
- package/dist/core/api/groups.js +32 -3
- package/dist/core/api/groups.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 +41 -0
- package/dist/core/api/moderation.d.ts.map +1 -1
- package/dist/core/api/moderation.js +41 -1
- 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 +103 -1
- package/dist/core/api/server.d.ts.map +1 -1
- package/dist/core/api/server.js +108 -1
- 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 +3 -3
- 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 +98 -20
- 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/late-rows.d.ts +31 -0
- package/dist/query/late-rows.d.ts.map +1 -0
- package/dist/query/late-rows.js +49 -0
- package/dist/query/late-rows.js.map +1 -0
- 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/api/files.ts +249 -0
- package/src/client/capabilities.ts +14 -11
- package/src/client/client.ts +49 -8
- package/src/client/crypto/identity.ts +2 -2
- package/src/client/crypto/index.ts +1 -0
- package/src/client/index.ts +3 -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 +131 -4
- package/src/core/api/clients.ts +240 -3
- package/src/core/api/command.ts +96 -2
- package/src/core/api/files.ts +72 -1
- package/src/core/api/groups.ts +64 -3
- package/src/core/api/instance.ts +20 -2
- package/src/core/api/moderation.ts +57 -1
- package/src/core/api/parse.ts +1 -0
- package/src/core/api/server.ts +206 -1
- package/src/core/command/responses.ts +1 -0
- package/src/core/events.ts +48 -5
- package/src/core/index.ts +10 -2
- 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 +110 -19
- package/src/query/index.ts +1 -1
- package/src/query/late-rows.ts +64 -0
- 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
package/README.md
CHANGED
|
@@ -296,7 +296,25 @@ sends a raw command line and resolves to the rows that answer it.
|
|
|
296
296
|
|
|
297
297
|
### File Transfers
|
|
298
298
|
|
|
299
|
-
|
|
299
|
+
`uploadFile` and `downloadFile` (from `@honeybbq/teamspeak/client`) negotiate the
|
|
300
|
+
transfer and move the bytes in one call, and `listFilesRecursive` (from the
|
|
301
|
+
package) lists a directory and everything under it, each entry with its full path:
|
|
302
|
+
|
|
303
|
+
```typescript
|
|
304
|
+
import { downloadFile, uploadFile } from "@honeybbq/teamspeak/client";
|
|
305
|
+
import { listFilesRecursive } from "@honeybbq/teamspeak";
|
|
306
|
+
|
|
307
|
+
await uploadFile(client, cid, "/docs/notes.txt", data);
|
|
308
|
+
const bytes = await downloadFile(client, cid, "/docs/notes.txt");
|
|
309
|
+
const tree = await listFilesRecursive(client, cid, "/", { maxDepth: 4 });
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
They work over a voice `Client`, and over a `QueryClient` on raw TCP or SSH when
|
|
313
|
+
passed the file transfer host as `{ host }`; WebQuery refuses every file command.
|
|
314
|
+
`downloadFile` takes `{ dest }` to stream into a `Writable` instead of holding
|
|
315
|
+
the file, and refuses a file over `maxSize` (256 MiB) in memory.
|
|
316
|
+
|
|
317
|
+
To control the two halves, negotiate with the `files` module, then move the bytes over TCP:
|
|
300
318
|
|
|
301
319
|
```typescript
|
|
302
320
|
import { Readable } from "node:stream";
|
|
@@ -537,11 +555,11 @@ the way the voice protocol cannot: the same typed commands the core exports,
|
|
|
537
555
|
plus the ones only an administrator may run, over the three transports a
|
|
538
556
|
ServerQuery speaks.
|
|
539
557
|
|
|
540
|
-
| Transport | Where
|
|
541
|
-
| --------- |
|
|
542
|
-
| `tcp` | 10011
|
|
543
|
-
| `ssh` | 10022
|
|
544
|
-
| `web` | 10080 (HTTP) | both | the api key | **no** |
|
|
558
|
+
| Transport | Where | Server | Login | Notifications |
|
|
559
|
+
| --------- | --------------------------- | ---------------- | ----------------- | ------------- |
|
|
560
|
+
| `tcp` | 10011 | TeamSpeak 3 only | `transport.login` | yes |
|
|
561
|
+
| `ssh` | 10022 | both | the SSH login | yes |
|
|
562
|
+
| `web` | 10080 (HTTP), 10443 (HTTPS) | both | the api key | **no** |
|
|
545
563
|
|
|
546
564
|
TeamSpeak 3 (3.12 and later for WebQuery, 3.3 and later for SSH) serves SSH and
|
|
547
565
|
WebQuery as well as raw TCP, but only the raw port is on until
|
|
@@ -550,6 +568,41 @@ WebQuery as well as raw TCP, but only the raw port is on until
|
|
|
550
568
|
WebQuery also answers a request with no api key as a guest; TeamSpeak 3 refuses
|
|
551
569
|
one.
|
|
552
570
|
|
|
571
|
+
WebQuery is reached over `http:` or `https:`, by the scheme of `url`. TeamSpeak 3
|
|
572
|
+
serves HTTPS on 10443 once `query_protocols` lists `https` and
|
|
573
|
+
`query_https_certificate_file` and `query_https_private_key_file` are set. The
|
|
574
|
+
built-in `fetch` verifies the certificate against the trust store Node uses and
|
|
575
|
+
never skips that. A certificate from a private CA, or a self-signed one, is
|
|
576
|
+
trusted by starting Node with `NODE_EXTRA_CA_CERTS=ca.pem`, or by a `fetch`
|
|
577
|
+
that trusts the CA:
|
|
578
|
+
|
|
579
|
+
```typescript
|
|
580
|
+
import { readFileSync } from "node:fs";
|
|
581
|
+
import { Agent, request } from "node:https";
|
|
582
|
+
|
|
583
|
+
const agent = new Agent({ ca: readFileSync("ca.pem") });
|
|
584
|
+
const trusting = ((url: string, init: RequestInit) =>
|
|
585
|
+
new Promise<Response>((resolve, reject) => {
|
|
586
|
+
const req = request(
|
|
587
|
+
url,
|
|
588
|
+
{ method: init.method, headers: init.headers as never, agent },
|
|
589
|
+
(res) => {
|
|
590
|
+
const chunks: Buffer[] = [];
|
|
591
|
+
res.on("data", (chunk: Buffer) => chunks.push(chunk));
|
|
592
|
+
res.on("end", () =>
|
|
593
|
+
resolve(new Response(Buffer.concat(chunks), { status: res.statusCode })),
|
|
594
|
+
);
|
|
595
|
+
},
|
|
596
|
+
);
|
|
597
|
+
req.on("error", reject);
|
|
598
|
+
req.end(init.body as string);
|
|
599
|
+
})) as typeof fetch;
|
|
600
|
+
|
|
601
|
+
new QueryClient({
|
|
602
|
+
transport: { kind: "web", url: "https://ts.example.com:10443", apiKey, fetch: trusting },
|
|
603
|
+
});
|
|
604
|
+
```
|
|
605
|
+
|
|
553
606
|
Only raw TCP and SSH carry notifications: the server pushes them down the same
|
|
554
607
|
connection. WebQuery has no session to push to and refuses to register, so the
|
|
555
608
|
client never tries, and a listener for a server event there throws rather than
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One-call file transfers: negotiate the transfer, move the bytes, and return.
|
|
3
|
+
*
|
|
4
|
+
* `initFileUpload` and `uploadFileData` (and the download twins) are the two
|
|
5
|
+
* halves of a transfer; these run both, over the connection that sends the
|
|
6
|
+
* commands. That is a voice `Client`, or a `QueryClient` over raw TCP or SSH:
|
|
7
|
+
* measured on TeamSpeak 3 3.13.8 (raw TCP, SSH, voice) and TeamSpeak 6
|
|
8
|
+
* beta13.1 (SSH, voice), the key a query connection negotiates opens the same
|
|
9
|
+
* file transfer port a voice client's does. WebQuery refuses every file command
|
|
10
|
+
* (error 5120), on both servers.
|
|
11
|
+
*/
|
|
12
|
+
import { Readable, Writable } from "node:stream";
|
|
13
|
+
import type { ChannelID, CommandExecutor, FileDownloadOptions, FileUploadOptions } from "@honeybbq/teamspeak";
|
|
14
|
+
import type { FileTransferOptions } from "../transfer.js";
|
|
15
|
+
/**
|
|
16
|
+
* How {@link uploadFile} negotiates and moves the file: the options of
|
|
17
|
+
* `initFileUpload`, without `resume` (the whole file is sent), and those of
|
|
18
|
+
* `uploadFileData`. `signal` and `timeoutMs` apply to the negotiation, and the
|
|
19
|
+
* signal to the transfer too.
|
|
20
|
+
*/
|
|
21
|
+
export interface UploadFileOptions extends Omit<FileUploadOptions, "resume">, FileTransferOptions {
|
|
22
|
+
/**
|
|
23
|
+
* The host of the file transfer port. A voice client sends to the host it
|
|
24
|
+
* connected to, so it needs none; any other connection must name it. A host
|
|
25
|
+
* given here wins over the client's own, and so does `socketFactory`.
|
|
26
|
+
*/
|
|
27
|
+
readonly host?: string | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* The size in bytes of a stream passed as the data, which the server needs
|
|
30
|
+
* before it takes the file. Required for a `Readable`; for bytes it is their
|
|
31
|
+
* length, and a different value here is refused.
|
|
32
|
+
*/
|
|
33
|
+
readonly size?: bigint | number | undefined;
|
|
34
|
+
}
|
|
35
|
+
/** How {@link downloadFile} negotiates and moves the file. */
|
|
36
|
+
export interface DownloadFileOptions extends Omit<FileDownloadOptions, "seekPosition">, FileTransferOptions {
|
|
37
|
+
/** The host of the file transfer port, as for {@link UploadFileOptions}. */
|
|
38
|
+
readonly host?: string | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* Write the file into this stream instead of returning its bytes. The stream
|
|
41
|
+
* is not ended, as for `downloadFileData`.
|
|
42
|
+
*/
|
|
43
|
+
readonly dest?: Writable | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* The largest file, in bytes, to hold in memory when no `dest` is given;
|
|
46
|
+
* 268435456 (256 MiB) by default. A larger file is refused with a
|
|
47
|
+
* {@link FileTransferError} before any byte is read; the transfer the server
|
|
48
|
+
* negotiated is left unused.
|
|
49
|
+
*/
|
|
50
|
+
readonly maxSize?: number | undefined;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Upload a file into a channel's file area: negotiate the transfer
|
|
54
|
+
* (`initFileUpload`) and send the bytes (`uploadFileData`), in one call.
|
|
55
|
+
*
|
|
56
|
+
* `data` is the bytes, or a `Readable` with `options.size` the number of bytes
|
|
57
|
+
* it will give. An existing file is not replaced unless `overwrite` is set, and
|
|
58
|
+
* the directory the file goes in must exist (a missing one is refused with
|
|
59
|
+
* 2054, invalid file path): make it with `createDirectory` first. A refusal of
|
|
60
|
+
* the negotiation is thrown as the ServerError it is, before anything is sent;
|
|
61
|
+
* a failure of the connection to the file transfer port is a
|
|
62
|
+
* {@link FileTransferError}.
|
|
63
|
+
*
|
|
64
|
+
* Over a voice `Client` the bytes go through its own transfer sockets and to the
|
|
65
|
+
* host it connected to. Over any other connection, such as a `QueryClient`,
|
|
66
|
+
* name the host of the file transfer port in `options.host`.
|
|
67
|
+
*/
|
|
68
|
+
export declare function uploadFile(client: CommandExecutor, channelID: ChannelID, path: string, data: Uint8Array | Readable, options?: UploadFileOptions): Promise<void>;
|
|
69
|
+
/**
|
|
70
|
+
* Download a file from a channel's file area into memory: negotiate the
|
|
71
|
+
* transfer (`initFileDownload`) and read the bytes (`downloadFileData`), in one
|
|
72
|
+
* call. With `options.dest` the bytes go into that stream instead and nothing
|
|
73
|
+
* is held, which is the way to fetch a large file.
|
|
74
|
+
*
|
|
75
|
+
* A file over `maxSize` (256 MiB unless set) is refused when no `dest` is given.
|
|
76
|
+
* A file that is not there is the ServerError of the negotiation: 2051 on TS3
|
|
77
|
+
* 3.13.8 and TS6 beta13.1. A path that names a directory is not refused by the
|
|
78
|
+
* server: it announces a size and sends nothing, so the call rejects with a
|
|
79
|
+
* {@link FileTransferError} for the bytes that never came.
|
|
80
|
+
*
|
|
81
|
+
* Over a voice `Client` the bytes come through its own transfer sockets from
|
|
82
|
+
* the host it connected to. Over any other connection, name the host of the
|
|
83
|
+
* file transfer port in `options.host`.
|
|
84
|
+
*/
|
|
85
|
+
export declare function downloadFile(client: CommandExecutor, channelID: ChannelID, path: string, options: DownloadFileOptions & {
|
|
86
|
+
readonly dest: Writable;
|
|
87
|
+
}): Promise<void>;
|
|
88
|
+
export declare function downloadFile(client: CommandExecutor, channelID: ChannelID, path: string, options?: DownloadFileOptions): Promise<Uint8Array>;
|
|
89
|
+
//# sourceMappingURL=files.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../../../src/client/api/files.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEjD,OAAO,KAAK,EACV,SAAS,EACT,eAAe,EAEf,mBAAmB,EAEnB,iBAAiB,EAClB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAE1D;;;;;GAKG;AACH,MAAM,WAAW,iBAAkB,SAAQ,IAAI,CAAC,iBAAiB,EAAE,QAAQ,CAAC,EAAE,mBAAmB;IAC/F;;;;OAIG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC;;;;OAIG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;CAC7C;AAED,8DAA8D;AAC9D,MAAM,WAAW,mBACf,SAAQ,IAAI,CAAC,mBAAmB,EAAE,cAAc,CAAC,EAAE,mBAAmB;IACtE,4EAA4E;IAC5E,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC;;;OAGG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IACrC;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACvC;AAqFD;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,UAAU,CAC9B,MAAM,EAAE,eAAe,EACvB,SAAS,EAAE,SAAS,EACpB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,UAAU,GAAG,QAAQ,EAC3B,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,IAAI,CAAC,CAWf;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,eAAe,EACvB,SAAS,EAAE,SAAS,EACpB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,mBAAmB,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,GACzD,OAAO,CAAC,IAAI,CAAC,CAAC;AACjB,wBAAgB,YAAY,CAC1B,MAAM,EAAE,eAAe,EACvB,SAAS,EAAE,SAAS,EACpB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,UAAU,CAAC,CAAC"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One-call file transfers: negotiate the transfer, move the bytes, and return.
|
|
3
|
+
*
|
|
4
|
+
* `initFileUpload` and `uploadFileData` (and the download twins) are the two
|
|
5
|
+
* halves of a transfer; these run both, over the connection that sends the
|
|
6
|
+
* commands. That is a voice `Client`, or a `QueryClient` over raw TCP or SSH:
|
|
7
|
+
* measured on TeamSpeak 3 3.13.8 (raw TCP, SSH, voice) and TeamSpeak 6
|
|
8
|
+
* beta13.1 (SSH, voice), the key a query connection negotiates opens the same
|
|
9
|
+
* file transfer port a voice client's does. WebQuery refuses every file command
|
|
10
|
+
* (error 5120), on both servers.
|
|
11
|
+
*/
|
|
12
|
+
import { Readable, Writable } from "node:stream";
|
|
13
|
+
import { initFileDownload, initFileUpload } from "@honeybbq/teamspeak";
|
|
14
|
+
import { FileTransferError } from "../errors.js";
|
|
15
|
+
import { downloadFileData, uploadFileData } from "../transfer.js";
|
|
16
|
+
function isTransferCapable(client) {
|
|
17
|
+
const candidate = client;
|
|
18
|
+
return (typeof candidate.host === "string" &&
|
|
19
|
+
typeof candidate.uploadFileData === "function" &&
|
|
20
|
+
typeof candidate.downloadFileData === "function");
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* How to move the bytes over this connection: through the client's own
|
|
24
|
+
* transfer sockets when it has them and the caller named neither a host nor a
|
|
25
|
+
* socket factory, else by dialling `options.host`.
|
|
26
|
+
*/
|
|
27
|
+
function routeFor(client, options) {
|
|
28
|
+
if (isTransferCapable(client) && options.host === undefined && !options.socketFactory) {
|
|
29
|
+
return {
|
|
30
|
+
upload: (info, data, o) => client.uploadFileData(client.host, info, data, o),
|
|
31
|
+
download: (info, dest, o) => client.downloadFileData(client.host, info, dest, o),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
const host = options.host ?? (isTransferCapable(client) ? client.host : undefined);
|
|
35
|
+
if (host === undefined || host === "") {
|
|
36
|
+
throw new TypeError("the file transfer port is on a host this connection does not name: pass it as options.host");
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
upload: (info, data, o) => uploadFileData(host, info, data, o),
|
|
40
|
+
download: (info, dest, o) => downloadFileData(host, info, dest, o),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/** The part of the options a transfer's data phase reads. */
|
|
44
|
+
function transferOptions(options) {
|
|
45
|
+
return {
|
|
46
|
+
signal: options.signal,
|
|
47
|
+
socketFactory: options.socketFactory,
|
|
48
|
+
trace: options.trace,
|
|
49
|
+
idleTimeoutMs: options.idleTimeoutMs,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function sizeOf(data, declared) {
|
|
53
|
+
const size = declared === undefined ? undefined : BigInt(declared);
|
|
54
|
+
if (size !== undefined && (size < 0n || !Number.isInteger(Number(declared)))) {
|
|
55
|
+
throw new RangeError(`size is a non-negative whole number of bytes, not ${String(declared)}`);
|
|
56
|
+
}
|
|
57
|
+
if (data instanceof Uint8Array) {
|
|
58
|
+
if (size !== undefined && size !== BigInt(data.length)) {
|
|
59
|
+
throw new TypeError(`size says ${size} bytes and the data is ${data.length}`);
|
|
60
|
+
}
|
|
61
|
+
return BigInt(data.length);
|
|
62
|
+
}
|
|
63
|
+
if (size === undefined) {
|
|
64
|
+
throw new TypeError("a stream has no known length: pass options.size, in bytes");
|
|
65
|
+
}
|
|
66
|
+
return size;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Upload a file into a channel's file area: negotiate the transfer
|
|
70
|
+
* (`initFileUpload`) and send the bytes (`uploadFileData`), in one call.
|
|
71
|
+
*
|
|
72
|
+
* `data` is the bytes, or a `Readable` with `options.size` the number of bytes
|
|
73
|
+
* it will give. An existing file is not replaced unless `overwrite` is set, and
|
|
74
|
+
* the directory the file goes in must exist (a missing one is refused with
|
|
75
|
+
* 2054, invalid file path): make it with `createDirectory` first. A refusal of
|
|
76
|
+
* the negotiation is thrown as the ServerError it is, before anything is sent;
|
|
77
|
+
* a failure of the connection to the file transfer port is a
|
|
78
|
+
* {@link FileTransferError}.
|
|
79
|
+
*
|
|
80
|
+
* Over a voice `Client` the bytes go through its own transfer sockets and to the
|
|
81
|
+
* host it connected to. Over any other connection, such as a `QueryClient`,
|
|
82
|
+
* name the host of the file transfer port in `options.host`.
|
|
83
|
+
*/
|
|
84
|
+
export async function uploadFile(client, channelID, path, data, options = {}) {
|
|
85
|
+
const size = sizeOf(data, options.size);
|
|
86
|
+
const route = routeFor(client, options);
|
|
87
|
+
const info = await initFileUpload(client, channelID, path, size, {
|
|
88
|
+
password: options.password,
|
|
89
|
+
overwrite: options.overwrite,
|
|
90
|
+
signal: options.signal,
|
|
91
|
+
timeoutMs: options.timeoutMs,
|
|
92
|
+
});
|
|
93
|
+
const source = data instanceof Uint8Array ? Readable.from([data]) : data;
|
|
94
|
+
await route.upload(info, source, transferOptions(options));
|
|
95
|
+
}
|
|
96
|
+
export async function downloadFile(client, channelID, path, options = {}) {
|
|
97
|
+
const maxSize = options.maxSize ?? DEFAULT_MAX_SIZE;
|
|
98
|
+
if (!Number.isFinite(maxSize) || maxSize < 0) {
|
|
99
|
+
throw new RangeError(`maxSize is a non-negative number of bytes, not ${String(maxSize)}`);
|
|
100
|
+
}
|
|
101
|
+
const route = routeFor(client, options);
|
|
102
|
+
const info = await initFileDownload(client, channelID, path, {
|
|
103
|
+
password: options.password,
|
|
104
|
+
signal: options.signal,
|
|
105
|
+
timeoutMs: options.timeoutMs,
|
|
106
|
+
});
|
|
107
|
+
if (options.dest !== undefined) {
|
|
108
|
+
await route.download(info, options.dest, transferOptions(options));
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (info.size > BigInt(maxSize)) {
|
|
112
|
+
throw new FileTransferError(`the file is ${info.size} bytes, over the ${maxSize} bytes maxSize allows in memory`);
|
|
113
|
+
}
|
|
114
|
+
const chunks = [];
|
|
115
|
+
const sink = new Writable({
|
|
116
|
+
write(chunk, _encoding, done) {
|
|
117
|
+
chunks.push(chunk);
|
|
118
|
+
done();
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
await route.download(info, sink, transferOptions(options));
|
|
122
|
+
return Buffer.concat(chunks);
|
|
123
|
+
}
|
|
124
|
+
const DEFAULT_MAX_SIZE = 256 * 1024 * 1024;
|
|
125
|
+
//# sourceMappingURL=files.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"files.js","sourceRoot":"","sources":["../../../src/client/api/files.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* its name, icon, banner and welcome message — so reading it costs no extra
|
|
12
12
|
* round trip.
|
|
13
13
|
*/
|
|
14
|
-
import { type IconID, type ServerFamily, type ServerID } from "@honeybbq/teamspeak";
|
|
14
|
+
import { HostMessageMode, type IconID, type ServerFamily, type ServerID } from "@honeybbq/teamspeak";
|
|
15
15
|
import { type UIDAlgorithm } from "./crypto/identity.js";
|
|
16
16
|
/** What a server said about itself in the handshake. */
|
|
17
17
|
export interface ServerCapabilities {
|
|
@@ -48,10 +48,10 @@ export interface ServerCapabilities {
|
|
|
48
48
|
readonly hostMessage: string;
|
|
49
49
|
/**
|
|
50
50
|
* How the host message is to be shown (`virtualserver_hostmessage_mode`).
|
|
51
|
-
*
|
|
52
|
-
*
|
|
51
|
+
* A value the `HostMessageMode` table does not name is passed through as the
|
|
52
|
+
* server sent it.
|
|
53
53
|
*/
|
|
54
|
-
readonly hostMessageMode:
|
|
54
|
+
readonly hostMessageMode: HostMessageMode;
|
|
55
55
|
}
|
|
56
56
|
/**
|
|
57
57
|
* Read what a server says about itself in `initserver`, or a version string
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capabilities.d.ts","sourceRoot":"","sources":["../../src/client/capabilities.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"capabilities.d.ts","sourceRoot":"","sources":["../../src/client/capabilities.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAEL,eAAe,EAEf,KAAK,MAAM,EACX,KAAK,YAAY,EACjB,KAAK,QAAQ,EACd,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAA0B,KAAK,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEjF,wDAAwD;AACxD,MAAM,WAAW,kBAAkB;IACjC,yEAAyE;IACzE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,8DAA8D;IAC9D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,uCAAuC;IACvC,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B;;;;;OAKG;IACH,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC,wDAAwD;IACxD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,uFAAuF;IACvF,QAAQ,CAAC,kBAAkB,EAAE,MAAM,GAAG,SAAS,CAAC;IAChD,gFAAgF;IAChF,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,uFAAuF;IACvF,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,uGAAuG;IACvG,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,2FAA2F;IAC3F,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B;;;;OAIG;IACH,QAAQ,CAAC,eAAe,EAAE,eAAe,CAAC;CAC3C;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GACpD,kBAAkB,CAqBpB;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,IAAI,kBAAkB,CAE9D"}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* its name, icon, banner and welcome message — so reading it costs no extra
|
|
12
12
|
* round trip.
|
|
13
13
|
*/
|
|
14
|
-
import { asServerID } from "@honeybbq/teamspeak";
|
|
14
|
+
import { asServerID, HostMessageMode, serverFamilyForVersion, } from "@honeybbq/teamspeak";
|
|
15
15
|
import { parseIconID, parseUint64 } from "../core/internal.js";
|
|
16
16
|
import { uidAlgorithmForVersion } from "./crypto/identity.js";
|
|
17
17
|
/**
|
|
@@ -23,15 +23,10 @@ export function parseServerCapabilities(initserver) {
|
|
|
23
23
|
const version = params["virtualserver_version"] ?? "";
|
|
24
24
|
const parsed = Number.parseInt(version, 10);
|
|
25
25
|
const major = Number.isNaN(parsed) ? 0 : parsed;
|
|
26
|
-
let family = "unknown";
|
|
27
|
-
if (major === 3)
|
|
28
|
-
family = "ts3";
|
|
29
|
-
else if (major >= 6)
|
|
30
|
-
family = "ts6";
|
|
31
26
|
return {
|
|
32
27
|
version,
|
|
33
28
|
major,
|
|
34
|
-
family,
|
|
29
|
+
family: serverFamilyForVersion(version),
|
|
35
30
|
uidAlgorithm: uidAlgorithmForVersion(version),
|
|
36
31
|
name: params["virtualserver_name"] ?? "",
|
|
37
32
|
iconID: parseIconID(params["virtualserver_icon_id"]),
|
|
@@ -54,6 +49,6 @@ export function unknownServerCapabilities() {
|
|
|
54
49
|
}
|
|
55
50
|
function hostMessageMode(value) {
|
|
56
51
|
const parsed = Number.parseInt(value ?? "", 10);
|
|
57
|
-
return Number.isNaN(parsed) ?
|
|
52
|
+
return (Number.isNaN(parsed) ? HostMessageMode.None : parsed);
|
|
58
53
|
}
|
|
59
54
|
//# sourceMappingURL=capabilities.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capabilities.js","sourceRoot":"","sources":["../../src/client/capabilities.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"capabilities.js","sourceRoot":"","sources":["../../src/client/capabilities.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EACL,UAAU,EACV,eAAe,EACf,sBAAsB,GAIvB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,EAAE,sBAAsB,EAAqB,MAAM,sBAAsB,CAAC;AA2CjF;;;GAGG;AACH,MAAM,UAAU,uBAAuB,CACrC,UAAqD;IAErD,MAAM,MAAM,GACV,OAAO,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,qBAAqB,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC;IACtF,MAAM,OAAO,GAAG,MAAM,CAAC,uBAAuB,CAAC,IAAI,EAAE,CAAC;IACtD,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IAC5C,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IAEhD,OAAO;QACL,OAAO;QACP,KAAK;QACL,MAAM,EAAE,sBAAsB,CAAC,OAAO,CAAC;QACvC,YAAY,EAAE,sBAAsB,CAAC,OAAO,CAAC;QAC7C,IAAI,EAAE,MAAM,CAAC,oBAAoB,CAAC,IAAI,EAAE;QACxC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,uBAAuB,CAAC,CAAC;QACpD,kBAAkB,EAAE,MAAM,CAAC,kCAAkC,CAAC,EAAE,IAAI,EAAE,IAAI,SAAS;QACnF,cAAc,EAAE,MAAM,CAAC,8BAA8B,CAAC,IAAI,EAAE;QAC5D,QAAQ,EAAE,UAAU,CAAC,WAAW,CAAC,MAAM,CAAC,kBAAkB,CAAC,IAAI,EAAE,CAAC,CAAC;QACnE,SAAS,EAAE,MAAM,CAAC,iCAAiC,CAAC,IAAI,EAAE;QAC1D,WAAW,EAAE,MAAM,CAAC,2BAA2B,CAAC,IAAI,EAAE;QACtD,eAAe,EAAE,eAAe,CAAC,MAAM,CAAC,gCAAgC,CAAC,CAAC;KAC3E,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,yBAAyB;IACvC,OAAO,uBAAuB,CAAC,EAAE,CAAC,CAAC;AACrC,CAAC;AAED,SAAS,eAAe,CAAC,KAAyB;IAChD,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;IAChD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAoB,CAAC;AACnF,CAAC"}
|
package/dist/client/client.d.ts
CHANGED
|
@@ -225,11 +225,11 @@ export declare class Client implements CommandExecutor, AsyncDisposable {
|
|
|
225
225
|
* Upload `data` over a negotiated transfer, through the client's
|
|
226
226
|
* `fileTransferSocketFactory` if it has one.
|
|
227
227
|
*/
|
|
228
|
-
uploadFileData(host: string, info: FileUploadInfo, data: Readable, options?: Pick<FileTransferOptions, "signal" | "trace">): Promise<void>;
|
|
228
|
+
uploadFileData(host: string, info: FileUploadInfo, data: Readable, options?: Pick<FileTransferOptions, "signal" | "trace" | "idleTimeoutMs">): Promise<void>;
|
|
229
229
|
/**
|
|
230
230
|
* Download a negotiated transfer into `dest`, through the client's
|
|
231
231
|
* `fileTransferSocketFactory` if it has one.
|
|
232
232
|
*/
|
|
233
|
-
downloadFileData(host: string, info: FileDownloadInfo, dest: Writable, options?: Pick<FileTransferOptions, "signal" | "trace">): Promise<void>;
|
|
233
|
+
downloadFileData(host: string, info: FileDownloadInfo, dest: Writable, options?: Pick<FileTransferOptions, "signal" | "trace" | "idleTimeoutMs">): Promise<void>;
|
|
234
234
|
}
|
|
235
235
|
//# sourceMappingURL=client.d.ts.map
|
|
@@ -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;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;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client/client.ts"],"names":[],"mappings":"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"}
|
package/dist/client/client.js
CHANGED
|
@@ -83,6 +83,24 @@ const DEFAULT_COMMAND_TIMEOUT_MS = 10_000;
|
|
|
83
83
|
const DISCONNECT_TIMEOUT_MS = 1_000;
|
|
84
84
|
/** How long each reconnection attempt may take. */
|
|
85
85
|
const RECONNECT_ATTEMPT_TIMEOUT_MS = 30_000;
|
|
86
|
+
/** What an attempt is waiting for now, for the report of an attempt that ended. */
|
|
87
|
+
function describeWaiting(trace, clientEkPending) {
|
|
88
|
+
if (trace.phase === ConnectPhase.Open) {
|
|
89
|
+
return trace.target === "" ? "the server address to resolve" : "the connection to open";
|
|
90
|
+
}
|
|
91
|
+
if (trace.phase === ConnectPhase.Join) {
|
|
92
|
+
return clientEkPending
|
|
93
|
+
? "the server's answer to clientek"
|
|
94
|
+
: "the server's answer to clientinit";
|
|
95
|
+
}
|
|
96
|
+
if (trace.stage === "puzzleSolving")
|
|
97
|
+
return "the RSA puzzle to be solved";
|
|
98
|
+
if (trace.stage === "puzzleAnswered")
|
|
99
|
+
return "the server's answer to the RSA puzzle";
|
|
100
|
+
return trace.init1Step < 0
|
|
101
|
+
? "the server's first answer to init1"
|
|
102
|
+
: `the server's init1 step after step ${trace.init1Step}`;
|
|
103
|
+
}
|
|
86
104
|
/** The `initserver` parameters a trace never sees, because they could carry a secret. */
|
|
87
105
|
const SECRET_INITSERVER_PARAMS = new Set([
|
|
88
106
|
"client_server_password",
|
|
@@ -364,16 +382,27 @@ export class Client {
|
|
|
364
382
|
sessionStarted: false,
|
|
365
383
|
welcomeDone: false,
|
|
366
384
|
channels: 0,
|
|
385
|
+
stage: "init1",
|
|
386
|
+
init1Step: -1,
|
|
367
387
|
};
|
|
368
388
|
attempt.trace = trace;
|
|
369
|
-
attempt.onFail = (error) =>
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
389
|
+
attempt.onFail = (error) => {
|
|
390
|
+
const waitingFor = describeWaiting(trace, this.#handler.clientEkPending);
|
|
391
|
+
this.#logger.info("connection attempt ended", {
|
|
392
|
+
phase: trace.phase,
|
|
393
|
+
waitingFor,
|
|
394
|
+
error: String(error),
|
|
395
|
+
});
|
|
396
|
+
this.#callHook(this.#trace?.attemptFailed, () => ({
|
|
397
|
+
attempt: trace.attempt,
|
|
398
|
+
reconnect: trace.reconnect,
|
|
399
|
+
phase: trace.phase,
|
|
400
|
+
error,
|
|
401
|
+
target: trace.target,
|
|
402
|
+
waitingFor,
|
|
403
|
+
durationMs: performance.now() - trace.startedAt,
|
|
404
|
+
}));
|
|
405
|
+
};
|
|
377
406
|
// The handshake may complete just as the signal fires, in which case
|
|
378
407
|
// #failConnection leaves the session alone and the caller gets the
|
|
379
408
|
// connection it made rather than one it would believe had failed.
|
|
@@ -1080,12 +1109,19 @@ export class Client {
|
|
|
1080
1109
|
return;
|
|
1081
1110
|
const signal = this.#attempt.signal;
|
|
1082
1111
|
const handler = this.#handler;
|
|
1112
|
+
const trace = this.#attempt.trace;
|
|
1113
|
+
const step = p.data[0] ?? 0x7f;
|
|
1083
1114
|
const response = processInit1(this.#crypt, p.data, signal);
|
|
1084
1115
|
if (response instanceof Promise) {
|
|
1085
1116
|
// The puzzle: solved in slices, so the loop keeps running meanwhile.
|
|
1117
|
+
if (trace !== undefined)
|
|
1118
|
+
trace.stage = "puzzleSolving";
|
|
1086
1119
|
response.then((answer) => {
|
|
1087
|
-
if (answer && !signal.aborted)
|
|
1120
|
+
if (answer && !signal.aborted) {
|
|
1088
1121
|
handler.sendPacket(PacketType.Init1, answer, 0);
|
|
1122
|
+
if (trace !== undefined)
|
|
1123
|
+
trace.stage = "puzzleAnswered";
|
|
1124
|
+
}
|
|
1089
1125
|
}, (error) => {
|
|
1090
1126
|
// An attempt that ended did so for its own reason; the abandoned
|
|
1091
1127
|
// solve has nothing more to report.
|
|
@@ -1095,6 +1131,8 @@ export class Client {
|
|
|
1095
1131
|
}
|
|
1096
1132
|
else if (response) {
|
|
1097
1133
|
handler.sendPacket(PacketType.Init1, response, 0);
|
|
1134
|
+
if (trace !== undefined)
|
|
1135
|
+
trace.init1Step = step === 0x7f ? -1 : step;
|
|
1098
1136
|
}
|
|
1099
1137
|
return;
|
|
1100
1138
|
}
|
|
@@ -1433,6 +1471,9 @@ export class Client {
|
|
|
1433
1471
|
case "clientChatComposing":
|
|
1434
1472
|
this.#emitServerEvent({ name: "clientChatComposing", payload: result.event });
|
|
1435
1473
|
break;
|
|
1474
|
+
case "clientChatClosed":
|
|
1475
|
+
this.#emitServerEvent({ name: "clientChatClosed", payload: result.event });
|
|
1476
|
+
break;
|
|
1436
1477
|
case "poked":
|
|
1437
1478
|
this.#emitServerEvent({ name: "poked", payload: result.event });
|
|
1438
1479
|
break;
|