@honeybbq/teamspeak 1.0.0-beta.14 → 1.0.0-beta.16
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 +344 -5
- 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 +1 -1
- package/dist/client/index.js +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 +10 -7
- package/dist/core/api/channels.d.ts.map +1 -1
- package/dist/core/api/channels.js +10 -7
- package/dist/core/api/channels.js.map +1 -1
- package/dist/core/api/command.d.ts +11 -7
- package/dist/core/api/command.d.ts.map +1 -1
- package/dist/core/api/command.js +11 -7
- 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 +8 -1
- package/dist/core/api/files.d.ts.map +1 -1
- package/dist/core/api/files.js +8 -1
- package/dist/core/api/files.js.map +1 -1
- package/dist/core/api/groups.d.ts +21 -8
- package/dist/core/api/groups.d.ts.map +1 -1
- package/dist/core/api/groups.js +21 -8
- package/dist/core/api/groups.js.map +1 -1
- package/dist/core/api/server.d.ts +18 -6
- package/dist/core/api/server.d.ts.map +1 -1
- package/dist/core/api/server.js +18 -6
- 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/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 +9 -6
- package/dist/query/client.js.map +1 -1
- package/dist/query/late-rows.d.ts +6 -4
- package/dist/query/late-rows.d.ts.map +1 -1
- package/dist/query/late-rows.js +6 -4
- package/dist/query/late-rows.js.map +1 -1
- 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 +65 -0
- package/dist/stream/errors.d.ts.map +1 -0
- package/dist/stream/errors.js +120 -0
- package/dist/stream/errors.js.map +1 -0
- package/dist/stream/index.d.ts +26 -0
- package/dist/stream/index.d.ts.map +1 -0
- package/dist/stream/index.js +25 -0
- package/dist/stream/index.js.map +1 -0
- package/dist/stream/peer.d.ts +49 -0
- package/dist/stream/peer.d.ts.map +1 -0
- package/dist/stream/peer.js +115 -0
- package/dist/stream/peer.js.map +1 -0
- package/dist/stream/published.d.ts +56 -0
- package/dist/stream/published.d.ts.map +1 -0
- package/dist/stream/published.js +215 -0
- package/dist/stream/published.js.map +1 -0
- package/dist/stream/streams.d.ts +90 -0
- package/dist/stream/streams.d.ts.map +1 -0
- package/dist/stream/streams.js +670 -0
- package/dist/stream/streams.js.map +1 -0
- package/dist/stream/types.d.ts +431 -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 +93 -0
- package/dist/stream/wire.d.ts.map +1 -0
- package/dist/stream/wire.js +252 -0
- package/dist/stream/wire.js.map +1 -0
- package/package.json +13 -6
- package/src/client/commands.ts +4 -1
- package/src/client/index.ts +1 -1
- package/src/client/transport/generation-window.ts +11 -0
- package/src/client/transport/handler.ts +28 -2
- package/src/core/api/channels.ts +10 -7
- package/src/core/api/command.ts +11 -7
- package/src/core/api/credentials.ts +3 -2
- package/src/core/api/files.ts +8 -1
- package/src/core/api/groups.ts +21 -8
- package/src/core/api/server.ts +18 -6
- 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/notification-name.ts +16 -0
- package/src/query/client.ts +9 -6
- package/src/query/late-rows.ts +6 -4
- package/src/stream/emitter.ts +73 -0
- package/src/stream/errors.ts +138 -0
- package/src/stream/index.ts +62 -0
- package/src/stream/peer.ts +167 -0
- package/src/stream/published.ts +273 -0
- package/src/stream/streams.ts +764 -0
- package/src/stream/types.ts +486 -0
- package/src/stream/wait.ts +62 -0
- package/src/stream/wire.ts +324 -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
|
|
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
|
|
@@ -548,6 +549,339 @@ The uid hash matters because TS3 hashes a client's public key with SHA-1 and TS6
|
|
|
548
549
|
with SHA-256 — a client that assumes either one computes an identifier the other
|
|
549
550
|
server never sends.
|
|
550
551
|
|
|
552
|
+
### Streams
|
|
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 verified against the TeamSpeak 6 server
|
|
562
|
+
6.0.0-beta13.1 and the TeamSpeak 6 client 6.0.0-beta5 (macOS), and a TeamSpeak update may
|
|
563
|
+
break it. 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
|
+
// Always offer audio, even with no track to send (see the interop notes below).
|
|
607
|
+
if (!pc.getTransceivers().some((t) => t.receiver.track.kind === "audio")) {
|
|
608
|
+
pc.addTransceiver("audio", { direction: "sendonly" });
|
|
609
|
+
}
|
|
610
|
+
await pc.setLocalDescription(await pc.createOffer());
|
|
611
|
+
|
|
612
|
+
const viewer = await request.accept(pc.localDescription!.sdp);
|
|
613
|
+
pc.onicecandidate = (event) => {
|
|
614
|
+
if (event.candidate) void viewer.sendCandidate(event.candidate.toJSON());
|
|
615
|
+
};
|
|
616
|
+
viewer.on("answer", (sdp) => pc.setRemoteDescription({ type: "answer", sdp }));
|
|
617
|
+
viewer.on("candidate", (candidate) => void pc.addIceCandidate(candidate));
|
|
618
|
+
viewer.on("closed", () => pc.close());
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
await stream.stop();
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
A request that nobody has answered can end: the viewer withdraws it or goes out of view, or
|
|
625
|
+
the stream ends. `request.on("closed", ({ cause }) => …)` fires then, with `"withdrawn"` or
|
|
626
|
+
`"ended"`, so a UI can drop the request without polling `request.state`; `accept` and `deny`
|
|
627
|
+
then throw. It is not emitted for a request that was accepted or denied.
|
|
628
|
+
|
|
629
|
+
#### Interop with the TeamSpeak 6 client
|
|
630
|
+
|
|
631
|
+
What follows was observed with the TeamSpeak 6 client 6.0.0-beta5 (macOS) against a
|
|
632
|
+
TeamSpeak 6 server 6.0.0-beta13.1.
|
|
633
|
+
|
|
634
|
+
- **Offer audio, always.** When you publish, the client answers an offer only if it has an
|
|
635
|
+
`m=audio` section. For a video-only offer it never answers: it shows a fallback picture and
|
|
636
|
+
keeps the "Join" button, which then does nothing, because the server already counts the
|
|
637
|
+
viewer as joined. Add a send-only audio transceiver even when there is no audio track; an
|
|
638
|
+
audio section before the video section was accepted. `accept` and `ViewerPeer.sendOffer`
|
|
639
|
+
send an offer without audio unchanged, and emit a `"warning"` event on `Streams` with the
|
|
640
|
+
code `"offer-without-audio"`.
|
|
641
|
+
- **Trim the codecs.** The client's own offers carry one video and one audio section, about
|
|
642
|
+
3.9 KB. A browser's offer with every codec is about 6 KB with audio and video, and about
|
|
643
|
+
4.8 KB for video alone, against a limit of about 9.2 KB for a command line. Narrowing the
|
|
644
|
+
codecs keeps room for the candidates and the message:
|
|
645
|
+
|
|
646
|
+
```ts
|
|
647
|
+
const video = pc.addTransceiver(track, { direction: "sendonly" });
|
|
648
|
+
const codecs = RTCRtpSender.getCapabilities("video")!.codecs;
|
|
649
|
+
video.setCodecPreferences(codecs.filter((c) => /^video\/(VP8|H264)$/.test(c.mimeType)));
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
- **Accept every request.** A viewer in the client shows "waiting" until the publisher
|
|
653
|
+
answers. When it withdraws and asks again, that is a new request, with a new `JoinRequest`.
|
|
654
|
+
- **A channel switch ends the viewing, not the stream.** The client leaves a stream it
|
|
655
|
+
watches when it changes channel, so the publisher sees that viewer leave. The server does not end a stream when its publisher or a viewer changes channel.
|
|
656
|
+
- Denying, stopping, leaving, disconnecting and several viewers at once, some of them the
|
|
657
|
+
client and some the SDK, behaved as they do between SDK clients.
|
|
658
|
+
|
|
659
|
+
```ts
|
|
660
|
+
streams.on("warning", (w) => console.warn(`${w.code}: ${w.message}`));
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
Things to know:
|
|
664
|
+
|
|
665
|
+
- A client may run several streams, so every stream is known by its id. `list()` is what
|
|
666
|
+
this client has heard of. A stream that ends while its publisher is out of the channel is
|
|
667
|
+
reported to nobody there, and stays listed until the connection ends.
|
|
668
|
+
- A stream survives its publisher changing channel, with no event. It ends when the
|
|
669
|
+
publisher stops it or disconnects.
|
|
670
|
+
- The server drops a command line longer than about 9.2 KB without an answer. Streams
|
|
671
|
+
commands are checked first and throw a `StreamPayloadTooLargeError`; an SDP offer or an
|
|
672
|
+
ICE candidate is a few kilobytes at most.
|
|
673
|
+
- The server lets anyone stop any stream. `Streams` sends `stopstream` only for streams it
|
|
674
|
+
published, and treats a `notifystreamstopped` for any stream as final.
|
|
675
|
+
- The server remembers no decision, so a client can ask again, and every ask is relayed.
|
|
676
|
+
To refuse for good, pass `rememberDenied: true` (the 1024 most recent uids, in memory
|
|
677
|
+
only; a requester out of view is looked up with `clientgetuidfromclid`), or leave the
|
|
678
|
+
request unanswered. The server has no way to remove a viewer: closing a viewer's peer
|
|
679
|
+
ends your side of it, and stopping the stream ends it for everyone.
|
|
680
|
+
- `dispose()` stops the streams published through that `Streams` and leaves the ones it
|
|
681
|
+
watched, while the client is connected. A stream whose `publish` gave up after the command
|
|
682
|
+
went out is stopped when its announcement arrives, so none is left running unseen.
|
|
683
|
+
- A client whose `client_is_streaming` clears, or that comes into view not streaming, has
|
|
684
|
+
its streams dropped, after a grace of two seconds: `stopped` with the cause `"gone"`. The
|
|
685
|
+
server clears the flag right before it reports the stop, so a stop that arrives in that time
|
|
686
|
+
ends the stream with its own cause and reason instead, and a flag set again cancels the drop.
|
|
687
|
+
- The bitrate the server reports for a stream is not the one requested.
|
|
688
|
+
- A client announces that it streams only with `client_is_streaming`, and a stream that was
|
|
689
|
+
running before you came is not announced at all. `Streams` asks for the streams of each
|
|
690
|
+
client that sets the flag, once, on a TeamSpeak 6 server.
|
|
691
|
+
|
|
692
|
+
## Design
|
|
693
|
+
|
|
694
|
+
The conventions below hold across the whole package. Each is stated once here.
|
|
695
|
+
|
|
696
|
+
### Errors
|
|
697
|
+
|
|
698
|
+
An error is one of two kinds:
|
|
699
|
+
|
|
700
|
+
- **A refusal the server reports.** It is a `ServerError` carrying the server's
|
|
701
|
+
numeric `code`, its text as `serverMessage`, and `extraMessage` and
|
|
702
|
+
`failedPermissionID` when the server names them.
|
|
703
|
+
- **A condition the SDK detects itself**: not connected, no data, a timeout, a
|
|
704
|
+
failed handshake, a failed file transfer. Each has its own class.
|
|
705
|
+
|
|
706
|
+
Every error the SDK throws on purpose extends `TeamspeakError`, so
|
|
707
|
+
`instanceof TeamspeakError` tells its failures from anything else. A failure
|
|
708
|
+
that wraps another keeps it in `cause`. Match a refusal by `error.code` against
|
|
709
|
+
`ErrorCode`, and everything else by class:
|
|
710
|
+
|
|
711
|
+
```typescript
|
|
712
|
+
import {
|
|
713
|
+
ErrorCode,
|
|
714
|
+
NotConnectedError,
|
|
715
|
+
ServerError,
|
|
716
|
+
TeamspeakError,
|
|
717
|
+
asChannelID,
|
|
718
|
+
moveClient,
|
|
719
|
+
} from "@honeybbq/teamspeak";
|
|
720
|
+
|
|
721
|
+
try {
|
|
722
|
+
await moveClient(client, client.clientID, asChannelID(5), { password: "wrong" });
|
|
723
|
+
} catch (err) {
|
|
724
|
+
if (err instanceof ServerError && err.code === ErrorCode.ChannelInvalidPassword) {
|
|
725
|
+
// ask for the password again
|
|
726
|
+
} else if (err instanceof NotConnectedError) {
|
|
727
|
+
// the connection is gone
|
|
728
|
+
} else if (err instanceof ServerError) {
|
|
729
|
+
console.log(err.code, err.serverMessage, err.extraMessage, err.failedPermissionID);
|
|
730
|
+
} else if (err instanceof TeamspeakError) {
|
|
731
|
+
console.log(err.name, err.message, err.cause);
|
|
732
|
+
} else {
|
|
733
|
+
throw err;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
```
|
|
737
|
+
|
|
738
|
+
`ErrorCode` names the server's codes as constants. It is the table `errcode`
|
|
739
|
+
is in the Go SDK, with the same numbers. A server may send a code it does not
|
|
740
|
+
name, so `code` is a plain `number`. `instanceof` also works between two
|
|
741
|
+
installed copies of the package.
|
|
742
|
+
|
|
743
|
+
Errors live with the code that produces them, so each entry point exports only
|
|
744
|
+
the errors its own code throws:
|
|
745
|
+
|
|
746
|
+
| Entry point | Errors |
|
|
747
|
+
| -------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
748
|
+
| `@honeybbq/teamspeak` (shared by every connection) | `TeamspeakError`, `ServerError`, `CommandTimeoutError`, `NotConnectedError`, `AlreadyConnectedError`, `NoDataError` |
|
|
749
|
+
| `@honeybbq/teamspeak/client` (voice only) | `ConnectionTimeoutError`, `HandshakeError` and its `PuzzleTooHardError`, `InvalidAddressError`, `InvalidIdentityError`, `FileTransferError`, `RemovedError` (kick, ban or server stop) |
|
|
750
|
+
| `@honeybbq/teamspeak/stream` (streams only) | `StreamsUnsupportedError`, `StreamJoinDeniedError`, `StreamEndedError`, `StreamPayloadTooLargeError` |
|
|
751
|
+
| `@honeybbq/teamspeak/query` (query only) | `QueryAuthError`, `GuestAccessError`, `SshHostKeyError`, `MissingDependencyError`, `WebQueryHttpError` |
|
|
752
|
+
|
|
753
|
+
A call that is given a value it cannot use, such as an out-of-range id or an
|
|
754
|
+
option that contradicts another, throws the language's own `TypeError` or
|
|
755
|
+
`RangeError` before anything is sent. Those are not `TeamspeakError`s: they are
|
|
756
|
+
a bug in the caller, not a condition to handle. `WebQueryHttpError` carries the
|
|
757
|
+
HTTP `status`; a refusal the server itself reports is a `ServerError` over every
|
|
758
|
+
transport.
|
|
759
|
+
|
|
760
|
+
**Cancellation.** An aborted `signal` is the caller's own cancellation, not an
|
|
761
|
+
SDK error. `connect()`, the typed commands, `exec`, file transfers and `events()`
|
|
762
|
+
reject with the signal's `reason` itself, unwrapped: a `DOMException` named
|
|
763
|
+
`AbortError` after a bare `abort()`, a `TimeoutError` after
|
|
764
|
+
`AbortSignal.timeout()`, or whatever value was passed to `abort(reason)`. A
|
|
765
|
+
signal that is already aborted rejects before anything is sent. Aborting a
|
|
766
|
+
`connect()` abandons the attempt and leaves the client disconnected; a command
|
|
767
|
+
that was given up on may still have taken effect on the server. The signal and
|
|
768
|
+
the command's own `timeoutMs` are separate: when `timeoutMs` (ten seconds by
|
|
769
|
+
default) passes first, the command rejects with a `CommandTimeoutError`.
|
|
770
|
+
|
|
771
|
+
**No data.** `NoDataError` means the command succeeded but its answer lacked the
|
|
772
|
+
data it exists to return, such as the id of a channel just created. Treat the
|
|
773
|
+
command as having taken effect and do not retry it blindly, or it creates a
|
|
774
|
+
second object or is refused as a duplicate. `createChannel` and the group
|
|
775
|
+
commands look the new object up themselves and throw it only when nothing
|
|
776
|
+
matches. A list that comes back empty is `[]`, not this error.
|
|
777
|
+
|
|
778
|
+
### Ids
|
|
779
|
+
|
|
780
|
+
Every kind of id is a branded type: `ChannelID`, `ClientID`, `ClientDBID`,
|
|
781
|
+
`ServerGroupID`, `ChannelGroupID`, `PermissionID`, `IconID`, `ServerID`,
|
|
782
|
+
`BanID` and the rest, with `UID` and `ChannelUID` as branded strings. The
|
|
783
|
+
brand costs nothing at runtime, and passing the wrong kind is a compile error.
|
|
784
|
+
Make one with its constructor, which throws a `RangeError` for a value outside
|
|
785
|
+
the id's wire range:
|
|
786
|
+
|
|
787
|
+
```typescript
|
|
788
|
+
import { asChannelID, asClientID, moveClient } from "@honeybbq/teamspeak";
|
|
789
|
+
|
|
790
|
+
const cid = asChannelID(5); // or asChannelID(5n)
|
|
791
|
+
await moveClient(client, client.clientID, cid);
|
|
792
|
+
// moveClient(client, cid, client.clientID) does not compile.
|
|
793
|
+
const clid = asClientID(3);
|
|
794
|
+
```
|
|
795
|
+
|
|
796
|
+
A `ClientID`, `PermissionID`, `IconID` and the file transfer ids are `number`s.
|
|
797
|
+
The ones the protocol makes 64 bits wide are `bigint`s: `ChannelID`,
|
|
798
|
+
`ClientDBID`, `ServerGroupID`, `ChannelGroupID`, `ServerID`, `BanID`,
|
|
799
|
+
`MessageID` and `APIKeyID`. Their constructors take a `number` or a `bigint`.
|
|
800
|
+
|
|
801
|
+
### Options and absent values
|
|
802
|
+
|
|
803
|
+
**Options.** Required arguments are positional. Everything optional is one
|
|
804
|
+
options object, the last argument. A field that is not set is not sent:
|
|
805
|
+
`undefined` leaves the setting alone and `""` sends it empty.
|
|
806
|
+
|
|
807
|
+
```typescript
|
|
808
|
+
import { editChannel } from "@honeybbq/teamspeak";
|
|
809
|
+
|
|
810
|
+
await editChannel(client, cid, {
|
|
811
|
+
name: "Lounge",
|
|
812
|
+
topic: "", // sent empty: clears the topic
|
|
813
|
+
// password is not set: not sent, so the password stays as it is.
|
|
814
|
+
});
|
|
815
|
+
```
|
|
816
|
+
|
|
817
|
+
**Records.** In a record the server's answer fills in, an optional field is
|
|
818
|
+
`undefined` when the server did not send it. That is not the same as `0` or
|
|
819
|
+
`false`: `totalClients` is `undefined` in a `getChannelInfo` result, which does
|
|
820
|
+
not carry it, and `0` in a `listChannels` result of an empty channel.
|
|
821
|
+
|
|
822
|
+
```typescript
|
|
823
|
+
import { getChannelInfo } from "@honeybbq/teamspeak";
|
|
824
|
+
|
|
825
|
+
const info = await getChannelInfo(client, cid);
|
|
826
|
+
if (info.totalClients !== undefined) console.log(info.totalClients, "clients");
|
|
827
|
+
```
|
|
828
|
+
|
|
829
|
+
**Durations and times** are numbers of milliseconds with an `Ms` suffix
|
|
830
|
+
(`idleTimeMs`, `timeoutMs`) and `Date`. A `Date` field is `undefined` where the
|
|
831
|
+
server sends 0 for "never". The Go SDK uses `time.Duration` and `time.Time`.
|
|
832
|
+
|
|
833
|
+
### Events
|
|
834
|
+
|
|
835
|
+
Each server event has a typed name (`"textMessage"`, `"clientEnter"`, ...;
|
|
836
|
+
`EventName` has a constant for each), and `"notification"` delivers every
|
|
837
|
+
notification as the server sent it, including those no typed event models. The
|
|
838
|
+
notification arrives after the typed event it produced. `on()` returns a
|
|
839
|
+
function that removes the listener; `events(name, { signal })` is the same as an
|
|
840
|
+
async iterator.
|
|
841
|
+
|
|
842
|
+
```typescript
|
|
843
|
+
const off = client.on("textMessage", (message) => {
|
|
844
|
+
console.log(message.invokerName, message.message);
|
|
845
|
+
});
|
|
846
|
+
off();
|
|
847
|
+
|
|
848
|
+
client.on("notification", (n) => console.log(n.name, n.params));
|
|
849
|
+
```
|
|
850
|
+
|
|
851
|
+
- **Order.** Listeners run one at a time, in the order the events arrived, so a
|
|
852
|
+
listener may call the client and await an answer.
|
|
853
|
+
- **State.** The client updates its state, such as `client.view`, before it
|
|
854
|
+
queues the event that reports the change, so a listener sees state at least as
|
|
855
|
+
new as its event.
|
|
856
|
+
- **A throw** in a listener does not stop the others. The exception is rethrown
|
|
857
|
+
from a microtask, as an uncaught exception, as an EventEmitter listener's
|
|
858
|
+
would be. The Go SDK recovers a panicking handler and logs it instead.
|
|
859
|
+
- **Middleware.** `eventMiddleware` sees each server event before the listeners
|
|
860
|
+
and may pass it on, replace it or drop it; `connected`, `disconnected`,
|
|
861
|
+
`kicked` and the view events bypass it. `commandMiddleware` wraps the typed commands. Both are
|
|
862
|
+
described above.
|
|
863
|
+
|
|
864
|
+
### Stability
|
|
865
|
+
|
|
866
|
+
From 1.0 the package follows semver, as described under
|
|
867
|
+
[Compatibility](#compatibility): a breaking change to `@honeybbq/teamspeak`,
|
|
868
|
+
`/client` and `/versions` waits for 2.0. The enumerations, `ErrorCode` and the
|
|
869
|
+
event map may gain members in a minor release; give a `switch` over them a
|
|
870
|
+
default case.
|
|
871
|
+
|
|
872
|
+
An API that may still change in a minor release is marked `@experimental` in its TSDoc,
|
|
873
|
+
which editors and the API reference show. Today that is the whole
|
|
874
|
+
`@honeybbq/teamspeak/query` entry point, the server view (`client.view` and the
|
|
875
|
+
view events), the `previous` field of the client-left and channel-deleted
|
|
876
|
+
events, and `getServerVariables`.
|
|
877
|
+
|
|
878
|
+
### Server behaviour the SDK works around
|
|
879
|
+
|
|
880
|
+
The servers answer some commands in ways that differ between TeamSpeak 3 and 6
|
|
881
|
+
and between transports, and the SDK evens those out. The measured tables are in
|
|
882
|
+
[voice-protocol-responses.md](https://github.com/HoneyBBQ/teamspeak-js/blob/main/docs/voice-protocol-responses.md)
|
|
883
|
+
and [serverquery-responses.md](https://github.com/HoneyBBQ/teamspeak-js/blob/main/docs/serverquery-responses.md).
|
|
884
|
+
|
|
551
885
|
## ServerQuery
|
|
552
886
|
|
|
553
887
|
The `@experimental` subpath `@honeybbq/teamspeak/query` administers a server
|
|
@@ -751,6 +1085,7 @@ with the same semantics, apart from what the languages make differ:
|
|
|
751
1085
|
| `@honeybbq/teamspeak` | What every TeamSpeak connection shares: the command language, ids, errors, the typed commands, the event types and the status and reconnection types |
|
|
752
1086
|
| `@honeybbq/teamspeak/client` | The voice client, identities, file transfers, diagnostics and its retry policy |
|
|
753
1087
|
| `@honeybbq/teamspeak/versions` | Client versions a server accepts, for the client's `version` option; data only, one export each |
|
|
1088
|
+
| `@honeybbq/teamspeak/stream` | TeamSpeak 6 streams, the screen share: finding, publishing and watching them; signalling only, no WebRTC code |
|
|
754
1089
|
| `@honeybbq/teamspeak/query` | The ServerQuery client, running the same typed commands over raw TCP, SSH or WebQuery; `@experimental` |
|
|
755
1090
|
|
|
756
1091
|
Each entry exports only its own symbols. The client entry does not re-export
|
|
@@ -769,7 +1104,8 @@ get no further releases. Coming from 0.x? The
|
|
|
769
1104
|
|
|
770
1105
|
```
|
|
771
1106
|
src/
|
|
772
|
-
├── core/ # Shared: command language, ids, errors, typed commands
|
|
1107
|
+
├── core/ # Shared (@honeybbq/teamspeak): command language, ids, errors, typed commands
|
|
1108
|
+
├── versions/ # Client versions (@honeybbq/teamspeak/versions)
|
|
773
1109
|
├── client/ # The voice client (@honeybbq/teamspeak/client)
|
|
774
1110
|
│ ├── client.ts # Client lifecycle, connection management
|
|
775
1111
|
│ ├── commands.ts # Command sending and response tracking
|
|
@@ -785,7 +1121,8 @@ src/
|
|
|
785
1121
|
│ ├── handshake/ # Crypto handshake and license verification
|
|
786
1122
|
│ ├── transport/ # UDP packet framing, ACK, compression
|
|
787
1123
|
│ └── discovery/ # SRV / TSDNS / direct address resolution
|
|
788
|
-
|
|
1124
|
+
├── stream/ # TeamSpeak 6 streams (@honeybbq/teamspeak/stream)
|
|
1125
|
+
└── query/ # The ServerQuery client (@honeybbq/teamspeak/query)
|
|
789
1126
|
examples/
|
|
790
1127
|
└── connect.ts # Minimal connection example
|
|
791
1128
|
```
|
|
@@ -800,9 +1137,11 @@ Protocol knowledge was primarily informed by the [TSLib](https://github.com/Spla
|
|
|
800
1137
|
|
|
801
1138
|
## Disclaimer
|
|
802
1139
|
|
|
803
|
-
TeamSpeak is a registered trademark of [TeamSpeak Systems
|
|
1140
|
+
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.
|
|
1141
|
+
|
|
1142
|
+
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.
|
|
804
1143
|
|
|
805
|
-
|
|
1144
|
+
The project implements the client side only and does not implement a TeamSpeak server.
|
|
806
1145
|
|
|
807
1146
|
## Contributing
|
|
808
1147
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/client/commands.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,KAAK,GAAG,IAAI,CAAC;IAClB,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACrC;AAsBD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,cAAc;;IAOzB,6EAA6E;IAC7E,YAAY,WAAW,GAAE,MAAM,MAAgB,EAE9C;IAED,QAAQ,CACN,IAAI,CAAC,EAAE,YAAY,EACnB,WAAW,CAAC,EAAE,MAAM,EACpB,IAAI,CAAC,EAAE,MAAM,GACZ,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/client/commands.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,KAAK,GAAG,IAAI,CAAC;IAClB,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACrC;AAsBD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,cAAc;;IAOzB,6EAA6E;IAC7E,YAAY,WAAW,GAAE,MAAM,MAAgB,EAE9C;IAED,QAAQ,CACN,IAAI,CAAC,EAAE,YAAY,EACnB,WAAW,CAAC,EAAE,MAAM,EACpB,IAAI,CAAC,EAAE,MAAM,GACZ,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC,CAS/C;IAED,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAE3B;IAED,2EAA2E;IAC3E,qBAAqB,IAAI,IAAI,CAG5B;IAED,oEAAoE;IACpE,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAM7B;IAED,wEAAwE;IACxE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,SAAK,GAAG,IAAI,CAIxD;IAED;;;;;OAKG;IACH,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAiBlE;IAED,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,GAAG,IAAI,GAAG,IAAI,CAkB3C;IAED,aAAa,IAAI,IAAI,CAEpB;IAED;;;;;OAKG;IACH,KAAK,CAAC,GAAG,EAAE,KAAK,GAAG,IAAI,CAQtB;CAkBF;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAMhE"}
|
package/dist/client/commands.js
CHANGED
|
@@ -35,6 +35,9 @@ export class CommandTracker {
|
|
|
35
35
|
register(spec, correlation, echo) {
|
|
36
36
|
this.#nextRC++;
|
|
37
37
|
const rc = this.#nextRC;
|
|
38
|
+
// The tracker's own number is the one request field no caller can supply.
|
|
39
|
+
if (spec?.correlated?.key === "return_code")
|
|
40
|
+
correlation = String(rc);
|
|
38
41
|
const promise = new Promise((resolve) => {
|
|
39
42
|
this.#pending.set(rc, { resolve, spec, correlation, echo, correlated: [] });
|
|
40
43
|
});
|
|
@@ -127,7 +130,8 @@ export class CommandTracker {
|
|
|
127
130
|
p.resolve({ err, data: [] });
|
|
128
131
|
this.#buffer = [];
|
|
129
132
|
this.#welcomeComplete = false;
|
|
130
|
-
|
|
133
|
+
// Not restarted: a row still in flight from before the reset carries an old
|
|
134
|
+
// number, which a restarted counter would hand to an unrelated command.
|
|
131
135
|
}
|
|
132
136
|
#answers(p, row) {
|
|
133
137
|
if (row.source === "")
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"commands.js","sourceRoot":"","sources":["../../src/client/commands.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"commands.js","sourceRoot":"","sources":["../../src/client/commands.ts"],"names":[],"mappings":"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"}
|
package/dist/client/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* An independent TeamSpeak client: it connects over the voice protocol, the way
|
|
3
3
|
* the TeamSpeak application does.
|
|
4
4
|
*
|
|
5
5
|
* Only what belongs to the voice protocol is exported here. The typed commands,
|
package/dist/client/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* An independent TeamSpeak client: it connects over the voice protocol, the way
|
|
3
3
|
* the TeamSpeak application does.
|
|
4
4
|
*
|
|
5
5
|
* Only what belongs to the voice protocol is exported here. The typed commands,
|
|
@@ -4,6 +4,12 @@ export declare class GenerationWindow {
|
|
|
4
4
|
get generation(): number;
|
|
5
5
|
advance(amount: number): void;
|
|
6
6
|
advanceToExcluded(mappedValue: number): void;
|
|
7
|
+
/**
|
|
8
|
+
* Move the base to just past a packet that was just received. A stale packet,
|
|
9
|
+
* including one from the generation before a wrap, does not move the base
|
|
10
|
+
* backwards or treat the wait for the next generation as a full cycle.
|
|
11
|
+
*/
|
|
12
|
+
advanceAfter(mappedValue: number): void;
|
|
7
13
|
syncTo(mappedValue: number): void;
|
|
8
14
|
isInWindow(mappedValue: number): boolean;
|
|
9
15
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generation-window.d.ts","sourceRoot":"","sources":["../../../src/client/transport/generation-window.ts"],"names":[],"mappings":"AAAA,qBAAa,gBAAgB;;IAM3B,YAAY,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAG1C;IAED,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAQ5B;IAED,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAI3C;IAED,MAAM,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAIhC;IAED,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAMvC;IAED;;;;OAIG;IACH,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAMzC;IAgBD;;;;OAIG;IACH,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAExC;IAED,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAE3C;IASD;;;;OAIG;IACH,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAIzC;IAED,KAAK,IAAI,IAAI,CAGZ;CACF"}
|
|
1
|
+
{"version":3,"file":"generation-window.d.ts","sourceRoot":"","sources":["../../../src/client/transport/generation-window.ts"],"names":[],"mappings":"AAAA,qBAAa,gBAAgB;;IAM3B,YAAY,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAG1C;IAED,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAQ5B;IAED,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAI3C;IAED;;;;OAIG;IACH,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAItC;IAED,MAAM,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAIhC;IAED,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAMvC;IAED;;;;OAIG;IACH,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAMzC;IAgBD;;;;OAIG;IACH,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAExC;IAED,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAE3C;IASD;;;;OAIG;IACH,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAIzC;IAED,KAAK,IAAI,IAAI,CAGZ;CACF"}
|
|
@@ -26,6 +26,17 @@ export class GenerationWindow {
|
|
|
26
26
|
moveDist += this.#mod;
|
|
27
27
|
this.advance(moveDist + 1);
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Move the base to just past a packet that was just received. A stale packet,
|
|
31
|
+
* including one from the generation before a wrap, does not move the base
|
|
32
|
+
* backwards or treat the wait for the next generation as a full cycle.
|
|
33
|
+
*/
|
|
34
|
+
advanceAfter(mappedValue) {
|
|
35
|
+
const index = this.mappedToIndex(mappedValue);
|
|
36
|
+
if (index < 0)
|
|
37
|
+
return;
|
|
38
|
+
this.advance(index + 1);
|
|
39
|
+
}
|
|
29
40
|
syncTo(mappedValue) {
|
|
30
41
|
let moveDist = mappedValue - this.#mappedBaseOffset;
|
|
31
42
|
if (moveDist < 0)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generation-window.js","sourceRoot":"","sources":["../../../src/client/transport/generation-window.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"generation-window.js","sourceRoot":"","sources":["../../../src/client/transport/generation-window.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../src/client/transport/handler.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAElD,OAAO,EACL,KAAK,MAAM,EACX,UAAU,EAMX,MAAM,aAAa,CAAC;AAMrB,OAAO,EAAoC,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../src/client/transport/handler.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAElD,OAAO,EACL,KAAK,MAAM,EACX,UAAU,EAMX,MAAM,aAAa,CAAC;AAMrB,OAAO,EAAoC,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AA8BtF;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,EAAE,MAAa,CAAC;AAE9C;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,EAAE,MAAgB,CAAC;AA4BhD,qBAAa,aAAa;;IACxB,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,IAAI,CAAQ;IAC9C,QAAQ,EAAE,CAAC,CAAC,GAAG,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,CAAC,GAAG,IAAI,CAAQ;IACtD;;;;OAIG;IACH,cAAc,EAAE,CAAC,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC,GAAG,IAAI,CAAQ;IACzD,2FAA2F;IAC3F,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,cAAc,KAAK,IAAI,CAAC,GAAG,IAAI,CAAQ;IAkDzD,YAAY,KAAK,EAAE,KAAK,EAAE,MAAM,GAAE,MAAmB,EAAE,aAAa,CAAC,EAAE,aAAa,EAInF;IAED,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAE5B;IAED;;;;;;;OAOG;IACG,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAyD/D;IAOD,sGAAsG;IACtG,KAAK,CAAC,IAAI,EAAE,cAAc,GAAG,IAAI,CA+ChC;IAED;;;;;;OAMG;IACH,oBAAoB,IAAI,IAAI,CAE3B;IAED,UAAU,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAOnE;IAED,eAAe,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CA6BrD;IAED;;;;OAIG;IACH,iBAAiB,CACf,IAAI,EAAE,UAAU,EAChB,eAAe,EAAE,SAAS,MAAM,EAAE,EAClC,KAAK,EAAE,MAAM,EACb,gBAAgB,GAAE,SAAS,MAAM,EAAO,GACvC,IAAI,CAiCN;IAED;;;;OAIG;IACH,sBAAsB,CACpB,IAAI,EAAE,UAAU,EAChB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,MAAM,GACZ,IAAI,CAiBN;IA4BD,wEAAwE;IACxE,eAAe,IAAI,eAAe,CAEjC;IAED,oEAAoE;IACpE,IAAI,SAAS,IAAI;QAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAEhF;IAED,4EAA4E;IAC5E,iFAAiF;IACjF,IAAI,eAAe,IAAI,OAAO,CAE7B;IAED,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED,iDAAiD;IACjD,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,KAAK,IAAI,IAAI,CAmBZ;CA8hBF"}
|
|
@@ -12,6 +12,12 @@ import { RoundTripEstimator, TrafficMeter } from "./traffic.js";
|
|
|
12
12
|
import { processInit1 } from "../handshake/crypt-handshake.js";
|
|
13
13
|
const MAX_OUT_PACKET_SIZE = 500;
|
|
14
14
|
const RECEIVE_PACKET_WINDOW_SIZE = 1024;
|
|
15
|
+
// Voice, whisper, ping and pong are never sent again, so any number of them can
|
|
16
|
+
// be lost in a row. Their window spans half the id space: a packet's generation
|
|
17
|
+
// is the one that puts it nearest the last packet received, as long as fewer
|
|
18
|
+
// than 32768 were lost, where a narrower window would place the first packet
|
|
19
|
+
// after a long loss across the wrap in the old generation and never recover.
|
|
20
|
+
const UNACKED_RECEIVE_WINDOW_SIZE = 1 << 15;
|
|
15
21
|
const PING_INTERVAL_MS = 5_000;
|
|
16
22
|
const PACKET_TIMEOUT_MS = 60_000;
|
|
17
23
|
const MAX_RETRY_INTERVAL_MS = 1_000;
|
|
@@ -87,6 +93,10 @@ export class PacketHandler {
|
|
|
87
93
|
#generationCounter = new Uint32Array(9);
|
|
88
94
|
#recvWindowCommand = new GenerationWindow(1 << 16, RECEIVE_PACKET_WINDOW_SIZE);
|
|
89
95
|
#recvWindowCommandLow = new GenerationWindow(1 << 16, RECEIVE_PACKET_WINDOW_SIZE);
|
|
96
|
+
#recvWindowVoice = new GenerationWindow(1 << 16, UNACKED_RECEIVE_WINDOW_SIZE);
|
|
97
|
+
#recvWindowVoiceWhisper = new GenerationWindow(1 << 16, UNACKED_RECEIVE_WINDOW_SIZE);
|
|
98
|
+
#recvWindowPing = new GenerationWindow(1 << 16, UNACKED_RECEIVE_WINDOW_SIZE);
|
|
99
|
+
#recvWindowPong = new GenerationWindow(1 << 16, UNACKED_RECEIVE_WINDOW_SIZE);
|
|
90
100
|
#sendWindowCommand = new GenerationWindow(1 << 16, RECEIVE_PACKET_WINDOW_SIZE);
|
|
91
101
|
#sendWindowCommandLow = new GenerationWindow(1 << 16, RECEIVE_PACKET_WINDOW_SIZE);
|
|
92
102
|
#commandQueue = new Map();
|
|
@@ -505,11 +515,12 @@ export class PacketHandler {
|
|
|
505
515
|
const decrypted = this.#decryptPacketData(p, header, ciphertext, tag);
|
|
506
516
|
if (decrypted === null)
|
|
507
517
|
return;
|
|
518
|
+
const pType = packetType(p);
|
|
519
|
+
this.#advanceReceiveGeneration(pType, p.id);
|
|
508
520
|
p.data = decrypted.plaintext;
|
|
509
521
|
// Only a packet that decrypted shows the server is there: anyone can send
|
|
510
522
|
// datagrams that do not, and they must not keep a dead session alive.
|
|
511
523
|
this.#lastMessageReceived = performance.now();
|
|
512
|
-
const pType = packetType(p);
|
|
513
524
|
if (pType === PacketType.Pong && p.data.length >= 2) {
|
|
514
525
|
const acked = new DataView(p.data.buffer, p.data.byteOffset).getUint16(0, false);
|
|
515
526
|
const sentAt = this.#pingsSent.get(acked);
|
|
@@ -538,14 +549,33 @@ export class PacketHandler {
|
|
|
538
549
|
case PacketType.AckLow:
|
|
539
550
|
return this.#sendWindowCommandLow.getGeneration(id);
|
|
540
551
|
case PacketType.Voice:
|
|
552
|
+
return this.#recvWindowVoice.getGeneration(id);
|
|
541
553
|
case PacketType.VoiceWhisper:
|
|
554
|
+
return this.#recvWindowVoiceWhisper.getGeneration(id);
|
|
542
555
|
case PacketType.Ping:
|
|
556
|
+
return this.#recvWindowPing.getGeneration(id);
|
|
543
557
|
case PacketType.Pong:
|
|
558
|
+
return this.#recvWindowPong.getGeneration(id);
|
|
544
559
|
case PacketType.Init1:
|
|
545
560
|
default:
|
|
546
561
|
return 0;
|
|
547
562
|
}
|
|
548
563
|
}
|
|
564
|
+
/**
|
|
565
|
+
* Follow the packet id of an authenticated non-command packet. Voice has no
|
|
566
|
+
* retransmission or acknowledgement window, but its 16-bit id still wraps
|
|
567
|
+
* and changes the generation used by the encryption key.
|
|
568
|
+
*/
|
|
569
|
+
#advanceReceiveGeneration(pType, id) {
|
|
570
|
+
if (pType === PacketType.Voice)
|
|
571
|
+
this.#recvWindowVoice.advanceAfter(id);
|
|
572
|
+
if (pType === PacketType.VoiceWhisper)
|
|
573
|
+
this.#recvWindowVoiceWhisper.advanceAfter(id);
|
|
574
|
+
if (pType === PacketType.Ping)
|
|
575
|
+
this.#recvWindowPing.advanceAfter(id);
|
|
576
|
+
if (pType === PacketType.Pong)
|
|
577
|
+
this.#recvWindowPong.advanceAfter(id);
|
|
578
|
+
}
|
|
549
579
|
#decryptPacketData(p, header, ciphertext, tag) {
|
|
550
580
|
const unencrypted = (packetFlags(p) & PacketFlags.Unencrypted) !== 0;
|
|
551
581
|
const dummy = !this.#crypt.cryptoInitComplete;
|