@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.
Files changed (127) hide show
  1. package/README.md +344 -5
  2. package/dist/client/commands.d.ts.map +1 -1
  3. package/dist/client/commands.js +5 -1
  4. package/dist/client/commands.js.map +1 -1
  5. package/dist/client/index.d.ts +1 -1
  6. package/dist/client/index.js +1 -1
  7. package/dist/client/transport/generation-window.d.ts +6 -0
  8. package/dist/client/transport/generation-window.d.ts.map +1 -1
  9. package/dist/client/transport/generation-window.js +11 -0
  10. package/dist/client/transport/generation-window.js.map +1 -1
  11. package/dist/client/transport/handler.d.ts.map +1 -1
  12. package/dist/client/transport/handler.js +31 -1
  13. package/dist/client/transport/handler.js.map +1 -1
  14. package/dist/core/api/channels.d.ts +10 -7
  15. package/dist/core/api/channels.d.ts.map +1 -1
  16. package/dist/core/api/channels.js +10 -7
  17. package/dist/core/api/channels.js.map +1 -1
  18. package/dist/core/api/command.d.ts +11 -7
  19. package/dist/core/api/command.d.ts.map +1 -1
  20. package/dist/core/api/command.js +11 -7
  21. package/dist/core/api/command.js.map +1 -1
  22. package/dist/core/api/credentials.d.ts +3 -2
  23. package/dist/core/api/credentials.d.ts.map +1 -1
  24. package/dist/core/api/credentials.js +3 -2
  25. package/dist/core/api/credentials.js.map +1 -1
  26. package/dist/core/api/files.d.ts +8 -1
  27. package/dist/core/api/files.d.ts.map +1 -1
  28. package/dist/core/api/files.js +8 -1
  29. package/dist/core/api/files.js.map +1 -1
  30. package/dist/core/api/groups.d.ts +21 -8
  31. package/dist/core/api/groups.d.ts.map +1 -1
  32. package/dist/core/api/groups.js +21 -8
  33. package/dist/core/api/groups.js.map +1 -1
  34. package/dist/core/api/server.d.ts +18 -6
  35. package/dist/core/api/server.d.ts.map +1 -1
  36. package/dist/core/api/server.js +18 -6
  37. package/dist/core/api/server.js.map +1 -1
  38. package/dist/core/command/responses.d.ts +8 -3
  39. package/dist/core/command/responses.d.ts.map +1 -1
  40. package/dist/core/command/responses.js +11 -3
  41. package/dist/core/command/responses.js.map +1 -1
  42. package/dist/core/command-name.d.ts +12 -0
  43. package/dist/core/command-name.d.ts.map +1 -1
  44. package/dist/core/command-name.js +12 -0
  45. package/dist/core/command-name.js.map +1 -1
  46. package/dist/core/errorcode.d.ts +8 -0
  47. package/dist/core/errorcode.d.ts.map +1 -1
  48. package/dist/core/errorcode.js +8 -0
  49. package/dist/core/errorcode.js.map +1 -1
  50. package/dist/core/errors.d.ts +7 -6
  51. package/dist/core/errors.d.ts.map +1 -1
  52. package/dist/core/errors.js +7 -6
  53. package/dist/core/errors.js.map +1 -1
  54. package/dist/core/notification-name.d.ts +16 -0
  55. package/dist/core/notification-name.d.ts.map +1 -1
  56. package/dist/core/notification-name.js +16 -0
  57. package/dist/core/notification-name.js.map +1 -1
  58. package/dist/query/client.d.ts.map +1 -1
  59. package/dist/query/client.js +9 -6
  60. package/dist/query/client.js.map +1 -1
  61. package/dist/query/late-rows.d.ts +6 -4
  62. package/dist/query/late-rows.d.ts.map +1 -1
  63. package/dist/query/late-rows.js +6 -4
  64. package/dist/query/late-rows.js.map +1 -1
  65. package/dist/stream/emitter.d.ts +18 -0
  66. package/dist/stream/emitter.d.ts.map +1 -0
  67. package/dist/stream/emitter.js +66 -0
  68. package/dist/stream/emitter.js.map +1 -0
  69. package/dist/stream/errors.d.ts +65 -0
  70. package/dist/stream/errors.d.ts.map +1 -0
  71. package/dist/stream/errors.js +120 -0
  72. package/dist/stream/errors.js.map +1 -0
  73. package/dist/stream/index.d.ts +26 -0
  74. package/dist/stream/index.d.ts.map +1 -0
  75. package/dist/stream/index.js +25 -0
  76. package/dist/stream/index.js.map +1 -0
  77. package/dist/stream/peer.d.ts +49 -0
  78. package/dist/stream/peer.d.ts.map +1 -0
  79. package/dist/stream/peer.js +115 -0
  80. package/dist/stream/peer.js.map +1 -0
  81. package/dist/stream/published.d.ts +56 -0
  82. package/dist/stream/published.d.ts.map +1 -0
  83. package/dist/stream/published.js +215 -0
  84. package/dist/stream/published.js.map +1 -0
  85. package/dist/stream/streams.d.ts +90 -0
  86. package/dist/stream/streams.d.ts.map +1 -0
  87. package/dist/stream/streams.js +670 -0
  88. package/dist/stream/streams.js.map +1 -0
  89. package/dist/stream/types.d.ts +431 -0
  90. package/dist/stream/types.d.ts.map +1 -0
  91. package/dist/stream/types.js +2 -0
  92. package/dist/stream/types.js.map +1 -0
  93. package/dist/stream/wait.d.ts +14 -0
  94. package/dist/stream/wait.d.ts.map +1 -0
  95. package/dist/stream/wait.js +46 -0
  96. package/dist/stream/wait.js.map +1 -0
  97. package/dist/stream/wire.d.ts +93 -0
  98. package/dist/stream/wire.d.ts.map +1 -0
  99. package/dist/stream/wire.js +252 -0
  100. package/dist/stream/wire.js.map +1 -0
  101. package/package.json +13 -6
  102. package/src/client/commands.ts +4 -1
  103. package/src/client/index.ts +1 -1
  104. package/src/client/transport/generation-window.ts +11 -0
  105. package/src/client/transport/handler.ts +28 -2
  106. package/src/core/api/channels.ts +10 -7
  107. package/src/core/api/command.ts +11 -7
  108. package/src/core/api/credentials.ts +3 -2
  109. package/src/core/api/files.ts +8 -1
  110. package/src/core/api/groups.ts +21 -8
  111. package/src/core/api/server.ts +18 -6
  112. package/src/core/command/responses.ts +14 -3
  113. package/src/core/command-name.ts +12 -0
  114. package/src/core/errorcode.ts +8 -0
  115. package/src/core/errors.ts +7 -6
  116. package/src/core/notification-name.ts +16 -0
  117. package/src/query/client.ts +9 -6
  118. package/src/query/late-rows.ts +6 -4
  119. package/src/stream/emitter.ts +73 -0
  120. package/src/stream/errors.ts +138 -0
  121. package/src/stream/index.ts +62 -0
  122. package/src/stream/peer.ts +167 -0
  123. package/src/stream/published.ts +273 -0
  124. package/src/stream/streams.ts +764 -0
  125. package/src/stream/types.ts +486 -0
  126. package/src/stream/wait.ts +62 -0
  127. package/src/stream/wire.ts +324 -0
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  # @honeybbq/teamspeak
4
4
 
5
- **A clean-room TeamSpeak client protocol library written in pure TypeScript.**
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
- └── query/ # ServerQuery client (not exported yet)
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 GmbH](https://teamspeak.com/). This project is not affiliated with, endorsed by, or associated with TeamSpeak Systems GmbH in any way.
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
- This library is a **clean-room implementation** developed from publicly available documentation, protocol analysis of network traffic, and independent research. No proprietary TeamSpeak SDK code, headers, or libraries were used in its creation.
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,CAO/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,CAOtB;CAkBF;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAMhE"}
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"}
@@ -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
- this.#nextRC = 0;
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":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AA6BrD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,OAAO,cAAc;IAChB,QAAQ,GAAG,IAAI,GAAG,EAA0B,CAAC;IAC7C,YAAY,CAAe;IACpC,OAAO,GAAG,CAAC,CAAC;IACZ,OAAO,GAAkB,EAAE,CAAC;IAC5B,gBAAgB,GAAG,KAAK,CAAC;IAEzB,6EAA6E;IAC7E,YAAY,WAAW,GAAiB,GAAG,EAAE,CAAC,CAAC;QAC7C,IAAI,CAAC,YAAY,GAAG,WAAW,CAAC;IAClC,CAAC;IAED,QAAQ,CACN,IAAmB,EACnB,WAAoB,EACpB,IAAa;QAEb,IAAI,CAAC,OAAO,EAAE,CAAC;QACf,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC;QACxB,0EAA0E;QAC1E,IAAI,IAAI,EAAE,UAAU,EAAE,GAAG,KAAK,aAAa;YAAE,WAAW,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC;QACtE,MAAM,OAAO,GAAG,IAAI,OAAO,CAAgB,CAAC,OAAO,EAAE,EAAE;YACrD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,CAAC;QAC9E,CAAC,CAAC,CAAC;QACH,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;IACvB,CAAC;IAED,UAAU,CAAC,EAAU;QACnB,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC3B,CAAC;IAED,2EAA2E;IAC3E,qBAAqB;QACnB,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC;QAC7B,IAAI,CAAC,OAAO,GAAG,EAAE,CAAC;IACpB,CAAC;IAED,oEAAoE;IACpE,OAAO,CAAC,IAAY;QAClB,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;YACvC,IAAI,CAAC,CAAC,IAAI,EAAE,aAAa,EAAE,QAAQ,CAAC,IAAI,CAAC;gBAAE,OAAO,IAAI,CAAC;YACvD,IAAI,CAAC,CAAC,IAAI,EAAE,UAAU,EAAE,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC;gBAAE,OAAO,IAAI,CAAC;QACpE,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,wEAAwE;IACxE,MAAM,CAAC,MAA8B,EAAE,MAAM,GAAG,EAAE;QAChD,IAAI,CAAC,IAAI,CAAC,gBAAgB;YAAE,OAAO;QACnC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,KAAK,CAAC;YAAE,OAAO;QACrC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACxC,CAAC;IAED;;;;;OAKG;IACH,eAAe,CAAC,IAAY,EAAE,MAA8B;QAC1D,KAAK,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACpC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,UAAU,CAAC;YAC7B,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,WAAW,KAAK,SAAS,IAAI,CAAC,CAAC,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC;gBAAE,SAAS;YACnF,IAAI,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,WAAW;gBAAE,SAAS;YAE9C,IAAI,IAAI,KAAK,CAAC,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,GAAG,CAAC,KAAK,GAAG,EAAE,CAAC;gBAC5D,MAAM,GAAG,GAAG,IAAI,WAAW,CAAC,cAAc,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE;oBACjF,YAAY,EAAE,MAAM,CAAC,WAAW,CAAC,IAAI,SAAS;iBAC/C,CAAC,CAAC;gBACH,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;gBACvC,OAAO;YACT,CAAC;YACD,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAC1B,IAAI,CAAC,CAAC,OAAO;gBAAE,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;YAC9F,OAAO;QACT,CAAC;IACH,CAAC;IAED,OAAO,CAAC,EAAU,EAAE,GAAiB;QACnC,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC;QAC1B,IAAI,CAAC,OAAO,GAAG,EAAE,CAAC;QAClB,MAAM,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAChC,IAAI,CAAC,CAAC;YAAE,OAAO;QAEf,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAClF,IAAI,GAAG,EAAE,CAAC;YACR,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;YACnC,OAAO;QACT,CAAC;QACD,yEAAyE;QACzE,uCAAuC;QACvC,IAAI,CAAC,CAAC,IAAI,EAAE,UAAU,IAAI,CAAC,CAAC,WAAW,KAAK,SAAS,IAAI,CAAC,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACnF,CAAC,CAAC,OAAO,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;YAChC,OAAO;QACT,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,CAAC,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;IACvE,CAAC;IAED,aAAa;QACX,IAAI,CAAC,OAAO,GAAG,EAAE,CAAC;IACpB,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,GAAU;QACd,MAAM,OAAO,GAAG,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;QAC5C,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;QACtB,KAAK,MAAM,CAAC,IAAI,OAAO;YAAE,CAAC,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;QACtD,IAAI,CAAC,OAAO,GAAG,EAAE,CAAC;QAClB,IAAI,CAAC,gBAAgB,GAAG,KAAK,CAAC;QAC9B,4EAA4E;QAC5E,wEAAwE;IAC1E,CAAC;IAED,QAAQ,CAAC,CAAiB,EAAE,GAAgB;QAC1C,IAAI,GAAG,CAAC,MAAM,KAAK,EAAE;YAAE,OAAO,IAAI,CAAC;QACnC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,aAAa,EAAE,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC;YAAE,OAAO,KAAK,CAAC;QAC/D,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,IAAI,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,KAAK,MAAM,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC;YAAE,OAAO,KAAK,CAAC;QAChG,2EAA2E;QAC3E,gDAAgD;QAChD,MAAM,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC;QAC3B,IAAI,GAAG,KAAK,SAAS,IAAI,CAAC,CAAC,IAAI,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC;QAC3D,MAAM,KAAK,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC9B,OAAO,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,CAAC,CAAC,IAAI,CAAC;IACjD,CAAC;IAED,OAAO,CAAC,EAAU,EAAE,CAAiB,EAAE,MAAqB;QAC1D,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACzB,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACpB,CAAC;CACF;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAW,EAAE,EAAU;IACtD,MAAM,MAAM,GAAG,YAAY,CAAC,GAAG,CAAC,EAAE,MAAM,IAAI,EAAE,CAAC;IAC/C,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,EAAE,CAAC;QACzC,MAAM,IAAI,SAAS,CAAC,sEAAsE,CAAC,CAAC;IAC9F,CAAC;IACD,OAAO,GAAG,GAAG,gBAAgB,EAAE,EAAE,CAAC;AACpC,CAAC"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * A clean-room TeamSpeak client: it connects over the voice protocol, the way
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,
@@ -1,5 +1,5 @@
1
1
  /**
2
- * A clean-room TeamSpeak client: it connects over the voice protocol, the way
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;AAwBtF;;;;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;IA8CzD,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;CA8gBF"}
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;