@honeybbq/teamspeak 1.0.0-beta.26 → 1.0.0-beta.27

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 (81) hide show
  1. package/README.md +2 -1
  2. package/dist/client/client.d.ts +10 -10
  3. package/dist/client/client.d.ts.map +1 -1
  4. package/dist/client/client.js +154 -114
  5. package/dist/client/client.js.map +1 -1
  6. package/dist/client/commands.d.ts +3 -3
  7. package/dist/client/commands.js +3 -3
  8. package/dist/client/handshake.d.ts.map +1 -1
  9. package/dist/client/handshake.js +10 -14
  10. package/dist/client/handshake.js.map +1 -1
  11. package/dist/client/types.d.ts +16 -1
  12. package/dist/client/types.d.ts.map +1 -1
  13. package/dist/client/types.js.map +1 -1
  14. package/dist/core/command/request.d.ts +4 -3
  15. package/dist/core/command/request.d.ts.map +1 -1
  16. package/dist/core/command/request.js.map +1 -1
  17. package/dist/core/command/responses.d.ts.map +1 -1
  18. package/dist/core/command/responses.js +10 -1
  19. package/dist/core/command/responses.js.map +1 -1
  20. package/dist/core/errors.d.ts +3 -19
  21. package/dist/core/errors.d.ts.map +1 -1
  22. package/dist/core/errors.js +3 -21
  23. package/dist/core/errors.js.map +1 -1
  24. package/dist/core/index.d.ts +2 -2
  25. package/dist/core/index.d.ts.map +1 -1
  26. package/dist/core/index.js +1 -1
  27. package/dist/core/index.js.map +1 -1
  28. package/dist/core/internal.d.ts +2 -2
  29. package/dist/core/internal.d.ts.map +1 -1
  30. package/dist/core/internal.js +2 -2
  31. package/dist/core/internal.js.map +1 -1
  32. package/dist/core/middleware.d.ts +3 -1
  33. package/dist/core/middleware.d.ts.map +1 -1
  34. package/dist/core/middleware.js +8 -0
  35. package/dist/core/middleware.js.map +1 -1
  36. package/dist/core/types.d.ts +60 -1
  37. package/dist/core/types.d.ts.map +1 -1
  38. package/dist/core/types.js.map +1 -1
  39. package/dist/query/client.d.ts +10 -3
  40. package/dist/query/client.d.ts.map +1 -1
  41. package/dist/query/client.js +80 -90
  42. package/dist/query/client.js.map +1 -1
  43. package/dist/query/options.d.ts +37 -37
  44. package/dist/query/options.d.ts.map +1 -1
  45. package/dist/query/options.js +16 -25
  46. package/dist/query/options.js.map +1 -1
  47. package/dist/query/ssh.d.ts +1 -1
  48. package/dist/stream/published.d.ts.map +1 -1
  49. package/dist/stream/published.js +2 -9
  50. package/dist/stream/published.js.map +1 -1
  51. package/dist/stream/streams.d.ts +1 -1
  52. package/dist/stream/streams.js +2 -2
  53. package/dist/stream/streams.js.map +1 -1
  54. package/package.json +1 -1
  55. package/src/client/client.ts +184 -126
  56. package/src/client/commands.ts +3 -3
  57. package/src/client/handshake.ts +10 -16
  58. package/src/client/types.ts +16 -0
  59. package/src/core/command/request.ts +4 -3
  60. package/src/core/command/responses.ts +10 -1
  61. package/src/core/errors.ts +3 -22
  62. package/src/core/index.ts +5 -0
  63. package/src/core/internal.ts +2 -8
  64. package/src/core/middleware.ts +13 -1
  65. package/src/core/types.ts +67 -1
  66. package/src/query/client.ts +84 -96
  67. package/src/query/options.ts +54 -52
  68. package/src/query/ssh.ts +1 -1
  69. package/src/query/testing/live.ts +8 -1
  70. package/src/stream/published.ts +2 -9
  71. package/src/stream/streams.ts +2 -2
  72. package/dist/client/helpers.d.ts +0 -6
  73. package/dist/client/helpers.d.ts.map +0 -1
  74. package/dist/client/helpers.js +0 -13
  75. package/dist/client/helpers.js.map +0 -1
  76. package/dist/client/throttle.d.ts +0 -31
  77. package/dist/client/throttle.d.ts.map +0 -1
  78. package/dist/client/throttle.js +0 -118
  79. package/dist/client/throttle.js.map +0 -1
  80. package/src/client/helpers.ts +0 -10
  81. package/src/client/throttle.ts +0 -130
package/README.md CHANGED
@@ -35,7 +35,7 @@
35
35
  - **Typed commands and events.** Every command the voice protocol answers is a typed function, and the server's notifications arrive as typed events with pluggable middleware.
36
36
  - **File transfers.** Upload, download and delete files on the server, with avatars and icons covered.
37
37
  - **TeamSpeak 6 screen-share signalling.** Find, publish and watch streams, with SDP and ICE as plain strings for any WebRTC stack.
38
- - **Built for long-running bots.** Opt-in reconnection tuned for TeamSpeak's antiflood ban, and a token-bucket rate limiter that keeps you clear of flood kicks.
38
+ - **Built for long-running bots.** Opt-in reconnection tuned for TeamSpeak's antiflood ban, and a line middleware seam for pacing commands, with recipes for a token bucket, resending a refused command and keeping a query session open.
39
39
  - **Identities and server facts.** Generate, import, export and upgrade identities; read the server version, its uid hash (SHA-1 on TS3, SHA-256 on TS6) and its name, icon and banner.
40
40
  - **TeamSpeak 3 and 6, ESM with types.** Pure TypeScript, zero native dependencies, full declarations, and a custom-transport hook for proxies and tests.
41
41
 
@@ -88,6 +88,7 @@ The full API reference is at [honeybbq.github.io/teamspeak-js](https://honeybbq.
88
88
  - [Configuration](https://honeybbq.github.io/teamspeak-js/documents/configuration.html): options, client version, tracing, diagnostics, reconnection, custom transports and server capabilities.
89
89
  - [Streams](https://honeybbq.github.io/teamspeak-js/documents/streams.html): TeamSpeak 6 screen-share signalling, and interop with the TeamSpeak 6 client.
90
90
  - [ServerQuery](https://honeybbq.github.io/teamspeak-js/documents/serverquery.html): the query client and its SSH host-key policy.
91
+ - [Flood control](https://honeybbq.github.io/teamspeak-js/documents/flood-control.html): what the server counts, what each command costs, and line middleware recipes for pacing, resending and keeping a query session open.
91
92
  - [Design](https://honeybbq.github.io/teamspeak-js/documents/design.html): errors, ids, absent values, events, stability, and the server behaviour the SDK works around.
92
93
  - [Compatibility](https://honeybbq.github.io/teamspeak-js/documents/compatibility.html): supported environments and the differences from the Go SDK.
93
94
  - [Architecture](https://honeybbq.github.io/teamspeak-js/documents/architecture.html): source layout and entry points.
@@ -93,9 +93,9 @@ export declare class Client implements CommandExecutor, AsyncDisposable {
93
93
  *
94
94
  * Says goodbye first, waiting for the server's acknowledgement for a second
95
95
  * at most, or less if `signal` aborts sooner. The goodbye is sent at once,
96
- * past the flood throttle, and also by a client still connecting whose
97
- * clientinit is out; commands still waiting for their turn reject with a
98
- * NotConnectedError and are never sent. The connection is closed
96
+ * past the line middleware, and also by a client still connecting whose
97
+ * clientinit is out; commands still inside a line middleware have their
98
+ * signal aborted, and reject with a NotConnectedError without being sent. The connection is closed
99
99
  * either way. A `connect()` still waiting for its handshake rejects with a
100
100
  * NotConnectedError, and so do the commands still waiting for an answer.
101
101
  * Resolves once `disconnected` has been delivered to the listeners, so one
@@ -125,10 +125,9 @@ export declare class Client implements CommandExecutor, AsyncDisposable {
125
125
  * Rejects with a NotConnectedError unless the client is connected: while it
126
126
  * is still connecting, the session's keys may not exist yet.
127
127
  *
128
- * A command the server refuses as flooding (`ErrorCode.ClientIsFlooding`)
129
- * with a time to wait is sent again after it, and every other command waits
130
- * too, up to three times and while its timeout allows; the refusal that
131
- * remains rejects as it is.
128
+ * The line passes the `lineMiddleware`, if any. A command the server refuses
129
+ * as flooding (`ErrorCode.ClientIsFlooding`) rejects like any server error;
130
+ * `floodWaitMs` reads the wait it names.
132
131
  */
133
132
  exec(command: string, options?: CommandOptions): Promise<Record<string, string>[]>;
134
133
  /**
@@ -136,9 +135,10 @@ export declare class Client implements CommandExecutor, AsyncDisposable {
136
135
  * commands the server answers with nothing at all. `CommandName` lists
137
136
  * the command names.
138
137
  *
139
- * Commands are paced by a flood throttle; aborting `signal` while this waits
140
- * for its turn rejects with the signal's reason and sends nothing. Rejects
141
- * with a NotConnectedError unless the client is connected.
138
+ * The line passes the `lineMiddleware`, if any; aborting `signal` while it
139
+ * waits there rejects with the signal's reason and sends nothing. Resolves
140
+ * once the line has been handed to the connection. Rejects with a
141
+ * NotConnectedError unless the client is connected.
142
142
  */
143
143
  execNoWait(command: string, options?: {
144
144
  readonly signal?: AbortSignal | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client/client.ts"],"names":[],"mappings":"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"}
@@ -10,7 +10,7 @@ import { RESOLVE_BUDGET_MS, Resolver } from "./discovery/resolver.js";
10
10
  import { PacketHandler } from "./transport/handler.js";
11
11
  import { PacketType } from "./transport/packet.js";
12
12
  import { CommandTracker, appendReturnCode } from "./commands.js";
13
- import { buildCommand, buildCommandChain, encodeRequest, floodHoldMs, FLOOD_RETRIES, floodRetryDelayMs, parseServerError, resolveBackoffPolicy, } from "../core/internal.js";
13
+ import { buildCommand, buildCommandChain, buildLineChain, encodeRequest, parseServerError, resolveBackoffPolicy, } from "../core/internal.js";
14
14
  import { uploadFileData, downloadFileData } from "./transfer.js";
15
15
  import { buildSetConnectionInfo } from "./connection-info.js";
16
16
  import { buildEventChain } from "./events.js";
@@ -18,9 +18,7 @@ import { processInit1 } from "./handshake/crypt-handshake.js";
18
18
  import { handleHandshakeInitIV, handleHandshakeExpand2, defaultClientVersion, handleInitServer, prepareClientPassword, } from "./handshake.js";
19
19
  import { handleNotification } from "../core/internal.js";
20
20
  import { parseChannel, parseChannelGroup, parseCommand, parseServerGroup, parseUint16, } from "../core/internal.js";
21
- import { CommandThrottle } from "./throttle.js";
22
21
  import { splitCommandRows, sleep } from "../core/internal.js";
23
- import { isAutoNicknameMatch } from "./helpers.js";
24
22
  import { CommandTimeoutError, NotConnectedError } from "@honeybbq/teamspeak";
25
23
  import { responseSpec } from "../core/internal.js";
26
24
  import { unknownServerCapabilities } from "./capabilities.js";
@@ -241,7 +239,17 @@ export class Client {
241
239
  /** Cuts short the reconnect loop's wait, on disconnect(). */
242
240
  #stopReconnecting;
243
241
  #status = ClientStatus.Disconnected;
244
- #throttle = new CommandThrottle();
242
+ /**
243
+ * Aborted when the session ends, by `disconnect()` or a drop: what a command
244
+ * still inside a line middleware sees as its signal. A new one for each
245
+ * connection attempt.
246
+ */
247
+ #down = new AbortController();
248
+ /** The per-call state of the commands in the line chain, by their signal and their line. */
249
+ #calls = new WeakMap();
250
+ /** The line chain around the base that waits for the answer, and the one around the base that resolves once written. */
251
+ #line;
252
+ #lineNoWait;
245
253
  #cmdTrack = new CommandTracker(() => this.#clid);
246
254
  #viewStore;
247
255
  #clients;
@@ -319,14 +327,16 @@ export class Client {
319
327
  },
320
328
  markConnected: (initServer) => this.#markConnected(initServer),
321
329
  markHandshakeDone: (serverPublicKey) => this.#markHandshakeDone(serverPublicKey),
322
- execNoWait: (command) => this.#execNoWait(command, undefined, true),
323
330
  handlePacket: (packet) => this.#handlePacket(packet),
324
331
  };
325
332
  // Built once: the chains are read on every command and event, and
326
333
  // changing them while those run would make the behaviour depend on timing.
327
334
  const cmdMiddlewares = options.commandMiddleware ?? [];
328
335
  this.#eventMiddlewares = options.eventMiddleware ?? [];
329
- this.#send = buildCommandChain(cmdMiddlewares, (request, commandOptions = {}) => this.#exec(encodeRequest(request, prepareClientPassword), commandOptions, false));
336
+ this.#send = buildCommandChain(cmdMiddlewares, (request, commandOptions = {}) => this.#exec(encodeRequest(request, prepareClientPassword), commandOptions));
337
+ const lineMiddlewares = options.lineMiddleware ?? [];
338
+ this.#line = buildLineChain(lineMiddlewares, (line, lineOptions) => this.#writeLine(line, lineOptions, false));
339
+ this.#lineNoWait = buildLineChain(lineMiddlewares, (line, lineOptions) => this.#writeNoWait(line, lineOptions));
330
340
  this.#eventChain = buildEventChain([...this.#eventMiddlewares], (event) => this.#deliver(event.name, event.payload));
331
341
  }
332
342
  /**
@@ -504,9 +514,9 @@ export class Client {
504
514
  *
505
515
  * Says goodbye first, waiting for the server's acknowledgement for a second
506
516
  * at most, or less if `signal` aborts sooner. The goodbye is sent at once,
507
- * past the flood throttle, and also by a client still connecting whose
508
- * clientinit is out; commands still waiting for their turn reject with a
509
- * NotConnectedError and are never sent. The connection is closed
517
+ * past the line middleware, and also by a client still connecting whose
518
+ * clientinit is out; commands still inside a line middleware have their
519
+ * signal aborted, and reject with a NotConnectedError without being sent. The connection is closed
510
520
  * either way. A `connect()` still waiting for its handshake rejects with a
511
521
  * NotConnectedError, and so do the commands still waiting for an answer.
512
522
  * Resolves once `disconnected` has been delivered to the listeners, so one
@@ -538,9 +548,9 @@ export class Client {
538
548
  const holdsSession = this.#status === ClientStatus.Connected ||
539
549
  (this.#status === ClientStatus.Connecting && this.#crypt.cryptoInitComplete);
540
550
  this.#status = ClientStatus.Disconnected;
541
- // Whatever still waits for its turn is rejected and never sent; the
542
- // goodbye does not wait for one.
543
- this.#throttle.rejectAll(new NotConnectedError());
551
+ // Whatever still waits in a line middleware is aborted and never sent; the
552
+ // goodbye does not pass one.
553
+ this.#down.abort(new NotConnectedError());
544
554
  let left;
545
555
  this.#attempt.rejectAfter(new NotConnectedError(), new Promise((r) => (left = r)));
546
556
  const leaving = this.#leave(holdsSession, options.signal, options.reasonMessage ?? "Shutdown").finally(left);
@@ -568,7 +578,7 @@ export class Client {
568
578
  const answered = this.#exec(buildCommand("clientdisconnect", {
569
579
  reasonid: String(Reason.Disconnect),
570
580
  reasonmsg: reasonMessage,
571
- }), { signal, timeoutMs: DISCONNECT_TIMEOUT_MS }, true, true).then(() => true, () => false);
581
+ }), { signal, timeoutMs: DISCONNECT_TIMEOUT_MS }, true).then(() => true, () => false);
572
582
  const acknowledged = await Promise.race([answered, left]);
573
583
  this.#goodbyeAnswered = undefined;
574
584
  this.#callHook(this.#trace?.disconnectDone, () => ({
@@ -597,44 +607,44 @@ export class Client {
597
607
  * Rejects with a NotConnectedError unless the client is connected: while it
598
608
  * is still connecting, the session's keys may not exist yet.
599
609
  *
600
- * A command the server refuses as flooding (`ErrorCode.ClientIsFlooding`)
601
- * with a time to wait is sent again after it, and every other command waits
602
- * too, up to three times and while its timeout allows; the refusal that
603
- * remains rejects as it is.
610
+ * The line passes the `lineMiddleware`, if any. A command the server refuses
611
+ * as flooding (`ErrorCode.ClientIsFlooding`) rejects like any server error;
612
+ * `floodWaitMs` reads the wait it names.
604
613
  */
605
614
  // oxlint-disable-next-line typescript/array-type -- the public API report spells this `[]`; respelling it changes the report
606
615
  exec(command, options = {}) {
607
- return this.#exec(command, options, false);
616
+ return this.#exec(command, options);
608
617
  }
609
618
  /**
610
619
  * Send a raw command line without waiting for an answer, for the few
611
620
  * commands the server answers with nothing at all. `CommandName` lists
612
621
  * the command names.
613
622
  *
614
- * Commands are paced by a flood throttle; aborting `signal` while this waits
615
- * for its turn rejects with the signal's reason and sends nothing. Rejects
616
- * with a NotConnectedError unless the client is connected.
623
+ * The line passes the `lineMiddleware`, if any; aborting `signal` while it
624
+ * waits there rejects with the signal's reason and sends nothing. Resolves
625
+ * once the line has been handed to the connection. Rejects with a
626
+ * NotConnectedError unless the client is connected.
617
627
  */
618
628
  execNoWait(command, options = {}) {
619
- return this.#execNoWait(command, options.signal, false);
629
+ return this.#execNoWait(command, options.signal);
620
630
  }
621
631
  /**
622
- * Whether a command may go out now. The handshake's own commands (`internal`)
623
- * need only an open socket; everything else needs a connected session, so
624
- * nothing a caller sends can go out under the public dummy key, or after
632
+ * Whether a command may go out now. The goodbye (`internal`) needs only an
633
+ * open socket; everything else needs a connected session, so nothing a
634
+ * caller sends can go out under the public dummy key, or after
625
635
  * disconnect() has begun saying goodbye.
626
636
  */
627
637
  #canSend(internal) {
628
638
  return this.#handler.isOpen && (internal || this.#status === ClientStatus.Connected);
629
639
  }
630
- async #execNoWait(command, signal, internal) {
640
+ async #execNoWait(command, signal) {
631
641
  signal?.throwIfAborted();
632
- if (!this.#canSend(internal))
633
- throw new NotConnectedError();
634
- await this.#throttle.wait(signal);
635
- if (!this.#canSend(internal))
642
+ if (!this.#canSend(false))
636
643
  throw new NotConnectedError();
637
- this.#sendLine(command);
644
+ const name = parseCommand(command)?.name ?? "";
645
+ // No timeout, as ever for a command nobody waits an answer for: the line
646
+ // middleware and the caller's signal are what can hold it.
647
+ await this.#runLine(name, command, { signal, noWait: true });
638
648
  }
639
649
  /**
640
650
  * Send a typed command; this is the CommandExecutor the typed commands run on.
@@ -654,94 +664,130 @@ export class Client {
654
664
  hook(this, line);
655
665
  this.#handler.sendPacket(PacketType.Command, Buffer.from(line), 0);
656
666
  }
657
- #exec(command, options, internal, goodbye = false) {
667
+ #exec(command, options, goodbye = false) {
658
668
  if (!commandChannel.hasSubscribers) {
659
- return this.#execUntraced(command, options, internal, goodbye);
669
+ return this.#execUntraced(command, options, goodbye);
660
670
  }
661
671
  const trace = {
662
672
  command: parseCommand(command)?.name ?? "",
663
673
  client: this,
664
674
  };
665
- return commandChannel.tracePromise(() => this.#execUntraced(command, options, internal, goodbye), trace);
675
+ return commandChannel.tracePromise(() => this.#execUntraced(command, options, goodbye), trace);
666
676
  }
667
- async #execUntraced(command, options, internal, goodbye) {
677
+ async #execUntraced(command, options, goodbye) {
668
678
  const { signal, timeoutMs = DEFAULT_COMMAND_TIMEOUT_MS, onSent } = options;
669
679
  signal?.throwIfAborted();
670
680
  // A command sent on no socket used to vanish and surface 10 seconds later
671
681
  // as a timeout, which reads like a slow server rather than a usage error.
672
- if (!this.#canSend(internal))
682
+ if (!this.#canSend(goodbye))
683
+ throw new NotConnectedError();
684
+ const name = parseCommand(command)?.name ?? "";
685
+ return this.#runLine(name, command, { signal, timeoutMs, onSent, goodbye });
686
+ }
687
+ /**
688
+ * Run one line through the line middleware, or straight to the connection
689
+ * for the goodbye. The timeout, the caller's signal and the end of the
690
+ * session each abort the signal the middleware is given, and the call
691
+ * rejects with the reason: the timeout bounds the whole chain, the waits of
692
+ * a middleware included.
693
+ */
694
+ async #runLine(name, text, call) {
695
+ const { signal, timeoutMs, goodbye = false } = call;
696
+ const abort = new AbortController();
697
+ const down = this.#down.signal;
698
+ const stop = (reason) => abort.abort(reason);
699
+ const timer = timeoutMs === undefined
700
+ ? undefined
701
+ : setTimeout(() => stop(new CommandTimeoutError(name)), timeoutMs);
702
+ const onCaller = () => stop(abortReason(signal));
703
+ // The goodbye leaves as the session ends, which is what aborts `down`.
704
+ const onDown = () => stop(down.reason);
705
+ signal?.addEventListener("abort", onCaller, { once: true });
706
+ if (!goodbye)
707
+ down.addEventListener("abort", onDown, { once: true });
708
+ if (!goodbye && down.aborted)
709
+ onDown();
710
+ // A middleware that ignores its signal must not hold the call past it.
711
+ const gaveUp = new Promise((_, reject) => {
712
+ abort.signal.addEventListener("abort", () => reject(abortReason(abort.signal)), {
713
+ once: true,
714
+ });
715
+ });
716
+ gaveUp.catch(() => { });
717
+ const line = { name, text };
718
+ const state = { onSent: call.onSent, sent: false };
719
+ this.#calls.set(line, state);
720
+ this.#calls.set(abort.signal, state);
721
+ try {
722
+ // The goodbye goes to its own base: it passes no middleware, and may go
723
+ // out while the status says Disconnected.
724
+ const send = goodbye
725
+ ? (l, o) => this.#writeLine(l, o, true)
726
+ : call.noWait === true
727
+ ? this.#lineNoWait
728
+ : this.#line;
729
+ return await Promise.race([send(line, { signal: abort.signal }), gaveUp]);
730
+ }
731
+ finally {
732
+ clearTimeout(timer);
733
+ signal?.removeEventListener("abort", onCaller);
734
+ down.removeEventListener("abort", onDown);
735
+ }
736
+ }
737
+ /** Tell the call a line belongs to that its line was handed to the connection, the first time only. */
738
+ #sent(line, signal) {
739
+ const call = this.#calls.get(line) ?? this.#calls.get(signal);
740
+ if (call === undefined || call.sent)
741
+ return;
742
+ call.sent = true;
743
+ call.onSent?.();
744
+ }
745
+ /**
746
+ * The base of the line chain for a line nobody answers: write it, and
747
+ * resolve with no rows once it is written.
748
+ */
749
+ async #writeNoWait(line, options) {
750
+ options.signal.throwIfAborted();
751
+ if (!this.#canSend(false))
752
+ throw new NotConnectedError();
753
+ this.#sendLine(line.text);
754
+ this.#sent(line, options.signal);
755
+ return [];
756
+ }
757
+ /**
758
+ * The base of the line chain: write the line, with a return code of its own
759
+ * each time it is called, and read its answer. Looks at the connection once
760
+ * more, as a middleware may have waited, and ends when the signal does.
761
+ */
762
+ async #writeLine(line, options, goodbye) {
763
+ const { signal } = options;
764
+ signal.throwIfAborted();
765
+ if (!this.#canSend(goodbye))
673
766
  throw new NotConnectedError();
674
767
  // Which notifications answer this command, and — for answers that arrive
675
768
  // after the `error` — the request field they echo back.
676
- const parsed = parseCommand(command);
677
- const spec = responseSpec(parsed?.name ?? "");
769
+ const parsed = parseCommand(line.text);
770
+ const spec = responseSpec(line.name);
678
771
  const key = spec?.correlated?.key;
679
772
  const correlation = key === undefined ? undefined : parsed?.params[key];
680
773
  const echo = spec?.echoKey === undefined ? undefined : parsed?.params[spec.echoKey];
681
- let [rc, answered] = this.#cmdTrack.register(spec, correlation, echo);
682
- const deadline = performance.now() + timeoutMs;
683
- let timer;
684
- let onAbort;
685
- // Started before the throttle: the timeout bounds the whole command, its
686
- // wait for a turn included, and so does the signal.
687
- const givenUp = new Promise((_, reject) => {
688
- timer = setTimeout(() => reject(new CommandTimeoutError(parsed?.name ?? "")), timeoutMs);
689
- onAbort = () => reject(abortReason(signal));
690
- signal?.addEventListener("abort", onAbort, { once: true });
691
- });
692
- givenUp.catch(() => { });
693
- const throttleAbort = new AbortController();
774
+ const [rc, answered] = this.#cmdTrack.register(spec, correlation, echo);
775
+ answered.catch(() => { });
694
776
  try {
695
- for (let refused = 0;; refused++) {
696
- const withRc = appendReturnCode(command, rc);
697
- // The goodbye of a disconnect goes out at once: queued behind other
698
- // commands it would not leave in time, and the server would keep the
699
- // session.
700
- if (!goodbye)
701
- await Promise.race([this.#throttle.wait(throttleAbort.signal), givenUp]);
702
- if (!this.#canSend(internal))
703
- throw new NotConnectedError();
704
- this.#sendLine(withRc);
705
- if (refused === 0)
706
- onSent?.();
707
- const result = await Promise.race([answered, givenUp]);
708
- // Refused as flooding with a time to wait, which the command can
709
- // afford: nothing is sent until then, and the command, which the
710
- // server did not run, goes out again.
711
- const wait = result.err ? floodRetryDelayMs(result.err) : undefined;
712
- const hold = wait === undefined ? undefined : floodHoldMs(wait);
713
- if (hold !== undefined &&
714
- !goodbye &&
715
- refused < FLOOD_RETRIES &&
716
- hold < deadline - performance.now()) {
717
- this.#logger.debug("the server asked for a pause", {
718
- command: parsed?.name,
719
- waitMs: wait,
720
- });
721
- this.#throttle.hold(hold);
722
- this.#cmdTrack.unregister(rc);
723
- [rc, answered] = this.#cmdTrack.register(spec, correlation, echo);
724
- continue;
725
- }
726
- if (result.err)
727
- throw result.err;
728
- // The server took the rename, so it is what this client now asks for.
729
- if (!internal && parsed?.name === "clientupdate") {
730
- const renamed = parsed.params["client_nickname"];
731
- if (renamed !== undefined)
732
- this.#requestedNickname = renamed;
733
- }
734
- return result.data;
777
+ this.#sendLine(appendReturnCode(line.text, rc));
778
+ this.#sent(line, signal);
779
+ const result = await abortable(answered, signal);
780
+ if (result.err)
781
+ throw result.err;
782
+ // The server took the rename, so it is what this client now asks for.
783
+ if (!goodbye && line.name === "clientupdate") {
784
+ const renamed = parsed?.params["client_nickname"];
785
+ if (renamed !== undefined)
786
+ this.#requestedNickname = renamed;
735
787
  }
788
+ return result.data;
736
789
  }
737
790
  finally {
738
- // A command given up while it waited for its turn gives the turn back.
739
- throttleAbort.abort();
740
- // Cleared, not left to fire: a pending timer keeps the process alive for
741
- // the full timeout after the last command has long been answered.
742
- clearTimeout(timer);
743
- if (onAbort)
744
- signal?.removeEventListener("abort", onAbort);
745
791
  this.#cmdTrack.unregister(rc);
746
792
  }
747
793
  }
@@ -1175,6 +1221,8 @@ export class Client {
1175
1221
  this.#handler = this.#newHandler();
1176
1222
  this.#attempt = newConnectAttempt();
1177
1223
  this.#cmdTrack.reset(new NotConnectedError());
1224
+ this.#down.abort(new NotConnectedError());
1225
+ this.#down = new AbortController();
1178
1226
  this.#viewStore.reset();
1179
1227
  this.#clid = 0;
1180
1228
  this.#capabilities = unknownServerCapabilities();
@@ -1470,7 +1518,7 @@ export class Client {
1470
1518
  */
1471
1519
  #answerConnectionInfoRequest() {
1472
1520
  const line = buildSetConnectionInfo(this.#handler.trafficSnapshot(), this.#handler.roundTrip);
1473
- this.#execNoWait(line, undefined, false).catch((error) => {
1521
+ this.#execNoWait(line, undefined).catch((error) => {
1474
1522
  this.#logger.debug("could not answer a connection info request", {
1475
1523
  error: String(error),
1476
1524
  });
@@ -1507,12 +1555,12 @@ export class Client {
1507
1555
  switch (result.kind) {
1508
1556
  case "clientEnter": {
1509
1557
  const info = result.info;
1510
- // Identify self by the clid initserver already assigned rather than by
1511
- // nickname: any other client whose nickname happens to be "<our
1512
- // nick><digits>" — which is how the server renames a client whose
1513
- // nickname was taken — would otherwise be mistaken for us, poisoning
1514
- // every outgoing packet header with a foreign clientID until the server
1515
- // drops the connection for command-resend timeout.
1558
+ // Identify self by the clid initserver assigned, never by nickname: any
1559
+ // other client whose nickname happens to be "<our nick><digits>" — which
1560
+ // is how the server renames a client whose nickname was taken — would
1561
+ // otherwise be mistaken for us, poisoning every outgoing packet header
1562
+ // with a foreign clientID until the server drops the connection for
1563
+ // command-resend timeout.
1516
1564
  //
1517
1565
  // This no longer marks the end of the welcome sequence: the server
1518
1566
  // sends more welcome data after it, and `channellistfinished` is the
@@ -1522,15 +1570,6 @@ export class Client {
1522
1570
  // the nickname asked for stays as it was.
1523
1571
  this.#nickname = info.nickname;
1524
1572
  }
1525
- else if (this.#clid === 0 &&
1526
- isAutoNicknameMatch(this.#requestedNickname, info.nickname)) {
1527
- // initserver carried no aclid, so the welcome enter is the only way
1528
- // to learn our clid. Gated on clid still being unknown so a later
1529
- // lookalike can never hijack an established session.
1530
- this.#clid = info.id;
1531
- this.#handler.setClientID(info.id);
1532
- this.#nickname = info.nickname;
1533
- }
1534
1573
  this.#emitServerEvent({ name: "clientEnter", payload: info });
1535
1574
  break;
1536
1575
  }
@@ -1785,6 +1824,7 @@ export class Client {
1785
1824
  // Nothing in flight can be answered any more; failing it now, with the
1786
1825
  // reason, beats every caller timing out one by one.
1787
1826
  this.#cmdTrack.reset(new NotConnectedError(err ? { cause: err } : undefined));
1827
+ this.#down.abort(new NotConnectedError(err ? { cause: err } : undefined));
1788
1828
  if (this.#status !== ClientStatus.Connected) {
1789
1829
  // The socket closed before the handshake finished, so this is a failed
1790
1830
  // connection attempt rather than a disconnect of a live session.