@honeybbq/teamspeak 1.0.0-beta.13 → 1.0.0-beta.15
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 +321 -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/client.d.ts +2 -2
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/client.js.map +1 -1
- package/dist/client/commands.d.ts.map +1 -1
- package/dist/client/commands.js +5 -1
- package/dist/client/commands.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/transport/generation-window.d.ts +6 -0
- package/dist/client/transport/generation-window.d.ts.map +1 -1
- package/dist/client/transport/generation-window.js +11 -0
- package/dist/client/transport/generation-window.js.map +1 -1
- package/dist/client/transport/handler.d.ts.map +1 -1
- package/dist/client/transport/handler.js +31 -1
- package/dist/client/transport/handler.js.map +1 -1
- package/dist/core/api/channels.d.ts +63 -8
- package/dist/core/api/channels.d.ts.map +1 -1
- package/dist/core/api/channels.js +74 -8
- package/dist/core/api/channels.js.map +1 -1
- package/dist/core/api/clients.d.ts +87 -1
- package/dist/core/api/clients.d.ts.map +1 -1
- package/dist/core/api/clients.js +95 -1
- package/dist/core/api/clients.js.map +1 -1
- package/dist/core/api/command.d.ts +51 -8
- package/dist/core/api/command.d.ts.map +1 -1
- package/dist/core/api/command.js +37 -8
- package/dist/core/api/command.js.map +1 -1
- package/dist/core/api/credentials.d.ts +3 -2
- package/dist/core/api/credentials.d.ts.map +1 -1
- package/dist/core/api/credentials.js +3 -2
- package/dist/core/api/credentials.js.map +1 -1
- package/dist/core/api/files.d.ts +47 -2
- package/dist/core/api/files.d.ts.map +1 -1
- package/dist/core/api/files.js +54 -2
- package/dist/core/api/files.js.map +1 -1
- package/dist/core/api/groups.d.ts +49 -11
- package/dist/core/api/groups.d.ts.map +1 -1
- package/dist/core/api/groups.js +53 -11
- package/dist/core/api/groups.js.map +1 -1
- package/dist/core/api/moderation.d.ts +36 -2
- package/dist/core/api/moderation.d.ts.map +1 -1
- package/dist/core/api/moderation.js +36 -3
- package/dist/core/api/moderation.js.map +1 -1
- package/dist/core/api/server.d.ts +112 -7
- package/dist/core/api/server.d.ts.map +1 -1
- package/dist/core/api/server.js +123 -7
- package/dist/core/api/server.js.map +1 -1
- package/dist/core/command/responses.d.ts +8 -3
- package/dist/core/command/responses.d.ts.map +1 -1
- package/dist/core/command/responses.js +11 -3
- package/dist/core/command/responses.js.map +1 -1
- package/dist/core/command-name.d.ts +12 -0
- package/dist/core/command-name.d.ts.map +1 -1
- package/dist/core/command-name.js +12 -0
- package/dist/core/command-name.js.map +1 -1
- package/dist/core/errorcode.d.ts +8 -0
- package/dist/core/errorcode.d.ts.map +1 -1
- package/dist/core/errorcode.js +8 -0
- package/dist/core/errorcode.js.map +1 -1
- package/dist/core/errors.d.ts +7 -6
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +7 -6
- package/dist/core/errors.js.map +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/notification-name.d.ts +16 -0
- package/dist/core/notification-name.d.ts.map +1 -1
- package/dist/core/notification-name.js +16 -0
- package/dist/core/notification-name.js.map +1 -1
- package/dist/query/client.d.ts.map +1 -1
- package/dist/query/client.js +85 -11
- package/dist/query/client.js.map +1 -1
- package/dist/query/late-rows.d.ts +33 -0
- package/dist/query/late-rows.d.ts.map +1 -0
- package/dist/query/late-rows.js +51 -0
- package/dist/query/late-rows.js.map +1 -0
- package/dist/stream/emitter.d.ts +18 -0
- package/dist/stream/emitter.d.ts.map +1 -0
- package/dist/stream/emitter.js +66 -0
- package/dist/stream/emitter.js.map +1 -0
- package/dist/stream/errors.d.ts +73 -0
- package/dist/stream/errors.d.ts.map +1 -0
- package/dist/stream/errors.js +128 -0
- package/dist/stream/errors.js.map +1 -0
- package/dist/stream/index.d.ts +27 -0
- package/dist/stream/index.d.ts.map +1 -0
- package/dist/stream/index.js +26 -0
- package/dist/stream/index.js.map +1 -0
- package/dist/stream/peer.d.ts +43 -0
- package/dist/stream/peer.d.ts.map +1 -0
- package/dist/stream/peer.js +100 -0
- package/dist/stream/peer.js.map +1 -0
- package/dist/stream/published.d.ts +54 -0
- package/dist/stream/published.d.ts.map +1 -0
- package/dist/stream/published.js +205 -0
- package/dist/stream/published.js.map +1 -0
- package/dist/stream/streams.d.ts +92 -0
- package/dist/stream/streams.d.ts.map +1 -0
- package/dist/stream/streams.js +671 -0
- package/dist/stream/streams.js.map +1 -0
- package/dist/stream/types.d.ts +428 -0
- package/dist/stream/types.d.ts.map +1 -0
- package/dist/stream/types.js +2 -0
- package/dist/stream/types.js.map +1 -0
- package/dist/stream/wait.d.ts +14 -0
- package/dist/stream/wait.d.ts.map +1 -0
- package/dist/stream/wait.js +46 -0
- package/dist/stream/wait.js.map +1 -0
- package/dist/stream/wire.d.ts +91 -0
- package/dist/stream/wire.d.ts.map +1 -0
- package/dist/stream/wire.js +248 -0
- package/dist/stream/wire.js.map +1 -0
- package/package.json +13 -6
- package/src/client/api/files.ts +249 -0
- package/src/client/client.ts +2 -2
- package/src/client/commands.ts +4 -1
- package/src/client/index.ts +2 -1
- package/src/client/transport/generation-window.ts +11 -0
- package/src/client/transport/handler.ts +28 -2
- package/src/core/api/channels.ts +132 -8
- package/src/core/api/clients.ts +167 -1
- package/src/core/api/command.ts +85 -8
- package/src/core/api/credentials.ts +3 -2
- package/src/core/api/files.ts +80 -2
- package/src/core/api/groups.ts +85 -11
- package/src/core/api/moderation.ts +52 -3
- package/src/core/api/server.ts +212 -7
- package/src/core/command/responses.ts +14 -3
- package/src/core/command-name.ts +12 -0
- package/src/core/errorcode.ts +8 -0
- package/src/core/errors.ts +7 -6
- package/src/core/index.ts +8 -1
- package/src/core/notification-name.ts +16 -0
- package/src/query/client.ts +91 -10
- package/src/query/late-rows.ts +66 -0
- package/src/stream/emitter.ts +73 -0
- package/src/stream/errors.ts +146 -0
- package/src/stream/index.ts +58 -0
- package/src/stream/peer.ts +141 -0
- package/src/stream/published.ts +254 -0
- package/src/stream/streams.ts +765 -0
- package/src/stream/types.ts +475 -0
- package/src/stream/wait.ts +62 -0
- package/src/stream/wire.ts +319 -0
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# @honeybbq/teamspeak
|
|
4
4
|
|
|
5
|
-
**
|
|
5
|
+
**An independent TeamSpeak client protocol library written in pure TypeScript.**
|
|
6
6
|
|
|
7
7
|
Works with TeamSpeak 3 and TeamSpeak 6 servers. No proprietary SDK, no copied code.
|
|
8
8
|
|
|
@@ -32,6 +32,7 @@ Upgrading from 0.x? The package was renamed; see the [migration guide](https://g
|
|
|
32
32
|
- **Identity management** — Generate, import/export, and upgrade security level of identities
|
|
33
33
|
- **Full command set** — 111 typed functions covering every command the voice protocol answers
|
|
34
34
|
- **Reconnection** — Opt-in retry with backoff, tuned for TeamSpeak's antiflood ban
|
|
35
|
+
- **Streams** — TeamSpeak 6 screen share signalling: find, publish and watch streams, with SDP and ICE as strings for any WebRTC stack (experimental)
|
|
35
36
|
- **Server capabilities** — Reports the server version and the uid hash it uses (SHA-1 on TS3, SHA-256 on TS6), and the server's name, icon, banner and welcome message
|
|
36
37
|
- **Custom transports** — Supply your own socket, for proxies, tunnels or tests
|
|
37
38
|
- **ESM with types** — Ships ES modules with full TypeScript declarations
|
|
@@ -296,7 +297,25 @@ sends a raw command line and resolves to the rows that answer it.
|
|
|
296
297
|
|
|
297
298
|
### File Transfers
|
|
298
299
|
|
|
299
|
-
|
|
300
|
+
`uploadFile` and `downloadFile` (from `@honeybbq/teamspeak/client`) negotiate the
|
|
301
|
+
transfer and move the bytes in one call, and `listFilesRecursive` (from the
|
|
302
|
+
package) lists a directory and everything under it, each entry with its full path:
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
import { downloadFile, uploadFile } from "@honeybbq/teamspeak/client";
|
|
306
|
+
import { listFilesRecursive } from "@honeybbq/teamspeak";
|
|
307
|
+
|
|
308
|
+
await uploadFile(client, cid, "/docs/notes.txt", data);
|
|
309
|
+
const bytes = await downloadFile(client, cid, "/docs/notes.txt");
|
|
310
|
+
const tree = await listFilesRecursive(client, cid, "/", { maxDepth: 4 });
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
They work over a voice `Client`, and over a `QueryClient` on raw TCP or SSH when
|
|
314
|
+
passed the file transfer host as `{ host }`; WebQuery refuses every file command.
|
|
315
|
+
`downloadFile` takes `{ dest }` to stream into a `Writable` instead of holding
|
|
316
|
+
the file, and refuses a file over `maxSize` (256 MiB) in memory.
|
|
317
|
+
|
|
318
|
+
To control the two halves, negotiate with the `files` module, then move the bytes over TCP:
|
|
300
319
|
|
|
301
320
|
```typescript
|
|
302
321
|
import { Readable } from "node:stream";
|
|
@@ -530,6 +549,297 @@ The uid hash matters because TS3 hashes a client's public key with SHA-1 and TS6
|
|
|
530
549
|
with SHA-256 — a client that assumes either one computes an identifier the other
|
|
531
550
|
server never sends.
|
|
532
551
|
|
|
552
|
+
### Streams (experimental)
|
|
553
|
+
|
|
554
|
+
TeamSpeak 6 clients can share a screen, a window or a camera. `@honeybbq/teamspeak/stream`
|
|
555
|
+
does the signalling of that: it finds the streams of the clients in view, publishes one,
|
|
556
|
+
and watches one. The video itself travels between the publisher and each viewer over
|
|
557
|
+
WebRTC, which is yours: `Streams` hands you the SDP and the ICE candidates as strings and
|
|
558
|
+
takes them back, so any WebRTC stack plugs in, in a browser, in Electron or in Node. The
|
|
559
|
+
package has no WebRTC dependency.
|
|
560
|
+
|
|
561
|
+
TeamSpeak does not document this protocol. It was measured on TeamSpeak 6 6.0.0-beta13.1,
|
|
562
|
+
and has not been tried against the native TeamSpeak 6 client yet, so every export is
|
|
563
|
+
`@beta` and may change in a minor release. A TeamSpeak 3 server does not answer the stream
|
|
564
|
+
commands at all, so `publish` and `watch` throw a `StreamsUnsupportedError` before sending
|
|
565
|
+
anything.
|
|
566
|
+
|
|
567
|
+
Watching: find a stream in `streams.list()` or wait for `started`, ask to watch it, and
|
|
568
|
+
give the publisher's offer to a peer connection. The viewer's connection is receive-only.
|
|
569
|
+
|
|
570
|
+
```ts
|
|
571
|
+
import { Streams } from "@honeybbq/teamspeak/stream";
|
|
572
|
+
|
|
573
|
+
const streams = new Streams(client);
|
|
574
|
+
streams.on("started", (info) => console.log(`${info.name} is streaming`));
|
|
575
|
+
|
|
576
|
+
const view = await streams.watch(info, { message: "may I watch?" });
|
|
577
|
+
|
|
578
|
+
const pc = new RTCPeerConnection({ iceServers: [{ urls: "stun:stun.l.google.com:19302" }] });
|
|
579
|
+
pc.ontrack = (event) => (video.srcObject = event.streams[0]);
|
|
580
|
+
pc.onicecandidate = (event) => {
|
|
581
|
+
if (event.candidate) void view.sendCandidate(event.candidate.toJSON());
|
|
582
|
+
};
|
|
583
|
+
// Candidates that arrived with the offer are delivered to the first listener.
|
|
584
|
+
view.on("candidate", (candidate) => void pc.addIceCandidate(candidate));
|
|
585
|
+
view.on("closed", () => pc.close());
|
|
586
|
+
|
|
587
|
+
await pc.setRemoteDescription({ type: "offer", sdp: view.offer });
|
|
588
|
+
const answer = await pc.createAnswer();
|
|
589
|
+
await pc.setLocalDescription(answer);
|
|
590
|
+
await view.answer(answer.sdp!);
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
Publishing: the publisher makes the offer, one peer connection for each viewer. The server
|
|
594
|
+
relays every request to watch, from any channel, and enforces neither `viewerLimit` nor
|
|
595
|
+
`accessibility`: deciding who is let in is the publisher's job, and `request` is emitted
|
|
596
|
+
for each one, with nothing accepted for you.
|
|
597
|
+
|
|
598
|
+
```ts
|
|
599
|
+
const stream = await streams.publish({ name: "My screen", source: "screen" });
|
|
600
|
+
|
|
601
|
+
stream.on("request", async (request) => {
|
|
602
|
+
if (!allowed(request.viewer)) return request.deny("Not today");
|
|
603
|
+
|
|
604
|
+
const pc = new RTCPeerConnection({ iceServers });
|
|
605
|
+
for (const track of capture.getTracks()) pc.addTrack(track, capture);
|
|
606
|
+
await pc.setLocalDescription(await pc.createOffer());
|
|
607
|
+
|
|
608
|
+
const viewer = await request.accept(pc.localDescription!.sdp);
|
|
609
|
+
pc.onicecandidate = (event) => {
|
|
610
|
+
if (event.candidate) void viewer.sendCandidate(event.candidate.toJSON());
|
|
611
|
+
};
|
|
612
|
+
viewer.on("answer", (sdp) => pc.setRemoteDescription({ type: "answer", sdp }));
|
|
613
|
+
viewer.on("candidate", (candidate) => void pc.addIceCandidate(candidate));
|
|
614
|
+
viewer.on("closed", () => pc.close());
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
await stream.stop();
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
Things to know:
|
|
621
|
+
|
|
622
|
+
- A client may run several streams, so every stream is known by its id. `list()` is what
|
|
623
|
+
this client has heard of. A stream that ends while its publisher is out of the channel is
|
|
624
|
+
reported to nobody there, and stays listed until the connection ends.
|
|
625
|
+
- A stream survives its publisher changing channel, with no event. It ends when the
|
|
626
|
+
publisher stops it or disconnects.
|
|
627
|
+
- The server drops a command line longer than about 9.2 KB without an answer. Streams
|
|
628
|
+
commands are checked first and throw a `StreamPayloadTooLargeError`; an SDP offer or an
|
|
629
|
+
ICE candidate is a few kilobytes at most.
|
|
630
|
+
- The server lets anyone stop any stream. `Streams` sends `stopstream` only for streams it
|
|
631
|
+
published, and treats a `notifystreamstopped` for any stream as final.
|
|
632
|
+
- The server remembers no decision, so a client can ask again, and every ask is relayed.
|
|
633
|
+
To refuse for good, pass `rememberDenied: true` (the 1024 most recent uids, in memory
|
|
634
|
+
only; a requester out of view is looked up with `clientgetuidfromclid`), or leave the
|
|
635
|
+
request unanswered. The server has no way to remove a viewer: closing a viewer's peer
|
|
636
|
+
ends your side of it, and stopping the stream ends it for everyone.
|
|
637
|
+
- `dispose()` stops the streams published through that `Streams` and leaves the ones it
|
|
638
|
+
watched, while the client is connected. A stream whose `publish` gave up after the command
|
|
639
|
+
went out is stopped when its announcement arrives, so none is left running unseen.
|
|
640
|
+
- A client whose `client_is_streaming` clears, or that comes into view not streaming, has
|
|
641
|
+
its streams dropped, after a grace of two seconds: `stopped` with the cause `"gone"`. The
|
|
642
|
+
server clears the flag right before it reports the stop, so a stop that arrives in that time
|
|
643
|
+
ends the stream with its own cause and reason instead, and a flag set again cancels the drop.
|
|
644
|
+
- The bitrate the server reports for a stream is not the one requested.
|
|
645
|
+
- A client announces that it streams only with `client_is_streaming`, and a stream that was
|
|
646
|
+
running before you came is not announced at all. `Streams` asks for the streams of each
|
|
647
|
+
client that sets the flag, once, on a TeamSpeak 6 server.
|
|
648
|
+
|
|
649
|
+
## Design
|
|
650
|
+
|
|
651
|
+
The conventions below hold across the whole package. Each is stated once here.
|
|
652
|
+
|
|
653
|
+
### Errors
|
|
654
|
+
|
|
655
|
+
An error is one of two kinds:
|
|
656
|
+
|
|
657
|
+
- **A refusal the server reports.** It is a `ServerError` carrying the server's
|
|
658
|
+
numeric `code`, its text as `serverMessage`, and `extraMessage` and
|
|
659
|
+
`failedPermissionID` when the server names them.
|
|
660
|
+
- **A condition the SDK detects itself**: not connected, no data, a timeout, a
|
|
661
|
+
failed handshake, a failed file transfer. Each has its own class.
|
|
662
|
+
|
|
663
|
+
Every error the SDK throws on purpose extends `TeamspeakError`, so
|
|
664
|
+
`instanceof TeamspeakError` tells its failures from anything else. A failure
|
|
665
|
+
that wraps another keeps it in `cause`. Match a refusal by `error.code` against
|
|
666
|
+
`ErrorCode`, and everything else by class:
|
|
667
|
+
|
|
668
|
+
```typescript
|
|
669
|
+
import {
|
|
670
|
+
ErrorCode,
|
|
671
|
+
NotConnectedError,
|
|
672
|
+
ServerError,
|
|
673
|
+
TeamspeakError,
|
|
674
|
+
asChannelID,
|
|
675
|
+
moveClient,
|
|
676
|
+
} from "@honeybbq/teamspeak";
|
|
677
|
+
|
|
678
|
+
try {
|
|
679
|
+
await moveClient(client, client.clientID, asChannelID(5), { password: "wrong" });
|
|
680
|
+
} catch (err) {
|
|
681
|
+
if (err instanceof ServerError && err.code === ErrorCode.ChannelInvalidPassword) {
|
|
682
|
+
// ask for the password again
|
|
683
|
+
} else if (err instanceof NotConnectedError) {
|
|
684
|
+
// the connection is gone
|
|
685
|
+
} else if (err instanceof ServerError) {
|
|
686
|
+
console.log(err.code, err.serverMessage, err.extraMessage, err.failedPermissionID);
|
|
687
|
+
} else if (err instanceof TeamspeakError) {
|
|
688
|
+
console.log(err.name, err.message, err.cause);
|
|
689
|
+
} else {
|
|
690
|
+
throw err;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
`ErrorCode` names the server's codes as constants. It is the table `errcode`
|
|
696
|
+
is in the Go SDK, with the same numbers. A server may send a code it does not
|
|
697
|
+
name, so `code` is a plain `number`. `instanceof` also works between two
|
|
698
|
+
installed copies of the package.
|
|
699
|
+
|
|
700
|
+
Errors live with the code that produces them, so each entry point exports only
|
|
701
|
+
the errors its own code throws:
|
|
702
|
+
|
|
703
|
+
| Entry point | Errors |
|
|
704
|
+
| -------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
705
|
+
| `@honeybbq/teamspeak` (shared by every connection) | `TeamspeakError`, `ServerError`, `CommandTimeoutError`, `NotConnectedError`, `AlreadyConnectedError`, `NoDataError` |
|
|
706
|
+
| `@honeybbq/teamspeak/client` (voice only) | `ConnectionTimeoutError`, `HandshakeError` and its `PuzzleTooHardError`, `InvalidAddressError`, `InvalidIdentityError`, `FileTransferError`, `RemovedError` (kick, ban or server stop) |
|
|
707
|
+
| `@honeybbq/teamspeak/stream` (streams only) | `StreamsUnsupportedError`, `StreamJoinDeniedError`, `StreamEndedError`, `StreamPayloadTooLargeError` |
|
|
708
|
+
| `@honeybbq/teamspeak/query` (query only) | `QueryAuthError`, `GuestAccessError`, `SshHostKeyError`, `MissingDependencyError`, `WebQueryHttpError` |
|
|
709
|
+
|
|
710
|
+
A call that is given a value it cannot use, such as an out-of-range id or an
|
|
711
|
+
option that contradicts another, throws the language's own `TypeError` or
|
|
712
|
+
`RangeError` before anything is sent. Those are not `TeamspeakError`s: they are
|
|
713
|
+
a bug in the caller, not a condition to handle. `WebQueryHttpError` carries the
|
|
714
|
+
HTTP `status`; a refusal the server itself reports is a `ServerError` over every
|
|
715
|
+
transport.
|
|
716
|
+
|
|
717
|
+
**Cancellation.** An aborted `signal` is the caller's own cancellation, not an
|
|
718
|
+
SDK error. `connect()`, the typed commands, `exec`, file transfers and `events()`
|
|
719
|
+
reject with the signal's `reason` itself, unwrapped: a `DOMException` named
|
|
720
|
+
`AbortError` after a bare `abort()`, a `TimeoutError` after
|
|
721
|
+
`AbortSignal.timeout()`, or whatever value was passed to `abort(reason)`. A
|
|
722
|
+
signal that is already aborted rejects before anything is sent. Aborting a
|
|
723
|
+
`connect()` abandons the attempt and leaves the client disconnected; a command
|
|
724
|
+
that was given up on may still have taken effect on the server. The signal and
|
|
725
|
+
the command's own `timeoutMs` are separate: when `timeoutMs` (ten seconds by
|
|
726
|
+
default) passes first, the command rejects with a `CommandTimeoutError`.
|
|
727
|
+
|
|
728
|
+
**No data.** `NoDataError` means the command succeeded but its answer lacked the
|
|
729
|
+
data it exists to return, such as the id of a channel just created. Treat the
|
|
730
|
+
command as having taken effect and do not retry it blindly, or it creates a
|
|
731
|
+
second object or is refused as a duplicate. `createChannel` and the group
|
|
732
|
+
commands look the new object up themselves and throw it only when nothing
|
|
733
|
+
matches. A list that comes back empty is `[]`, not this error.
|
|
734
|
+
|
|
735
|
+
### Ids
|
|
736
|
+
|
|
737
|
+
Every kind of id is a branded type: `ChannelID`, `ClientID`, `ClientDBID`,
|
|
738
|
+
`ServerGroupID`, `ChannelGroupID`, `PermissionID`, `IconID`, `ServerID`,
|
|
739
|
+
`BanID` and the rest, with `UID` and `ChannelUID` as branded strings. The
|
|
740
|
+
brand costs nothing at runtime, and passing the wrong kind is a compile error.
|
|
741
|
+
Make one with its constructor, which throws a `RangeError` for a value outside
|
|
742
|
+
the id's wire range:
|
|
743
|
+
|
|
744
|
+
```typescript
|
|
745
|
+
import { asChannelID, asClientID, moveClient } from "@honeybbq/teamspeak";
|
|
746
|
+
|
|
747
|
+
const cid = asChannelID(5); // or asChannelID(5n)
|
|
748
|
+
await moveClient(client, client.clientID, cid);
|
|
749
|
+
// moveClient(client, cid, client.clientID) does not compile.
|
|
750
|
+
const clid = asClientID(3);
|
|
751
|
+
```
|
|
752
|
+
|
|
753
|
+
A `ClientID`, `PermissionID`, `IconID` and the file transfer ids are `number`s.
|
|
754
|
+
The ones the protocol makes 64 bits wide are `bigint`s: `ChannelID`,
|
|
755
|
+
`ClientDBID`, `ServerGroupID`, `ChannelGroupID`, `ServerID`, `BanID`,
|
|
756
|
+
`MessageID` and `APIKeyID`. Their constructors take a `number` or a `bigint`.
|
|
757
|
+
|
|
758
|
+
### Options and absent values
|
|
759
|
+
|
|
760
|
+
**Options.** Required arguments are positional. Everything optional is one
|
|
761
|
+
options object, the last argument. A field that is not set is not sent:
|
|
762
|
+
`undefined` leaves the setting alone and `""` sends it empty.
|
|
763
|
+
|
|
764
|
+
```typescript
|
|
765
|
+
import { editChannel } from "@honeybbq/teamspeak";
|
|
766
|
+
|
|
767
|
+
await editChannel(client, cid, {
|
|
768
|
+
name: "Lounge",
|
|
769
|
+
topic: "", // sent empty: clears the topic
|
|
770
|
+
// password is not set: not sent, so the password stays as it is.
|
|
771
|
+
});
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
**Records.** In a record the server's answer fills in, an optional field is
|
|
775
|
+
`undefined` when the server did not send it. That is not the same as `0` or
|
|
776
|
+
`false`: `totalClients` is `undefined` in a `getChannelInfo` result, which does
|
|
777
|
+
not carry it, and `0` in a `listChannels` result of an empty channel.
|
|
778
|
+
|
|
779
|
+
```typescript
|
|
780
|
+
import { getChannelInfo } from "@honeybbq/teamspeak";
|
|
781
|
+
|
|
782
|
+
const info = await getChannelInfo(client, cid);
|
|
783
|
+
if (info.totalClients !== undefined) console.log(info.totalClients, "clients");
|
|
784
|
+
```
|
|
785
|
+
|
|
786
|
+
**Durations and times** are numbers of milliseconds with an `Ms` suffix
|
|
787
|
+
(`idleTimeMs`, `timeoutMs`) and `Date`. A `Date` field is `undefined` where the
|
|
788
|
+
server sends 0 for "never". The Go SDK uses `time.Duration` and `time.Time`.
|
|
789
|
+
|
|
790
|
+
### Events
|
|
791
|
+
|
|
792
|
+
Each server event has a typed name (`"textMessage"`, `"clientEnter"`, ...;
|
|
793
|
+
`EventName` has a constant for each), and `"notification"` delivers every
|
|
794
|
+
notification as the server sent it, including those no typed event models. The
|
|
795
|
+
notification arrives after the typed event it produced. `on()` returns a
|
|
796
|
+
function that removes the listener; `events(name, { signal })` is the same as an
|
|
797
|
+
async iterator.
|
|
798
|
+
|
|
799
|
+
```typescript
|
|
800
|
+
const off = client.on("textMessage", (message) => {
|
|
801
|
+
console.log(message.invokerName, message.message);
|
|
802
|
+
});
|
|
803
|
+
off();
|
|
804
|
+
|
|
805
|
+
client.on("notification", (n) => console.log(n.name, n.params));
|
|
806
|
+
```
|
|
807
|
+
|
|
808
|
+
- **Order.** Listeners run one at a time, in the order the events arrived, so a
|
|
809
|
+
listener may call the client and await an answer.
|
|
810
|
+
- **State.** The client updates its state, such as `client.view`, before it
|
|
811
|
+
queues the event that reports the change, so a listener sees state at least as
|
|
812
|
+
new as its event.
|
|
813
|
+
- **A throw** in a listener does not stop the others. The exception is rethrown
|
|
814
|
+
from a microtask, as an uncaught exception, as an EventEmitter listener's
|
|
815
|
+
would be. The Go SDK recovers a panicking handler and logs it instead.
|
|
816
|
+
- **Middleware.** `eventMiddleware` sees each server event before the listeners
|
|
817
|
+
and may pass it on, replace it or drop it; `connected`, `disconnected`,
|
|
818
|
+
`kicked` and the view events bypass it. `commandMiddleware` wraps the typed commands. Both are
|
|
819
|
+
described above.
|
|
820
|
+
|
|
821
|
+
### Stability
|
|
822
|
+
|
|
823
|
+
From 1.0 the package follows semver, as described under
|
|
824
|
+
[Compatibility](#compatibility): a breaking change to `@honeybbq/teamspeak`,
|
|
825
|
+
`/client` and `/versions` waits for 2.0. The enumerations, `ErrorCode` and the
|
|
826
|
+
event map may gain members in a minor release; give a `switch` over them a
|
|
827
|
+
default case.
|
|
828
|
+
|
|
829
|
+
An API that may still change in a minor release is marked `@experimental` (or, for
|
|
830
|
+
`@honeybbq/teamspeak/stream`, `@beta`) in its TSDoc, which editors and the API reference
|
|
831
|
+
show. Today that is the whole `@honeybbq/teamspeak/stream` entry point, and the whole
|
|
832
|
+
`@honeybbq/teamspeak/query` entry point, the server view (`client.view` and the
|
|
833
|
+
view events), the `previous` field of the client-left and channel-deleted
|
|
834
|
+
events, and `getServerVariables`.
|
|
835
|
+
|
|
836
|
+
### Server behaviour the SDK works around
|
|
837
|
+
|
|
838
|
+
The servers answer some commands in ways that differ between TeamSpeak 3 and 6
|
|
839
|
+
and between transports, and the SDK evens those out. The measured tables are in
|
|
840
|
+
[voice-protocol-responses.md](https://github.com/HoneyBBQ/teamspeak-js/blob/main/docs/voice-protocol-responses.md)
|
|
841
|
+
and [serverquery-responses.md](https://github.com/HoneyBBQ/teamspeak-js/blob/main/docs/serverquery-responses.md).
|
|
842
|
+
|
|
533
843
|
## ServerQuery
|
|
534
844
|
|
|
535
845
|
The `@experimental` subpath `@honeybbq/teamspeak/query` administers a server
|
|
@@ -733,6 +1043,7 @@ with the same semantics, apart from what the languages make differ:
|
|
|
733
1043
|
| `@honeybbq/teamspeak` | What every TeamSpeak connection shares: the command language, ids, errors, the typed commands, the event types and the status and reconnection types |
|
|
734
1044
|
| `@honeybbq/teamspeak/client` | The voice client, identities, file transfers, diagnostics and its retry policy |
|
|
735
1045
|
| `@honeybbq/teamspeak/versions` | Client versions a server accepts, for the client's `version` option; data only, one export each |
|
|
1046
|
+
| `@honeybbq/teamspeak/stream` | TeamSpeak 6 streams, the screen share: finding, publishing and watching them; signalling only, no WebRTC code; `@beta` |
|
|
736
1047
|
| `@honeybbq/teamspeak/query` | The ServerQuery client, running the same typed commands over raw TCP, SSH or WebQuery; `@experimental` |
|
|
737
1048
|
|
|
738
1049
|
Each entry exports only its own symbols. The client entry does not re-export
|
|
@@ -751,7 +1062,8 @@ get no further releases. Coming from 0.x? The
|
|
|
751
1062
|
|
|
752
1063
|
```
|
|
753
1064
|
src/
|
|
754
|
-
├── core/ # Shared: command language, ids, errors, typed commands
|
|
1065
|
+
├── core/ # Shared (@honeybbq/teamspeak): command language, ids, errors, typed commands
|
|
1066
|
+
├── versions/ # Client versions (@honeybbq/teamspeak/versions)
|
|
755
1067
|
├── client/ # The voice client (@honeybbq/teamspeak/client)
|
|
756
1068
|
│ ├── client.ts # Client lifecycle, connection management
|
|
757
1069
|
│ ├── commands.ts # Command sending and response tracking
|
|
@@ -767,7 +1079,8 @@ src/
|
|
|
767
1079
|
│ ├── handshake/ # Crypto handshake and license verification
|
|
768
1080
|
│ ├── transport/ # UDP packet framing, ACK, compression
|
|
769
1081
|
│ └── discovery/ # SRV / TSDNS / direct address resolution
|
|
770
|
-
|
|
1082
|
+
├── stream/ # TeamSpeak 6 streams (@honeybbq/teamspeak/stream)
|
|
1083
|
+
└── query/ # The ServerQuery client (@honeybbq/teamspeak/query)
|
|
771
1084
|
examples/
|
|
772
1085
|
└── connect.ts # Minimal connection example
|
|
773
1086
|
```
|
|
@@ -782,9 +1095,11 @@ Protocol knowledge was primarily informed by the [TSLib](https://github.com/Spla
|
|
|
782
1095
|
|
|
783
1096
|
## Disclaimer
|
|
784
1097
|
|
|
785
|
-
TeamSpeak is a registered trademark of [TeamSpeak Systems
|
|
1098
|
+
TeamSpeak is a registered trademark of [TeamSpeak Systems, Inc.](https://teamspeak.com/). This project is not affiliated with, endorsed by, or associated with TeamSpeak Systems, Inc. in any way.
|
|
1099
|
+
|
|
1100
|
+
This library is an **independent implementation** built from public documentation, observation of network traffic, and independent research. It contains no TeamSpeak proprietary code, headers, libraries, or binaries.
|
|
786
1101
|
|
|
787
|
-
|
|
1102
|
+
The project implements the client side only and does not implement a TeamSpeak server.
|
|
788
1103
|
|
|
789
1104
|
## Contributing
|
|
790
1105
|
|
|
@@ -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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"}
|
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":"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
|
|
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;AAmPD;;;;;;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;IA8GD;;;;;;;;;;;;;;;;;;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,GAAG,eAAe,CAAM,GAC5E,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,GAAG,eAAe,CAAM,GAC5E,OAAO,CAAC,IAAI,CAAC,CAKf;CA63BF"}
|