@honeybbq/teamspeak 0.0.1 → 1.0.0-beta.11

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 (438) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +749 -2
  3. package/dist/client/abort.d.ts +5 -0
  4. package/dist/client/abort.d.ts.map +1 -0
  5. package/dist/client/abort.js +23 -0
  6. package/dist/client/abort.js.map +1 -0
  7. package/dist/client/address.d.ts +17 -0
  8. package/dist/client/address.d.ts.map +1 -0
  9. package/dist/client/address.js +66 -0
  10. package/dist/client/address.js.map +1 -0
  11. package/dist/client/api/avatars.d.ts +65 -0
  12. package/dist/client/api/avatars.d.ts.map +1 -0
  13. package/dist/client/api/avatars.js +118 -0
  14. package/dist/client/api/avatars.js.map +1 -0
  15. package/dist/client/api/voice.d.ts +59 -0
  16. package/dist/client/api/voice.d.ts.map +1 -0
  17. package/dist/client/api/voice.js +110 -0
  18. package/dist/client/api/voice.js.map +1 -0
  19. package/dist/client/capabilities.d.ts +68 -0
  20. package/dist/client/capabilities.d.ts.map +1 -0
  21. package/dist/client/capabilities.js +59 -0
  22. package/dist/client/capabilities.js.map +1 -0
  23. package/dist/client/client.d.ts +235 -0
  24. package/dist/client/client.d.ts.map +1 -0
  25. package/dist/client/client.js +1666 -0
  26. package/dist/client/client.js.map +1 -0
  27. package/dist/client/commands.d.ts +66 -0
  28. package/dist/client/commands.d.ts.map +1 -0
  29. package/dist/client/commands.js +168 -0
  30. package/dist/client/commands.js.map +1 -0
  31. package/dist/client/connection-info.d.ts +14 -0
  32. package/dist/client/connection-info.d.ts.map +1 -0
  33. package/dist/client/connection-info.js +47 -0
  34. package/dist/client/connection-info.js.map +1 -0
  35. package/dist/client/crypto/crypt-ops.d.ts +7 -0
  36. package/dist/client/crypto/crypt-ops.d.ts.map +1 -0
  37. package/dist/client/crypto/crypt-ops.js +2 -0
  38. package/dist/client/crypto/crypt-ops.js.map +1 -0
  39. package/dist/client/crypto/crypt.d.ts +37 -0
  40. package/dist/client/crypto/crypt.d.ts.map +1 -0
  41. package/dist/client/crypto/crypt.js +203 -0
  42. package/dist/client/crypto/crypt.js.map +1 -0
  43. package/dist/client/crypto/eax.d.ts +12 -0
  44. package/dist/client/crypto/eax.d.ts.map +1 -0
  45. package/dist/client/crypto/eax.js +118 -0
  46. package/dist/client/crypto/eax.js.map +1 -0
  47. package/dist/client/crypto/identity.d.ts +97 -0
  48. package/dist/client/crypto/identity.d.ts.map +1 -0
  49. package/dist/client/crypto/identity.js +412 -0
  50. package/dist/client/crypto/identity.js.map +1 -0
  51. package/dist/client/crypto/index.d.ts +7 -0
  52. package/dist/client/crypto/index.d.ts.map +1 -0
  53. package/dist/client/crypto/index.js +5 -0
  54. package/dist/client/crypto/index.js.map +1 -0
  55. package/dist/client/crypto/primitives.d.ts +43 -0
  56. package/dist/client/crypto/primitives.d.ts.map +1 -0
  57. package/dist/client/crypto/primitives.js +116 -0
  58. package/dist/client/crypto/primitives.js.map +1 -0
  59. package/dist/client/diagnostics.d.ts +45 -0
  60. package/dist/client/diagnostics.d.ts.map +1 -0
  61. package/dist/client/diagnostics.js +30 -0
  62. package/dist/client/diagnostics.js.map +1 -0
  63. package/dist/client/discovery/index.d.ts +2 -0
  64. package/dist/client/discovery/index.d.ts.map +1 -0
  65. package/dist/client/discovery/index.js +2 -0
  66. package/dist/client/discovery/index.js.map +1 -0
  67. package/dist/client/discovery/resolver.d.ts +26 -0
  68. package/dist/client/discovery/resolver.d.ts.map +1 -0
  69. package/dist/client/discovery/resolver.js +275 -0
  70. package/dist/client/discovery/resolver.js.map +1 -0
  71. package/dist/client/errors.d.ts +82 -0
  72. package/dist/client/errors.d.ts.map +1 -0
  73. package/dist/client/errors.js +201 -0
  74. package/dist/client/errors.js.map +1 -0
  75. package/dist/client/events.d.ts +3 -0
  76. package/dist/client/events.d.ts.map +1 -0
  77. package/dist/client/events.js +8 -0
  78. package/dist/client/events.js.map +1 -0
  79. package/dist/client/handshake/crypt-handshake.d.ts +17 -0
  80. package/dist/client/handshake/crypt-handshake.d.ts.map +1 -0
  81. package/dist/client/handshake/crypt-handshake.js +128 -0
  82. package/dist/client/handshake/crypt-handshake.js.map +1 -0
  83. package/dist/client/handshake/crypt-init2.d.ts +7 -0
  84. package/dist/client/handshake/crypt-init2.d.ts.map +1 -0
  85. package/dist/client/handshake/crypt-init2.js +37 -0
  86. package/dist/client/handshake/crypt-init2.js.map +1 -0
  87. package/dist/client/handshake/index.d.ts +4 -0
  88. package/dist/client/handshake/index.d.ts.map +1 -0
  89. package/dist/client/handshake/index.js +4 -0
  90. package/dist/client/handshake/index.js.map +1 -0
  91. package/dist/client/handshake/license.d.ts +35 -0
  92. package/dist/client/handshake/license.d.ts.map +1 -0
  93. package/dist/client/handshake/license.js +169 -0
  94. package/dist/client/handshake/license.js.map +1 -0
  95. package/dist/client/handshake.d.ts +27 -0
  96. package/dist/client/handshake.d.ts.map +1 -0
  97. package/dist/client/handshake.js +126 -0
  98. package/dist/client/handshake.js.map +1 -0
  99. package/dist/client/helpers.d.ts +6 -0
  100. package/dist/client/helpers.d.ts.map +1 -0
  101. package/dist/client/helpers.js +13 -0
  102. package/dist/client/helpers.js.map +1 -0
  103. package/dist/client/hooks.d.ts +7 -0
  104. package/dist/client/hooks.d.ts.map +1 -0
  105. package/dist/client/hooks.js +28 -0
  106. package/dist/client/hooks.js.map +1 -0
  107. package/dist/client/index.d.ts +28 -0
  108. package/dist/client/index.d.ts.map +1 -0
  109. package/dist/client/index.js +29 -0
  110. package/dist/client/index.js.map +1 -0
  111. package/dist/client/reconnect.d.ts +12 -0
  112. package/dist/client/reconnect.d.ts.map +1 -0
  113. package/dist/client/reconnect.js +44 -0
  114. package/dist/client/reconnect.js.map +1 -0
  115. package/dist/client/throttle.d.ts +20 -0
  116. package/dist/client/throttle.d.ts.map +1 -0
  117. package/dist/client/throttle.js +83 -0
  118. package/dist/client/throttle.js.map +1 -0
  119. package/dist/client/trace.d.ts +223 -0
  120. package/dist/client/trace.d.ts.map +1 -0
  121. package/dist/client/trace.js +13 -0
  122. package/dist/client/trace.js.map +1 -0
  123. package/dist/client/transfer.d.ts +111 -0
  124. package/dist/client/transfer.d.ts.map +1 -0
  125. package/dist/client/transfer.js +243 -0
  126. package/dist/client/transfer.js.map +1 -0
  127. package/dist/client/transport/generation-window.d.ts +30 -0
  128. package/dist/client/transport/generation-window.d.ts.map +1 -0
  129. package/dist/client/transport/generation-window.js +97 -0
  130. package/dist/client/transport/generation-window.js.map +1 -0
  131. package/dist/client/transport/handler.d.ts +79 -0
  132. package/dist/client/transport/handler.d.ts.map +1 -0
  133. package/dist/client/transport/handler.js +868 -0
  134. package/dist/client/transport/handler.js.map +1 -0
  135. package/dist/client/transport/index.d.ts +6 -0
  136. package/dist/client/transport/index.d.ts.map +1 -0
  137. package/dist/client/transport/index.js +5 -0
  138. package/dist/client/transport/index.js.map +1 -0
  139. package/dist/client/transport/packet.d.ts +36 -0
  140. package/dist/client/transport/packet.d.ts.map +1 -0
  141. package/dist/client/transport/packet.js +55 -0
  142. package/dist/client/transport/packet.js.map +1 -0
  143. package/dist/client/transport/quicklz.d.ts +24 -0
  144. package/dist/client/transport/quicklz.d.ts.map +1 -0
  145. package/dist/client/transport/quicklz.js +179 -0
  146. package/dist/client/transport/quicklz.js.map +1 -0
  147. package/dist/client/transport/traffic.d.ts +43 -0
  148. package/dist/client/transport/traffic.d.ts.map +1 -0
  149. package/dist/client/transport/traffic.js +90 -0
  150. package/dist/client/transport/traffic.js.map +1 -0
  151. package/dist/client/types.d.ts +402 -0
  152. package/dist/client/types.d.ts.map +1 -0
  153. package/dist/client/types.js +113 -0
  154. package/dist/client/types.js.map +1 -0
  155. package/dist/client/view.d.ts +162 -0
  156. package/dist/client/view.d.ts.map +1 -0
  157. package/dist/client/view.js +228 -0
  158. package/dist/client/view.js.map +1 -0
  159. package/dist/core/api/avatars.d.ts +26 -0
  160. package/dist/core/api/avatars.d.ts.map +1 -0
  161. package/dist/core/api/avatars.js +37 -0
  162. package/dist/core/api/avatars.js.map +1 -0
  163. package/dist/core/api/channels.d.ts +191 -0
  164. package/dist/core/api/channels.d.ts.map +1 -0
  165. package/dist/core/api/channels.js +174 -0
  166. package/dist/core/api/channels.js.map +1 -0
  167. package/dist/core/api/clients.d.ts +562 -0
  168. package/dist/core/api/clients.d.ts.map +1 -0
  169. package/dist/core/api/clients.js +435 -0
  170. package/dist/core/api/clients.js.map +1 -0
  171. package/dist/core/api/command.d.ts +400 -0
  172. package/dist/core/api/command.d.ts.map +1 -0
  173. package/dist/core/api/command.js +462 -0
  174. package/dist/core/api/command.js.map +1 -0
  175. package/dist/core/api/credentials.d.ts +186 -0
  176. package/dist/core/api/credentials.d.ts.map +1 -0
  177. package/dist/core/api/credentials.js +144 -0
  178. package/dist/core/api/credentials.js.map +1 -0
  179. package/dist/core/api/files.d.ts +153 -0
  180. package/dist/core/api/files.d.ts.map +1 -0
  181. package/dist/core/api/files.js +185 -0
  182. package/dist/core/api/files.js.map +1 -0
  183. package/dist/core/api/groups.d.ts +301 -0
  184. package/dist/core/api/groups.d.ts.map +1 -0
  185. package/dist/core/api/groups.js +339 -0
  186. package/dist/core/api/groups.js.map +1 -0
  187. package/dist/core/api/icons.d.ts +67 -0
  188. package/dist/core/api/icons.d.ts.map +1 -0
  189. package/dist/core/api/icons.js +125 -0
  190. package/dist/core/api/icons.js.map +1 -0
  191. package/dist/core/api/instance.d.ts +197 -0
  192. package/dist/core/api/instance.d.ts.map +1 -0
  193. package/dist/core/api/instance.js +160 -0
  194. package/dist/core/api/instance.js.map +1 -0
  195. package/dist/core/api/moderation.d.ts +266 -0
  196. package/dist/core/api/moderation.d.ts.map +1 -0
  197. package/dist/core/api/moderation.js +258 -0
  198. package/dist/core/api/moderation.js.map +1 -0
  199. package/dist/core/api/parse.d.ts +36 -0
  200. package/dist/core/api/parse.d.ts.map +1 -0
  201. package/dist/core/api/parse.js +109 -0
  202. package/dist/core/api/parse.js.map +1 -0
  203. package/dist/core/api/server.d.ts +334 -0
  204. package/dist/core/api/server.d.ts.map +1 -0
  205. package/dist/core/api/server.js +324 -0
  206. package/dist/core/api/server.js.map +1 -0
  207. package/dist/core/channel-tree.d.ts +87 -0
  208. package/dist/core/channel-tree.d.ts.map +1 -0
  209. package/dist/core/channel-tree.js +378 -0
  210. package/dist/core/channel-tree.js.map +1 -0
  211. package/dist/core/command/command.d.ts +20 -0
  212. package/dist/core/command/command.d.ts.map +1 -0
  213. package/dist/core/command/command.js +112 -0
  214. package/dist/core/command/command.js.map +1 -0
  215. package/dist/core/command/ids.d.ts +131 -0
  216. package/dist/core/command/ids.d.ts.map +1 -0
  217. package/dist/core/command/ids.js +87 -0
  218. package/dist/core/command/ids.js.map +1 -0
  219. package/dist/core/command/parser.d.ts +17 -0
  220. package/dist/core/command/parser.d.ts.map +1 -0
  221. package/dist/core/command/parser.js +65 -0
  222. package/dist/core/command/parser.js.map +1 -0
  223. package/dist/core/command/request.d.ts +61 -0
  224. package/dist/core/command/request.d.ts.map +1 -0
  225. package/dist/core/command/request.js +64 -0
  226. package/dist/core/command/request.js.map +1 -0
  227. package/dist/core/command/responses.d.ts +73 -0
  228. package/dist/core/command/responses.d.ts.map +1 -0
  229. package/dist/core/command/responses.js +97 -0
  230. package/dist/core/command/responses.js.map +1 -0
  231. package/dist/core/command-name.d.ts +349 -0
  232. package/dist/core/command-name.d.ts.map +1 -0
  233. package/dist/core/command-name.js +347 -0
  234. package/dist/core/command-name.js.map +1 -0
  235. package/dist/core/enums.d.ts +188 -0
  236. package/dist/core/enums.d.ts.map +1 -0
  237. package/dist/core/enums.js +172 -0
  238. package/dist/core/enums.js.map +1 -0
  239. package/dist/core/errorcode.d.ts +413 -0
  240. package/dist/core/errorcode.d.ts.map +1 -0
  241. package/dist/core/errorcode.js +410 -0
  242. package/dist/core/errorcode.js.map +1 -0
  243. package/dist/core/errors.d.ts +119 -0
  244. package/dist/core/errors.d.ts.map +1 -0
  245. package/dist/core/errors.js +241 -0
  246. package/dist/core/errors.js.map +1 -0
  247. package/dist/core/events.d.ts +404 -0
  248. package/dist/core/events.d.ts.map +1 -0
  249. package/dist/core/events.js +6 -0
  250. package/dist/core/events.js.map +1 -0
  251. package/dist/core/helpers.d.ts +42 -0
  252. package/dist/core/helpers.d.ts.map +1 -0
  253. package/dist/core/helpers.js +156 -0
  254. package/dist/core/helpers.js.map +1 -0
  255. package/dist/core/index.d.ts +41 -0
  256. package/dist/core/index.d.ts.map +1 -0
  257. package/dist/core/index.js +40 -0
  258. package/dist/core/index.js.map +1 -0
  259. package/dist/core/internal.d.ts +24 -0
  260. package/dist/core/internal.d.ts.map +1 -0
  261. package/dist/core/internal.js +21 -0
  262. package/dist/core/internal.js.map +1 -0
  263. package/dist/core/middleware.d.ts +14 -0
  264. package/dist/core/middleware.d.ts.map +1 -0
  265. package/dist/core/middleware.js +18 -0
  266. package/dist/core/middleware.js.map +1 -0
  267. package/dist/core/notification-name.d.ts +160 -0
  268. package/dist/core/notification-name.d.ts.map +1 -0
  269. package/dist/core/notification-name.js +158 -0
  270. package/dist/core/notification-name.js.map +1 -0
  271. package/dist/core/notifications.d.ts +93 -0
  272. package/dist/core/notifications.d.ts.map +1 -0
  273. package/dist/core/notifications.js +301 -0
  274. package/dist/core/notifications.js.map +1 -0
  275. package/dist/core/permission-name.d.ts +1046 -0
  276. package/dist/core/permission-name.d.ts.map +1 -0
  277. package/dist/core/permission-name.js +1045 -0
  278. package/dist/core/permission-name.js.map +1 -0
  279. package/dist/core/permission.d.ts +31 -0
  280. package/dist/core/permission.d.ts.map +1 -0
  281. package/dist/core/permission.js +20 -0
  282. package/dist/core/permission.js.map +1 -0
  283. package/dist/core/property-name.d.ts +403 -0
  284. package/dist/core/property-name.d.ts.map +1 -0
  285. package/dist/core/property-name.js +405 -0
  286. package/dist/core/property-name.js.map +1 -0
  287. package/dist/core/reason.d.ts +39 -0
  288. package/dist/core/reason.d.ts.map +1 -0
  289. package/dist/core/reason.js +36 -0
  290. package/dist/core/reason.js.map +1 -0
  291. package/dist/core/reconnect.d.ts +59 -0
  292. package/dist/core/reconnect.d.ts.map +1 -0
  293. package/dist/core/reconnect.js +55 -0
  294. package/dist/core/reconnect.js.map +1 -0
  295. package/dist/core/types.d.ts +122 -0
  296. package/dist/core/types.d.ts.map +1 -0
  297. package/dist/core/types.js +27 -0
  298. package/dist/core/types.js.map +1 -0
  299. package/dist/query/client.d.ts +244 -0
  300. package/dist/query/client.d.ts.map +1 -0
  301. package/dist/query/client.js +1122 -0
  302. package/dist/query/client.js.map +1 -0
  303. package/dist/query/errors.d.ts +61 -0
  304. package/dist/query/errors.d.ts.map +1 -0
  305. package/dist/query/errors.js +100 -0
  306. package/dist/query/errors.js.map +1 -0
  307. package/dist/query/index.d.ts +36 -0
  308. package/dist/query/index.d.ts.map +1 -0
  309. package/dist/query/index.js +34 -0
  310. package/dist/query/index.js.map +1 -0
  311. package/dist/query/knownhosts.d.ts +61 -0
  312. package/dist/query/knownhosts.d.ts.map +1 -0
  313. package/dist/query/knownhosts.js +223 -0
  314. package/dist/query/knownhosts.js.map +1 -0
  315. package/dist/query/lines.d.ts +14 -0
  316. package/dist/query/lines.d.ts.map +1 -0
  317. package/dist/query/lines.js +29 -0
  318. package/dist/query/lines.js.map +1 -0
  319. package/dist/query/options.d.ts +206 -0
  320. package/dist/query/options.d.ts.map +1 -0
  321. package/dist/query/options.js +251 -0
  322. package/dist/query/options.js.map +1 -0
  323. package/dist/query/reconnect.d.ts +29 -0
  324. package/dist/query/reconnect.d.ts.map +1 -0
  325. package/dist/query/reconnect.js +87 -0
  326. package/dist/query/reconnect.js.map +1 -0
  327. package/dist/query/ssh.d.ts +385 -0
  328. package/dist/query/ssh.d.ts.map +1 -0
  329. package/dist/query/ssh.js +257 -0
  330. package/dist/query/ssh.js.map +1 -0
  331. package/dist/query/stream.d.ts +38 -0
  332. package/dist/query/stream.d.ts.map +1 -0
  333. package/dist/query/stream.js +70 -0
  334. package/dist/query/stream.js.map +1 -0
  335. package/dist/query/tcp.d.ts +38 -0
  336. package/dist/query/tcp.d.ts.map +1 -0
  337. package/dist/query/tcp.js +53 -0
  338. package/dist/query/tcp.js.map +1 -0
  339. package/dist/query/transport.d.ts +36 -0
  340. package/dist/query/transport.d.ts.map +1 -0
  341. package/dist/query/transport.js +2 -0
  342. package/dist/query/transport.js.map +1 -0
  343. package/dist/query/web.d.ts +78 -0
  344. package/dist/query/web.d.ts.map +1 -0
  345. package/dist/query/web.js +157 -0
  346. package/dist/query/web.js.map +1 -0
  347. package/dist/versions/index.d.ts +3094 -0
  348. package/dist/versions/index.d.ts.map +1 -0
  349. package/dist/versions/index.js +9237 -0
  350. package/dist/versions/index.js.map +1 -0
  351. package/package.json +119 -4
  352. package/src/client/abort.ts +24 -0
  353. package/src/client/address.ts +74 -0
  354. package/src/client/api/avatars.ts +164 -0
  355. package/src/client/api/voice.ts +177 -0
  356. package/src/client/capabilities.ts +106 -0
  357. package/src/client/client.ts +1976 -0
  358. package/src/client/commands.ts +201 -0
  359. package/src/client/connection-info.ts +52 -0
  360. package/src/client/crypto/crypt-ops.ts +6 -0
  361. package/src/client/crypto/crypt.ts +275 -0
  362. package/src/client/crypto/eax.ts +152 -0
  363. package/src/client/crypto/identity.ts +484 -0
  364. package/src/client/crypto/index.ts +21 -0
  365. package/src/client/crypto/primitives.ts +141 -0
  366. package/src/client/diagnostics.ts +58 -0
  367. package/src/client/discovery/index.ts +1 -0
  368. package/src/client/discovery/resolver.ts +325 -0
  369. package/src/client/errors.ts +217 -0
  370. package/src/client/events.ts +12 -0
  371. package/src/client/handshake/crypt-handshake.ts +157 -0
  372. package/src/client/handshake/crypt-init2.ts +48 -0
  373. package/src/client/handshake/index.ts +3 -0
  374. package/src/client/handshake/license.ts +208 -0
  375. package/src/client/handshake.ts +150 -0
  376. package/src/client/helpers.ts +10 -0
  377. package/src/client/hooks.ts +30 -0
  378. package/src/client/index.ts +97 -0
  379. package/src/client/reconnect.ts +43 -0
  380. package/src/client/throttle.ts +92 -0
  381. package/src/client/trace.ts +237 -0
  382. package/src/client/transfer.ts +436 -0
  383. package/src/client/transport/generation-window.ts +108 -0
  384. package/src/client/transport/handler.ts +1021 -0
  385. package/src/client/transport/index.ts +14 -0
  386. package/src/client/transport/packet.ts +69 -0
  387. package/src/client/transport/quicklz.ts +209 -0
  388. package/src/client/transport/traffic.ts +116 -0
  389. package/src/client/types.ts +478 -0
  390. package/src/client/view.ts +372 -0
  391. package/src/core/api/avatars.ts +40 -0
  392. package/src/core/api/channels.ts +420 -0
  393. package/src/core/api/clients.ts +1098 -0
  394. package/src/core/api/command.ts +737 -0
  395. package/src/core/api/credentials.ts +335 -0
  396. package/src/core/api/files.ts +411 -0
  397. package/src/core/api/groups.ts +730 -0
  398. package/src/core/api/icons.ts +175 -0
  399. package/src/core/api/instance.ts +369 -0
  400. package/src/core/api/moderation.ts +632 -0
  401. package/src/core/api/parse.ts +139 -0
  402. package/src/core/api/server.ts +769 -0
  403. package/src/core/channel-tree.ts +398 -0
  404. package/src/core/command/command.ts +125 -0
  405. package/src/core/command/ids.ts +153 -0
  406. package/src/core/command/parser.ts +68 -0
  407. package/src/core/command/request.ts +119 -0
  408. package/src/core/command/responses.ts +152 -0
  409. package/src/core/command-name.ts +350 -0
  410. package/src/core/enums.ts +215 -0
  411. package/src/core/errorcode.ts +412 -0
  412. package/src/core/errors.ts +266 -0
  413. package/src/core/events.ts +431 -0
  414. package/src/core/helpers.ts +161 -0
  415. package/src/core/index.ts +153 -0
  416. package/src/core/internal.ts +24 -0
  417. package/src/core/middleware.ts +24 -0
  418. package/src/core/notification-name.ts +161 -0
  419. package/src/core/notifications.ts +404 -0
  420. package/src/core/permission-name.ts +1162 -0
  421. package/src/core/permission.ts +34 -0
  422. package/src/core/property-name.ts +411 -0
  423. package/src/core/reason.ts +38 -0
  424. package/src/core/reconnect.ts +98 -0
  425. package/src/core/types.ts +150 -0
  426. package/src/query/client.ts +1303 -0
  427. package/src/query/errors.ts +108 -0
  428. package/src/query/index.ts +66 -0
  429. package/src/query/knownhosts.ts +257 -0
  430. package/src/query/lines.ts +28 -0
  431. package/src/query/options.ts +452 -0
  432. package/src/query/reconnect.ts +84 -0
  433. package/src/query/ssh.ts +674 -0
  434. package/src/query/stream.ts +91 -0
  435. package/src/query/tcp.ts +85 -0
  436. package/src/query/transport.ts +35 -0
  437. package/src/query/web.ts +234 -0
  438. package/src/versions/index.ts +10774 -0
@@ -0,0 +1,1666 @@
1
+ import {} from "./types.js";
2
+ import { ConnectPhase } from "./trace.js";
3
+ import { callHook } from "./hooks.js";
4
+ import { puzzleOf } from "./handshake/crypt-handshake.js";
5
+ import { asChannelID, asClientID, AlreadyConnectedError, backoffDelay, ClientStatus, ErrorCode, Reason, TeamspeakError, noopLogger, } from "@honeybbq/teamspeak";
6
+ import { HandshakeError, RemovedError } from "./errors.js";
7
+ import { abortable, abortReason } from "./abort.js";
8
+ import { Crypt } from "./crypto/index.js";
9
+ import { RESOLVE_BUDGET_MS, Resolver } from "./discovery/resolver.js";
10
+ import { PacketHandler } from "./transport/handler.js";
11
+ import { PacketType } from "./transport/packet.js";
12
+ import { CommandTracker, appendReturnCode } from "./commands.js";
13
+ import { buildCommand, buildCommandChain, encodeRequest, parseServerError, resolveBackoffPolicy, } from "../core/internal.js";
14
+ import { uploadFileData, downloadFileData } from "./transfer.js";
15
+ import { buildSetConnectionInfo } from "./connection-info.js";
16
+ import { buildEventChain } from "./events.js";
17
+ import { processInit1 } from "./handshake/crypt-handshake.js";
18
+ import { handleHandshakeInitIV, handleHandshakeExpand2, defaultClientVersion, handleInitServer, prepareClientPassword, } from "./handshake.js";
19
+ import { handleNotification } from "../core/internal.js";
20
+ import { parseChannel, parseCommand } from "../core/internal.js";
21
+ import { CommandThrottle } from "./throttle.js";
22
+ import { splitCommandRows, sleep } from "../core/internal.js";
23
+ import { isAutoNicknameMatch } from "./helpers.js";
24
+ import { CommandTimeoutError, NotConnectedError } from "@honeybbq/teamspeak";
25
+ import { responseSpec } from "../core/internal.js";
26
+ import { unknownServerCapabilities } from "./capabilities.js";
27
+ import { joinHostPort, parseConnectAddress, splitHostPort } from "./address.js";
28
+ import { commandChannel, incomingChannel } from "./diagnostics.js";
29
+ import { defaultShouldRetry } from "./reconnect.js";
30
+ import { ViewStore } from "./view.js";
31
+ /** The signature is Ed25519, 64 bytes; the server verifies it, so only its shape is checked. */
32
+ function normalizeClientVersion(version) {
33
+ if (version === undefined)
34
+ return defaultClientVersion;
35
+ const { platform, version: text, sign } = version;
36
+ for (const [name, value] of [
37
+ ["platform", platform],
38
+ ["version", text],
39
+ ["sign", sign],
40
+ ]) {
41
+ if (typeof value !== "string" || value === "") {
42
+ throw new TypeError(`a client version's ${name} is a non-empty string`);
43
+ }
44
+ }
45
+ const raw = Buffer.from(sign, "base64");
46
+ if (raw.length !== 64 || raw.toString("base64") !== sign) {
47
+ throw new RangeError("a client version's sign is the base64 of a 64-byte signature");
48
+ }
49
+ return { platform, version: text, sign };
50
+ }
51
+ function normalizeClientInitOptions(options) {
52
+ return {
53
+ serverPasswordHash: prepareClientPassword(options.serverPassword ?? ""),
54
+ defaultChannel: options.defaultChannel ?? "",
55
+ defaultChannelPasswordHash: prepareClientPassword(options.defaultChannelPassword ?? ""),
56
+ privilegeKey: options.privilegeKey ?? "",
57
+ metaData: options.metaData ?? "",
58
+ version: normalizeClientVersion(options.version),
59
+ };
60
+ }
61
+ let internalsOf;
62
+ let sentLineHook;
63
+ /**
64
+ * Observe every command line the client sends, as encoded. A seam for the
65
+ * package's own tests; not exported from the package.
66
+ *
67
+ * @internal
68
+ */
69
+ export function onSentLine(hook) {
70
+ sentLineHook = hook;
71
+ }
72
+ /** @internal The internals of `client`. */
73
+ export function clientInternals(client) {
74
+ return internalsOf(client);
75
+ }
76
+ /** How long a command waits for its answer unless told otherwise. */
77
+ const DEFAULT_COMMAND_TIMEOUT_MS = 10_000;
78
+ /**
79
+ * How long `disconnect()` waits for the server to acknowledge its goodbye
80
+ * unless told otherwise: the acknowledgement is a courtesy, and a connection
81
+ * that has already died would otherwise hold it for a full command timeout.
82
+ */
83
+ const DISCONNECT_TIMEOUT_MS = 1_000;
84
+ /** How long each reconnection attempt may take. */
85
+ const RECONNECT_ATTEMPT_TIMEOUT_MS = 30_000;
86
+ /** The `initserver` parameters a trace never sees, because they could carry a secret. */
87
+ const SECRET_INITSERVER_PARAMS = new Set([
88
+ "client_server_password",
89
+ "client_default_channel_password",
90
+ "client_default_token",
91
+ "client_key_offset",
92
+ ]);
93
+ function newConnectAttempt() {
94
+ let resolve;
95
+ let reject;
96
+ const promise = new Promise((res, rej) => {
97
+ resolve = res;
98
+ reject = rej;
99
+ });
100
+ // The failure is reported to whoever awaits connect(); an attempt nobody
101
+ // awaits any more must not surface as an unhandled rejection.
102
+ promise.catch(() => { });
103
+ const controller = new AbortController();
104
+ const attempt = {
105
+ promise,
106
+ signal: controller.signal,
107
+ settled: false,
108
+ trace: undefined,
109
+ onFail: undefined,
110
+ resolve() {
111
+ if (attempt.settled)
112
+ return;
113
+ attempt.settled = true;
114
+ resolve();
115
+ },
116
+ reject(error) {
117
+ if (attempt.settled)
118
+ return;
119
+ attempt.settled = true;
120
+ attempt.onFail?.(error);
121
+ controller.abort(error);
122
+ reject(error);
123
+ },
124
+ rejectAfter(error, until) {
125
+ if (attempt.settled)
126
+ return;
127
+ attempt.settled = true;
128
+ attempt.onFail?.(error);
129
+ controller.abort(error);
130
+ void until.finally(() => reject(error));
131
+ },
132
+ };
133
+ return attempt;
134
+ }
135
+ /**
136
+ * Voice deliveries are dropped while more than this many deliveries of any
137
+ * kind are waiting: audio is fifty packets a second per speaker, and a
138
+ * listener that cannot keep up would otherwise queue it without bound.
139
+ */
140
+ const MAX_PENDING_DELIVERIES = 256;
141
+ /** How often dropped voice is reported, at most. */
142
+ const VOICE_DROP_WARN_INTERVAL_MS = 10_000;
143
+ /** The only commands handled before crypto init completes. */
144
+ const PRE_INIT_COMMANDS = new Set(["clientinitiv", "initivexpand2", "error"]);
145
+ /**
146
+ * A TeamSpeak client over the voice protocol.
147
+ *
148
+ * Listeners run one at a time, in the order their events arrived, after the
149
+ * packet that carried them has been handled, so a listener may call the client
150
+ * and await an answer.
151
+ */
152
+ export class Client {
153
+ static {
154
+ internalsOf = (client) => client.#internals;
155
+ }
156
+ #crypt;
157
+ #handler;
158
+ #logger;
159
+ #nickname;
160
+ #clid = 0;
161
+ /**
162
+ * What the server reported about itself in initserver, including the UID
163
+ * hash it uses: TS3 hashes the public key with SHA-1 and TS6 with SHA-256.
164
+ */
165
+ #capabilities = unknownServerCapabilities();
166
+ #internals;
167
+ #identity;
168
+ #addr;
169
+ #resolver;
170
+ #clientInitOptions;
171
+ #socketFactory;
172
+ #fileTransferSocketFactory;
173
+ /** A copy of the caller's trace, so changing theirs later changes nothing. */
174
+ #trace;
175
+ /** The session in progress, for its `sessionEnd`; cleared when that has been reported. */
176
+ #session;
177
+ /** Undefined when the client does not reconnect. */
178
+ #reconnectOptions;
179
+ /**
180
+ * Set while a reconnect attempt is in flight, so overlapping failures do not
181
+ * start competing loops.
182
+ */
183
+ #reconnecting = false;
184
+ /** The reconnect loop's latest attempt; 0 while it has made none. */
185
+ #loopAttempt = 0;
186
+ /** The last answer of `shouldRetry`, so that one failure asks it once. */
187
+ #retryAnswer;
188
+ /**
189
+ * Why the last established session dropped: a reconnect loop whose own
190
+ * attempt connected and then dropped before the loop resumed retries for it.
191
+ */
192
+ #lastDrop;
193
+ /** Set by an explicit disconnect(), which must never be undone by a retry. */
194
+ #stopped = false;
195
+ /**
196
+ * The goodbye of a disconnect() still in progress, which a connect() waits
197
+ * for: tearing the old session down after a new one has started would end
198
+ * the new one instead.
199
+ */
200
+ #leaving;
201
+ /** Ends a disconnect()'s wait for its goodbye to be answered, while it waits. */
202
+ #goodbyeAnswered;
203
+ /** How many times disconnect() was called, so a waiting connect() sees one. */
204
+ #disconnects = 0;
205
+ /**
206
+ * Settles once the last queued `disconnected` has been delivered. A
207
+ * connect() waits for it, so the end of one session is never heard during
208
+ * the next: not by a listener added for the new session, and not by an
209
+ * events() iterator started for it.
210
+ */
211
+ #disconnectedDelivered;
212
+ /** Cuts short the reconnect loop's wait, on disconnect(). */
213
+ #stopReconnecting;
214
+ #status = ClientStatus.Disconnected;
215
+ #throttle = new CommandThrottle();
216
+ #cmdTrack = new CommandTracker(() => this.#clid);
217
+ #viewStore;
218
+ #clients;
219
+ #attempt = newConnectAttempt();
220
+ #listeners = new Map();
221
+ /** Deliveries waiting for their turn; see #emit. */
222
+ #queue = [];
223
+ #draining = false;
224
+ #voiceDropped = 0;
225
+ #voiceDropWarnedAt = -Infinity;
226
+ #eventMiddlewares;
227
+ #send;
228
+ #eventChain;
229
+ constructor(identity, addr, nickname, options = {}) {
230
+ this.#identity = identity;
231
+ this.#addr = addr;
232
+ this.#nickname = nickname;
233
+ this.#logger = options.logger ?? noopLogger;
234
+ this.#resolver = options.resolver ?? new Resolver({ nicknameLookup: options.nicknameLookup });
235
+ this.#clientInitOptions = normalizeClientInitOptions(options);
236
+ this.#socketFactory = options.socketFactory;
237
+ this.#fileTransferSocketFactory = options.fileTransferSocketFactory;
238
+ this.#reconnectOptions = options.reconnect;
239
+ this.#trace = options.trace === undefined ? undefined : { ...options.trace };
240
+ this.#viewStore = new ViewStore(this.#logger, () => this.#clid);
241
+ this.#clients = this.#viewStore.roster;
242
+ this.#crypt = new Crypt(identity);
243
+ this.#handler = this.#newHandler();
244
+ // oxlint-disable-next-line no-this-alias -- accessors cannot be arrow functions
245
+ const self = this;
246
+ this.#internals = {
247
+ get crypt() {
248
+ return self.#crypt;
249
+ },
250
+ get handler() {
251
+ return self.#handler;
252
+ },
253
+ get logger() {
254
+ return self.#logger;
255
+ },
256
+ get initOptions() {
257
+ return self.#clientInitOptions;
258
+ },
259
+ get nickname() {
260
+ return self.#nickname;
261
+ },
262
+ set nickname(value) {
263
+ self.#nickname = value;
264
+ },
265
+ get clid() {
266
+ return self.#clid;
267
+ },
268
+ set clid(value) {
269
+ self.#clid = value;
270
+ },
271
+ get clients() {
272
+ return self.#clients;
273
+ },
274
+ get capabilities() {
275
+ return self.#capabilities;
276
+ },
277
+ set capabilities(value) {
278
+ self.#capabilities = value;
279
+ },
280
+ markConnected: (initServer) => this.#markConnected(initServer),
281
+ markHandshakeDone: (serverPublicKey) => this.#markHandshakeDone(serverPublicKey),
282
+ execNoWait: (command) => this.#execNoWait(command, undefined, true),
283
+ handlePacket: (packet) => this.#handlePacket(packet),
284
+ };
285
+ // Built once: the chains are read on every command and event, and
286
+ // changing them while those run would make the behaviour depend on timing.
287
+ const cmdMiddlewares = options.commandMiddleware ?? [];
288
+ this.#eventMiddlewares = options.eventMiddleware ?? [];
289
+ this.#send = buildCommandChain(cmdMiddlewares, (request, commandOptions = {}) => this.#exec(encodeRequest(request, prepareClientPassword), commandOptions, false));
290
+ this.#eventChain = buildEventChain([...this.#eventMiddlewares], (event) => this.#deliver(event.name, event.payload));
291
+ }
292
+ /**
293
+ * What this client knows of the server: the channel tree, the clients it
294
+ * has been told about, and itself. Always there, and `"empty"` until the
295
+ * first `connect()`.
296
+ *
297
+ * @experimental New in 1.x and may change in a minor release.
298
+ */
299
+ get view() {
300
+ return this.#viewStore.view;
301
+ }
302
+ /** Where the client is in its connection's life. */
303
+ get status() {
304
+ return this.#status;
305
+ }
306
+ // ---- Connection -----------------------------------------------------------
307
+ /**
308
+ * Connect to the server. Resolves once the handshake completes.
309
+ *
310
+ * An address that cannot be one — no host, a port outside 1–65535 — rejects
311
+ * with an InvalidAddressError at once, before anything starts. A host name
312
+ * that does not resolve fails the attempt with a TeamspeakError whose
313
+ * `cause` is the lookup's error.
314
+ *
315
+ * A failed attempt rejects with its cause, such as the server's error for a
316
+ * rejected password. Aborting `signal` abandons the attempt and rejects with
317
+ * the signal's reason. The signal bounds only the connecting: once this
318
+ * resolves, the connection lasts until `disconnect()` or a drop.
319
+ * `connect()` does not retry; the `reconnect` option only retries a
320
+ * connection that drops after it was established.
321
+ *
322
+ * Called while a `disconnect()` is still saying goodbye, or while an earlier
323
+ * session's `disconnected` is still waiting to be delivered, this waits for
324
+ * that first, so the old session is closed, and its `disconnected`
325
+ * delivered, before the new one starts: no listener or `events()` iterator
326
+ * of the new session hears the old one end. A `disconnect()` called during
327
+ * that wait wins: this then rejects with a NotConnectedError.
328
+ *
329
+ * `connected` is delivered after this resolves, so a listener added right
330
+ * after `await client.connect()` still hears it.
331
+ */
332
+ async connect(options = {}) {
333
+ return this.#connect(options, 1, false);
334
+ }
335
+ async #connect(options, attemptNumber, reconnect) {
336
+ const { signal } = options;
337
+ signal?.throwIfAborted();
338
+ const disconnects = this.#disconnects;
339
+ if (this.#leaving !== undefined)
340
+ await abortable(this.#leaving, signal);
341
+ while (this.#disconnectedDelivered !== undefined) {
342
+ await abortable(this.#disconnectedDelivered, signal);
343
+ }
344
+ if (this.#disconnects !== disconnects)
345
+ throw new NotConnectedError();
346
+ if (this.#status !== ClientStatus.Disconnected)
347
+ throw new AlreadyConnectedError();
348
+ // Checked before anything changes: an address that can never work leaves
349
+ // the client as it was, with no attempt to announce the end of.
350
+ parseConnectAddress(this.#addr);
351
+ this.#stopped = false;
352
+ this.#resetForConnect();
353
+ this.#status = ClientStatus.Connecting;
354
+ const attempt = this.#attempt;
355
+ const trace = {
356
+ attempt: attemptNumber,
357
+ reconnect,
358
+ startedAt: performance.now(),
359
+ connectStartedAt: 0,
360
+ phase: ConnectPhase.Open,
361
+ target: "",
362
+ sessionStarted: false,
363
+ welcomeDone: false,
364
+ channels: 0,
365
+ };
366
+ attempt.trace = trace;
367
+ attempt.onFail = (error) => this.#callHook(this.#trace?.attemptFailed, () => ({
368
+ attempt: trace.attempt,
369
+ reconnect: trace.reconnect,
370
+ phase: trace.phase,
371
+ error,
372
+ target: trace.target,
373
+ durationMs: performance.now() - trace.startedAt,
374
+ }));
375
+ // The handshake may complete just as the signal fires, in which case
376
+ // #failConnection leaves the session alone and the caller gets the
377
+ // connection it made rather than one it would believe had failed.
378
+ const onAbort = () => this.#failConnection(abortReason(signal));
379
+ signal?.addEventListener("abort", onAbort, { once: true });
380
+ try {
381
+ try {
382
+ this.#callHook(this.#trace?.resolveStart, () => ({
383
+ attempt: trace.attempt,
384
+ reconnect: trace.reconnect,
385
+ addr: this.#addr,
386
+ }));
387
+ const resolveStarted = performance.now();
388
+ const resolution = await this.#resolveAddr(attempt.signal);
389
+ const targetAddr = resolution.target;
390
+ trace.target = targetAddr;
391
+ if (!attempt.settled) {
392
+ this.#callHook(this.#trace?.resolveDone, () => ({
393
+ attempt: trace.attempt,
394
+ reconnect: trace.reconnect,
395
+ addr: this.#addr,
396
+ target: targetAddr,
397
+ source: resolution.source,
398
+ candidates: resolution.candidates,
399
+ error: resolution.error,
400
+ durationMs: performance.now() - resolveStarted,
401
+ }));
402
+ }
403
+ if (!attempt.settled) {
404
+ this.#logger.info("connecting to server", { address: targetAddr });
405
+ trace.connectStartedAt = performance.now();
406
+ this.#callHook(this.#trace?.connectStart, () => ({
407
+ attempt: trace.attempt,
408
+ reconnect: trace.reconnect,
409
+ target: targetAddr,
410
+ }));
411
+ }
412
+ if (!attempt.settled) {
413
+ await this.#handler.connect(targetAddr, attempt.signal);
414
+ // The socket is open and the first packet is out: a failure from
415
+ // here to the shared secret is the handshake's.
416
+ if (trace.phase === ConnectPhase.Open)
417
+ trace.phase = ConnectPhase.Handshake;
418
+ }
419
+ }
420
+ catch (error) {
421
+ // Without this the client stays Connecting and every later connect()
422
+ // fails with AlreadyConnectedError.
423
+ this.#failConnection(error);
424
+ }
425
+ await attempt.promise;
426
+ }
427
+ finally {
428
+ signal?.removeEventListener("abort", onAbort);
429
+ }
430
+ }
431
+ /**
432
+ * Leave the server and close the connection.
433
+ *
434
+ * Says goodbye first, waiting for the server's acknowledgement for a second
435
+ * at most, or less if `signal` aborts sooner. The connection is closed
436
+ * either way. A `connect()` still waiting for its handshake rejects with a
437
+ * NotConnectedError, and so do the commands still waiting for an answer.
438
+ * Resolves once `disconnected` has been delivered to the listeners, so one
439
+ * added after `await client.disconnect()` does not hear it. Does nothing on
440
+ * a client that is not connected; called again while an earlier call is
441
+ * still saying goodbye, it waits for that one (and its message is the first
442
+ * call's).
443
+ *
444
+ * `reasonMessage` is what the server tells the other clients who see this
445
+ * one leave, in `clientLeave`'s `reasonMessage` (`reasonmsg` of
446
+ * `clientdisconnect`, sent with `reasonid=8`, `Reason.Disconnect`: measured
447
+ * on TeamSpeak 3 and 6, the server drops the message without it);
448
+ * default `"Shutdown"`.
449
+ */
450
+ async disconnect(options = {}) {
451
+ // Recorded before anything else: a dropped socket during the goodbye below
452
+ // must not be mistaken for an outage worth retrying.
453
+ this.#stopped = true;
454
+ this.#disconnects++;
455
+ this.#stopReconnecting?.abort();
456
+ if (this.#leaving !== undefined)
457
+ return this.#leaving;
458
+ // Already ended, by a drop or a failed attempt: its `disconnected` may
459
+ // still be on its way.
460
+ if (this.#status === ClientStatus.Disconnected)
461
+ return this.#disconnectedDelivered;
462
+ const wasConnected = this.#status === ClientStatus.Connected;
463
+ this.#status = ClientStatus.Disconnected;
464
+ let left;
465
+ this.#attempt.rejectAfter(new NotConnectedError(), new Promise((r) => (left = r)));
466
+ const leaving = this.#leave(wasConnected, options.signal, options.reasonMessage ?? "Shutdown").finally(left);
467
+ this.#leaving = leaving;
468
+ try {
469
+ await leaving;
470
+ }
471
+ finally {
472
+ this.#leaving = undefined;
473
+ }
474
+ }
475
+ async #leave(wasConnected, signal, reasonMessage) {
476
+ this.#logger.info("disconnecting from server");
477
+ if (wasConnected) {
478
+ this.#callHook(this.#trace?.disconnectStart, () => ({}));
479
+ const goodbyeStarted = performance.now();
480
+ // The server answers clientdisconnect with a notifyclientleftview about
481
+ // this client, and no error line; either one ends the wait. A goodbye
482
+ // that is not answered in time is a courtesy lost: the connection
483
+ // closes regardless.
484
+ const left = new Promise((resolve) => {
485
+ this.#goodbyeAnswered = () => resolve(true);
486
+ });
487
+ // On the internal path: the status already says Disconnected.
488
+ const answered = this.#exec(buildCommand("clientdisconnect", {
489
+ reasonid: String(Reason.Disconnect),
490
+ reasonmsg: reasonMessage,
491
+ }), { signal, timeoutMs: DISCONNECT_TIMEOUT_MS }, true).then(() => true, () => false);
492
+ const acknowledged = await Promise.race([answered, left]);
493
+ this.#goodbyeAnswered = undefined;
494
+ this.#callHook(this.#trace?.disconnectDone, () => ({
495
+ acknowledged,
496
+ durationMs: performance.now() - goodbyeStarted,
497
+ }));
498
+ }
499
+ this.#handler.close();
500
+ this.#cmdTrack.reset(new NotConnectedError());
501
+ this.#endSession(undefined);
502
+ // Announced here; #handleConnectionClosed stays quiet once the status is
503
+ // already Disconnected, so listeners hear about it once.
504
+ await this.#announceDisconnected(undefined, false);
505
+ }
506
+ /** `await using client = …` disconnects when the scope ends. */
507
+ [Symbol.asyncDispose]() {
508
+ return this.disconnect();
509
+ }
510
+ // ---- Commands ------------------------------------------------------------
511
+ /**
512
+ * Send a raw command line and return the rows that answer it: the escape
513
+ * hatch for a command the typed functions do not cover. The line is sent as
514
+ * given, so its values must already be escaped and any channel password
515
+ * already hashed. `CommandName` lists the command names.
516
+ *
517
+ * Rejects with a NotConnectedError unless the client is connected: while it
518
+ * is still connecting, the session's keys may not exist yet.
519
+ */
520
+ // oxlint-disable-next-line typescript/array-type -- the public API report spells this `[]`; respelling it changes the report
521
+ exec(command, options = {}) {
522
+ return this.#exec(command, options, false);
523
+ }
524
+ /**
525
+ * Send a raw command line without waiting for an answer, for the few
526
+ * commands the server answers with nothing at all. `CommandName` lists
527
+ * the command names.
528
+ *
529
+ * Commands are paced by a flood throttle; aborting `signal` while this waits
530
+ * for its turn rejects with the signal's reason and sends nothing. Rejects
531
+ * with a NotConnectedError unless the client is connected.
532
+ */
533
+ execNoWait(command, options = {}) {
534
+ return this.#execNoWait(command, options.signal, false);
535
+ }
536
+ /**
537
+ * Whether a command may go out now. The handshake's own commands (`internal`)
538
+ * need only an open socket; everything else needs a connected session, so
539
+ * nothing a caller sends can go out under the public dummy key, or after
540
+ * disconnect() has begun saying goodbye.
541
+ */
542
+ #canSend(internal) {
543
+ return this.#handler.isOpen && (internal || this.#status === ClientStatus.Connected);
544
+ }
545
+ async #execNoWait(command, signal, internal) {
546
+ signal?.throwIfAborted();
547
+ if (!this.#canSend(internal))
548
+ throw new NotConnectedError();
549
+ await this.#throttle.wait(signal);
550
+ if (!this.#canSend(internal))
551
+ throw new NotConnectedError();
552
+ this.#sendLine(command);
553
+ }
554
+ /**
555
+ * Send a typed command; this is the CommandExecutor the typed commands run on.
556
+ *
557
+ * The request passes through the `commandMiddleware` first. Channel
558
+ * passwords are then hashed, because that is this protocol's rule: the voice
559
+ * protocol expects `base64(sha1(password))`, where ServerQuery takes the
560
+ * password itself.
561
+ */
562
+ // oxlint-disable-next-line typescript/array-type -- the public API report spells this `[]`; respelling it changes the report
563
+ send(request, options = {}) {
564
+ return this.#send(request, options);
565
+ }
566
+ #sendLine(line) {
567
+ const hook = sentLineHook;
568
+ if (hook)
569
+ hook(this, line);
570
+ this.#handler.sendPacket(PacketType.Command, Buffer.from(line), 0);
571
+ }
572
+ #exec(command, options, internal) {
573
+ if (!commandChannel.hasSubscribers)
574
+ return this.#execUntraced(command, options, internal);
575
+ const trace = { command: parseCommand(command)?.name ?? "", client: this };
576
+ return commandChannel.tracePromise(() => this.#execUntraced(command, options, internal), trace);
577
+ }
578
+ async #execUntraced(command, options, internal) {
579
+ const { signal, timeoutMs = DEFAULT_COMMAND_TIMEOUT_MS } = options;
580
+ signal?.throwIfAborted();
581
+ // A command sent on no socket used to vanish and surface 10 seconds later
582
+ // as a timeout, which reads like a slow server rather than a usage error.
583
+ if (!this.#canSend(internal))
584
+ throw new NotConnectedError();
585
+ // Which notifications answer this command, and — for answers that arrive
586
+ // after the `error` — the request field they echo back.
587
+ const parsed = parseCommand(command);
588
+ const spec = responseSpec(parsed?.name ?? "");
589
+ const key = spec?.correlated?.key;
590
+ const correlation = key === undefined ? undefined : parsed?.params[key];
591
+ const echo = spec?.echoKey === undefined ? undefined : parsed?.params[spec.echoKey];
592
+ const [rc, answered] = this.#cmdTrack.register(spec, correlation, echo);
593
+ let timer;
594
+ let onAbort;
595
+ // Started before the throttle: the timeout bounds the whole command, its
596
+ // wait for a turn included, and so does the signal.
597
+ const givenUp = new Promise((_, reject) => {
598
+ timer = setTimeout(() => reject(new CommandTimeoutError(parsed?.name ?? "")), timeoutMs);
599
+ onAbort = () => reject(abortReason(signal));
600
+ signal?.addEventListener("abort", onAbort, { once: true });
601
+ });
602
+ givenUp.catch(() => { });
603
+ const throttleAbort = new AbortController();
604
+ try {
605
+ const withRc = appendReturnCode(command, rc);
606
+ await Promise.race([this.#throttle.wait(throttleAbort.signal), givenUp]);
607
+ if (!this.#canSend(internal))
608
+ throw new NotConnectedError();
609
+ this.#sendLine(withRc);
610
+ const result = await Promise.race([answered, givenUp]);
611
+ if (result.err)
612
+ throw result.err;
613
+ return result.data;
614
+ }
615
+ finally {
616
+ // A command given up while it waited for its turn gives the turn back.
617
+ throttleAbort.abort();
618
+ // Cleared, not left to fire: a pending timer keeps the process alive for
619
+ // the full timeout after the last command has long been answered.
620
+ clearTimeout(timer);
621
+ if (onAbort)
622
+ signal?.removeEventListener("abort", onAbort);
623
+ this.#cmdTrack.unregister(rc);
624
+ }
625
+ }
626
+ // ---- Events --------------------------------------------------------------
627
+ /**
628
+ * Listen for an event. Returns a function that removes the listener.
629
+ *
630
+ * The event is named by an {@link EventName} constant or by its string, and
631
+ * the listener's payload type follows from it:
632
+ *
633
+ * ```ts
634
+ * client.on(EventName.TextMessage, (message) => console.log(message.message));
635
+ * ```
636
+ *
637
+ * A listener that throws does not stop the others: the rest of the event's
638
+ * listeners, and the events after it, are still delivered, and the exception
639
+ * is rethrown on its own, from a microtask, so it surfaces as an
640
+ * `uncaughtException` just as an EventEmitter listener's would. An async
641
+ * listener's rejection is not caught, and surfaces as an unhandled
642
+ * rejection.
643
+ */
644
+ on(event, listener) {
645
+ const subscription = {
646
+ listener: listener,
647
+ removed: false,
648
+ };
649
+ // A new array rather than an edit in place: a delivery may be iterating
650
+ // over the old one.
651
+ this.#listeners.set(event, [...(this.#listeners.get(event) ?? []), subscription]);
652
+ const unsubscribe = () => {
653
+ if (subscription.removed)
654
+ return;
655
+ subscription.removed = true;
656
+ const list = this.#listeners.get(event) ?? [];
657
+ this.#listeners.set(event, list.filter((s) => s !== subscription));
658
+ };
659
+ return Object.assign(unsubscribe, { [Symbol.dispose]: unsubscribe });
660
+ }
661
+ /** Listen for the next occurrence of an event only. */
662
+ once(event, listener) {
663
+ const unsubscribe = this.on(event, ((payload) => {
664
+ unsubscribe();
665
+ listener(payload);
666
+ }));
667
+ return unsubscribe;
668
+ }
669
+ /**
670
+ * An event as an async iterator, for `for await`:
671
+ *
672
+ * ```ts
673
+ * for await (const message of client.events("textMessage", { signal })) { … }
674
+ * ```
675
+ *
676
+ * Events that arrive while the loop body runs are buffered, so none is
677
+ * missed. The buffer is unbounded: an iterator that is not consumed as fast
678
+ * as events arrive holds every one of them in memory until it is read, so
679
+ * stop iterating (or abort `signal`) when you stop reading. Voice is the
680
+ * exception: at most 256 `voiceData` frames are buffered, and the rest are
681
+ * dropped, as for a listener that falls behind.
682
+ *
683
+ * Iteration ends when the loop exits early (`break`, `return`, a throw), and
684
+ * — for every event but `disconnected` — when the client disconnects. When
685
+ * `signal` aborts, listening stops at once, whatever is buffered is
686
+ * discarded, and the pending and every later `next()` reject with the
687
+ * signal's reason, as `events.on` in Node does.
688
+ *
689
+ * `next()` may be called again before an earlier call has settled; the calls
690
+ * are answered in order.
691
+ */
692
+ events(event, options = {}) {
693
+ const { signal } = options;
694
+ const buffered = [];
695
+ // The next() calls waiting for an event, oldest first.
696
+ const waiting = [];
697
+ let ended = false;
698
+ let aborted = false;
699
+ const finishWaiting = () => {
700
+ for (const waiter of waiting.splice(0)) {
701
+ waiter.resolve({ value: undefined, done: true });
702
+ }
703
+ };
704
+ const stopListening = this.on(event, ((payload) => {
705
+ if (ended)
706
+ return;
707
+ const waiter = waiting.shift();
708
+ if (waiter) {
709
+ waiter.resolve({ value: payload, done: false });
710
+ return;
711
+ }
712
+ if (event === "voiceData" && buffered.length >= MAX_PENDING_DELIVERIES) {
713
+ this.#dropVoice();
714
+ return;
715
+ }
716
+ buffered.push(payload);
717
+ }));
718
+ // Ends after what is already buffered, which arrived before the disconnect.
719
+ const stopOnDisconnect = event === "disconnected"
720
+ ? undefined
721
+ : this.on("disconnected", () => {
722
+ cleanup();
723
+ finishWaiting();
724
+ });
725
+ const cleanup = () => {
726
+ ended = true;
727
+ stopListening();
728
+ stopOnDisconnect?.();
729
+ signal?.removeEventListener("abort", abort);
730
+ };
731
+ const abort = () => {
732
+ aborted = true;
733
+ buffered.length = 0;
734
+ cleanup();
735
+ const reason = abortReason(signal);
736
+ for (const waiter of waiting.splice(0))
737
+ waiter.reject(reason);
738
+ };
739
+ if (signal?.aborted)
740
+ abort();
741
+ else
742
+ signal?.addEventListener("abort", abort, { once: true });
743
+ const iterator = {
744
+ next() {
745
+ if (aborted)
746
+ return Promise.reject(abortReason(signal));
747
+ if (buffered.length > 0)
748
+ return Promise.resolve({ value: buffered.shift(), done: false });
749
+ if (ended) {
750
+ cleanup();
751
+ return Promise.resolve({ value: undefined, done: true });
752
+ }
753
+ return new Promise((resolve, reject) => waiting.push({ resolve, reject }));
754
+ },
755
+ return() {
756
+ buffered.length = 0;
757
+ cleanup();
758
+ finishWaiting();
759
+ return Promise.resolve({ value: undefined, done: true });
760
+ },
761
+ [Symbol.asyncIterator]() {
762
+ return iterator;
763
+ },
764
+ };
765
+ return iterator;
766
+ }
767
+ // ---- API shorthand -------------------------------------------------------
768
+ /** This client's id on the server, assigned in the handshake; 0 before it. */
769
+ get clientID() {
770
+ return asClientID(this.#clid);
771
+ }
772
+ /**
773
+ * What the connected server is: version, generation, and the uid hash it
774
+ * uses. Read from `initserver`; before connecting the family is `unknown`
775
+ * and the uid hash SHA-1.
776
+ */
777
+ get serverCapabilities() {
778
+ return this.#capabilities;
779
+ }
780
+ /**
781
+ * Our own `client_unique_identifier`, hashed with the algorithm the connected
782
+ * server uses.
783
+ *
784
+ * TS3 uses SHA-1 and TS6 SHA-256, so this reads the algorithm learned during
785
+ * initserver rather than assuming one. Comparing a hard-coded SHA-1 UID to a
786
+ * TS6 server silently never matches.
787
+ */
788
+ get uid() {
789
+ return this.#identity.uid(this.#capabilities.uidAlgorithm);
790
+ }
791
+ /**
792
+ * The host this client connects to, as it was given, without the port: where
793
+ * the file transfer port is, for `uploadFileData` and the avatar and icon
794
+ * helpers.
795
+ */
796
+ get host() {
797
+ return splitHostPort(this.#addr).host;
798
+ }
799
+ /** The channel this client is in, as it last learned; 0 before connecting. */
800
+ get channelID() {
801
+ return this.#clients.get(this.#clid)?.channelID ?? asChannelID(0n);
802
+ }
803
+ /**
804
+ * Send Opus voice directly to clients, to channels, or to both: the
805
+ * packet's direct form. Throws a NotConnectedError unless the client is
806
+ * connected, and a RangeError for no target at all, more than 32 clients,
807
+ * more than 255 channels, or an id the packet cannot hold. Whether a target
808
+ * hears it is the server's decision, from `i_client_whisper_power` against
809
+ * the target's `i_client_needed_whisper_power`.
810
+ */
811
+ sendWhisper(data, targets, codec) {
812
+ if (!this.#canSend(false))
813
+ throw new NotConnectedError();
814
+ this.#handler.sendWhisperPacket(data, [...(targets.clients ?? [])], codec, [
815
+ ...(targets.channels ?? []),
816
+ ]);
817
+ }
818
+ /**
819
+ * Send Opus voice to a group of clients: a server group, a channel group,
820
+ * the channel commanders or everyone, within the channels the target's
821
+ * `scope` picks. The packet's group form; `sendWhisper` is the direct one.
822
+ * Throws a NotConnectedError unless the client is connected, and a
823
+ * RangeError for a type, scope or group id the packet cannot hold. Whether
824
+ * a client hears it is the server's decision, as for `sendWhisper`.
825
+ */
826
+ sendGroupWhisper(data, target, codec) {
827
+ if (!this.#canSend(false))
828
+ throw new NotConnectedError();
829
+ const id = "group" in target ? target.group : 0n;
830
+ this.#handler.sendGroupWhisperPacket(data, target.type, target.scope, id, codec);
831
+ }
832
+ /**
833
+ * Send one encoded audio frame to the channel this client is in. Throws a
834
+ * NotConnectedError unless the client is connected.
835
+ */
836
+ sendVoice(data, codec, options) {
837
+ // Reserved: VoiceOptions has no fields to read yet.
838
+ void options;
839
+ if (!this.#canSend(false))
840
+ throw new NotConnectedError();
841
+ this.#handler.sendVoicePacket(data, codec);
842
+ }
843
+ // ---- File Transfer -------------------------------------------------------
844
+ /**
845
+ * Upload `data` over a negotiated transfer, through the client's
846
+ * `fileTransferSocketFactory` if it has one.
847
+ */
848
+ uploadFileData(host, info, data, options = {}) {
849
+ return uploadFileData(host, info, data, {
850
+ ...options,
851
+ socketFactory: this.#fileTransferSocketFactory,
852
+ });
853
+ }
854
+ /**
855
+ * Download a negotiated transfer into `dest`, through the client's
856
+ * `fileTransferSocketFactory` if it has one.
857
+ */
858
+ downloadFileData(host, info, dest, options = {}) {
859
+ return downloadFileData(host, info, dest, {
860
+ ...options,
861
+ socketFactory: this.#fileTransferSocketFactory,
862
+ });
863
+ }
864
+ // ---- Internal (package-visible) ------------------------------------------
865
+ #markConnected(initServer = {}) {
866
+ // Only the attempt in progress can complete: never one that disconnect()
867
+ // or a failure has already ended.
868
+ if (this.#status !== ClientStatus.Connecting || this.#attempt.settled)
869
+ return;
870
+ this.#status = ClientStatus.Connected;
871
+ // A privilege key is spent by the join that used it: a reconnection must
872
+ // not present it again.
873
+ if (this.#clientInitOptions.privilegeKey !== "") {
874
+ this.#clientInitOptions = { ...this.#clientInitOptions, privilegeKey: "" };
875
+ }
876
+ // Settled before the hook runs, so that nothing it does can turn this
877
+ // attempt into a failed one; connect() resumes only after the hook returns.
878
+ this.#attempt.resolve();
879
+ const trace = this.#attempt.trace;
880
+ if (trace !== undefined) {
881
+ trace.sessionStarted = true;
882
+ this.#session = {
883
+ clientID: asClientID(this.#clid),
884
+ startedAt: performance.now(),
885
+ attempt: trace.attempt,
886
+ reconnect: trace.reconnect,
887
+ };
888
+ this.#callHook(this.#trace?.sessionStart, () => {
889
+ const safe = {};
890
+ for (const [key, value] of Object.entries(initServer)) {
891
+ if (!SECRET_INITSERVER_PARAMS.has(key))
892
+ safe[key] = value;
893
+ }
894
+ const talkPower = Number.parseInt(initServer["client_talk_power"] ?? "", 10);
895
+ return {
896
+ attempt: trace.attempt,
897
+ reconnect: trace.reconnect,
898
+ clientID: asClientID(this.#clid),
899
+ nickname: initServer["acn"] ?? this.#nickname,
900
+ talkPower: Number.isNaN(talkPower) ? 0 : talkPower,
901
+ initServer: Object.freeze(safe),
902
+ capabilities: this.#capabilities,
903
+ durationMs: performance.now() - trace.connectStartedAt,
904
+ };
905
+ });
906
+ }
907
+ this.#emit("connected", { reconnected: trace?.reconnect ?? false });
908
+ }
909
+ /** The socket is open: the attempt's handshake begins. */
910
+ #markConnectDone(socket) {
911
+ const trace = this.#attempt.trace;
912
+ if (trace === undefined || this.#attempt.settled || trace.phase !== ConnectPhase.Open)
913
+ return;
914
+ trace.phase = ConnectPhase.Handshake;
915
+ this.#callHook(this.#trace?.connectDone, () => {
916
+ let localAddr = "";
917
+ try {
918
+ const local = socket.address?.();
919
+ if (local !== undefined)
920
+ localAddr = joinHostPort(local.address, String(local.port));
921
+ }
922
+ catch {
923
+ // A socket that cannot say where it is bound has no local address to report.
924
+ }
925
+ return {
926
+ attempt: trace.attempt,
927
+ reconnect: trace.reconnect,
928
+ target: trace.target,
929
+ localAddr,
930
+ durationMs: performance.now() - trace.connectStartedAt,
931
+ };
932
+ });
933
+ }
934
+ #markHandshakeDone(serverPublicKey = "") {
935
+ const trace = this.#attempt.trace;
936
+ if (trace === undefined || this.#attempt.settled || trace.phase !== ConnectPhase.Handshake) {
937
+ return;
938
+ }
939
+ trace.phase = ConnectPhase.Join;
940
+ this.#callHook(this.#trace?.handshakeDone, () => {
941
+ const puzzle = puzzleOf(this.#crypt);
942
+ return {
943
+ attempt: trace.attempt,
944
+ reconnect: trace.reconnect,
945
+ durationMs: performance.now() - trace.connectStartedAt,
946
+ puzzleLevel: puzzle.level,
947
+ puzzleDurationMs: puzzle.durationMs,
948
+ // The handler is this attempt's own, so its count starts at the socket.
949
+ resends: this.#handler.resends,
950
+ serverPublicKey,
951
+ };
952
+ });
953
+ }
954
+ #markWelcomeDone() {
955
+ const trace = this.#attempt.trace;
956
+ if (trace === undefined || !trace.sessionStarted || trace.welcomeDone)
957
+ return;
958
+ if (this.#status !== ClientStatus.Connected)
959
+ return;
960
+ trace.welcomeDone = true;
961
+ this.#callHook(this.#trace?.welcomeDone, () => ({
962
+ attempt: trace.attempt,
963
+ reconnect: trace.reconnect,
964
+ channelID: this.channelID,
965
+ channels: trace.channels,
966
+ clients: this.#clients.size,
967
+ durationMs: performance.now() - trace.connectStartedAt,
968
+ }));
969
+ }
970
+ /** Report the end of the session in progress, once. */
971
+ #endSession(error) {
972
+ this.#viewStore.end(error);
973
+ const session = this.#session;
974
+ if (session === undefined)
975
+ return;
976
+ this.#session = undefined;
977
+ this.#callHook(this.#trace?.sessionEnd, () => ({
978
+ clientID: session.clientID,
979
+ error,
980
+ durationMs: performance.now() - session.startedAt,
981
+ attempt: session.attempt,
982
+ reconnect: session.reconnect,
983
+ }));
984
+ }
985
+ /**
986
+ * Call a trace hook, if there is one, and never let it touch the
987
+ * connection: see callHook. The info is built only when there is a hook.
988
+ */
989
+ #callHook(hook, info) {
990
+ callHook(hook, this.#trace, info);
991
+ }
992
+ // ---- Private -------------------------------------------------------------
993
+ #newHandler() {
994
+ const handler = new PacketHandler(this.#crypt, this.#logger, this.#socketFactory);
995
+ // Each callback checks that its handler is still the current one: a
996
+ // packet or close reported late by one a later connect() already replaced
997
+ // must not reach, or tear down, the new connection.
998
+ handler.onPacket = (p) => {
999
+ if (handler === this.#handler)
1000
+ this.#handlePacket(p);
1001
+ };
1002
+ handler.onClosed = (err) => {
1003
+ if (handler === this.#handler)
1004
+ this.#handleConnectionClosed(err);
1005
+ };
1006
+ handler.onOpen = (socket) => {
1007
+ if (handler === this.#handler)
1008
+ this.#markConnectDone(socket);
1009
+ };
1010
+ handler.onReceiveError = (error) => {
1011
+ if (handler === this.#handler)
1012
+ this.#handleReceiveError(error);
1013
+ };
1014
+ return handler;
1015
+ }
1016
+ #resetForConnect() {
1017
+ this.#handler.close();
1018
+ this.#crypt = new Crypt(this.#identity);
1019
+ this.#handler = this.#newHandler();
1020
+ this.#attempt = newConnectAttempt();
1021
+ this.#cmdTrack.reset(new NotConnectedError());
1022
+ this.#viewStore.reset();
1023
+ this.#clid = 0;
1024
+ this.#capabilities = unknownServerCapabilities();
1025
+ }
1026
+ async #resolveAddr(signal) {
1027
+ // Parsed, not tested for a colon: an IPv6 literal has colons and no port.
1028
+ const { host, port } = splitHostPort(this.#addr);
1029
+ const fallback = joinHostPort(host, port);
1030
+ // Every resolver, a custom one too, gets the same budget: past it the
1031
+ // address is used as given. A timer the tests can control, not
1032
+ // AbortSignal.timeout().
1033
+ const budget = new AbortController();
1034
+ const timer = setTimeout(() => budget.abort(new TeamspeakError("address resolution took too long")), RESOLVE_BUDGET_MS);
1035
+ const lookup = AbortSignal.any([signal, budget.signal]);
1036
+ try {
1037
+ // Raced against the signal: a resolver that ignores it must still not
1038
+ // hold up an attempt that has been given up.
1039
+ const resolved = await abortable(this.#resolver.resolve(this.#addr, { signal: lookup }), lookup);
1040
+ const first = resolved[0];
1041
+ return {
1042
+ target: first?.addr ?? fallback,
1043
+ source: first?.source ?? "",
1044
+ candidates: resolved,
1045
+ error: undefined,
1046
+ };
1047
+ }
1048
+ catch (error) {
1049
+ signal.throwIfAborted();
1050
+ const reason = budget.signal.aborted ? budget.signal.reason : error;
1051
+ this.#logger.warn("address resolution failed, falling back to direct", {
1052
+ error: String(reason),
1053
+ });
1054
+ return { target: fallback, source: "", candidates: [], error: reason };
1055
+ }
1056
+ finally {
1057
+ clearTimeout(timer);
1058
+ }
1059
+ }
1060
+ /**
1061
+ * A datagram from the server could not be handled. During the handshake that
1062
+ * is the connection failing, reported as a HandshakeError with the cause;
1063
+ * once connected, one bad packet is not worth the session, so it is dropped.
1064
+ */
1065
+ #handleReceiveError(error) {
1066
+ if (this.#status !== ClientStatus.Connected) {
1067
+ this.#failConnection(error instanceof HandshakeError
1068
+ ? error
1069
+ : new HandshakeError("the handshake failed on a malformed packet", { cause: error }));
1070
+ return;
1071
+ }
1072
+ this.#logger.debug("dropped a malformed packet", { error: String(error) });
1073
+ }
1074
+ #handlePacket(p) {
1075
+ const pType = p.typeFlagged & 0x0f;
1076
+ if (pType === 8 /* Init1 */) {
1077
+ if (this.#crypt.cryptoInitComplete)
1078
+ return;
1079
+ const response = processInit1(this.#crypt, p.data);
1080
+ if (response) {
1081
+ this.#handler.sendPacket(PacketType.Init1, response, 0);
1082
+ }
1083
+ return;
1084
+ }
1085
+ if ((pType === 0 /* Voice */ || pType === 1) /* VoiceWhisper */ && p.data.length > 5) {
1086
+ this.#handleVoicePacket(p.data);
1087
+ return;
1088
+ }
1089
+ if ((pType === 2 /* Command */ || pType === 3) /* CommandLow */ && p.data.length > 0) {
1090
+ this.#handleCommandLines(Buffer.from(p.data).toString("utf8"));
1091
+ }
1092
+ }
1093
+ /**
1094
+ * Parse an incoming S2C voice packet.
1095
+ * Format: [VId: u16] [CId: u16] [Codec: u8] [Data: var]
1096
+ */
1097
+ #handleVoicePacket(payload) {
1098
+ // Audio arrives fifty times a second per speaker; build no event nobody
1099
+ // will see.
1100
+ if (!this.#listeners.get("voiceData")?.length && this.#eventMiddlewares.length === 0)
1101
+ return;
1102
+ if (this.#queue.length > MAX_PENDING_DELIVERIES) {
1103
+ this.#dropVoice();
1104
+ return;
1105
+ }
1106
+ const view = new DataView(payload.buffer, payload.byteOffset, payload.byteLength);
1107
+ const clientId = view.getUint16(2, false);
1108
+ if (clientId === this.#clid)
1109
+ return;
1110
+ // Copied: the packet's buffer is reused once this returns, and a listener
1111
+ // runs later.
1112
+ const data = payload.slice(5);
1113
+ this.#emitServerEvent({
1114
+ name: "voiceData",
1115
+ payload: { clientID: asClientID(clientId), codec: payload[4], data },
1116
+ });
1117
+ }
1118
+ #handleCommandLines(s) {
1119
+ if (!s)
1120
+ return;
1121
+ const lines = s.split("\0").join("\n").split("\n");
1122
+ for (const line of lines) {
1123
+ const trimmed = line.replace(/\r$/, "");
1124
+ if (!trimmed)
1125
+ continue;
1126
+ for (const row of splitCommandRows(trimmed)) {
1127
+ this.#handleCommandStr(row);
1128
+ }
1129
+ }
1130
+ }
1131
+ #handleCommandStr(s) {
1132
+ const cmd = parseCommand(s);
1133
+ if (!cmd)
1134
+ return;
1135
+ // Before crypto init a packet needs only the public dummy key, so anyone
1136
+ // on the path could have sent it: nothing but the handshake's own steps,
1137
+ // and an error that may end the attempt, is taken then. A notification
1138
+ // would be a forged event, a row a forged answer.
1139
+ if (!this.#crypt.cryptoInitComplete && !PRE_INIT_COMMANDS.has(cmd.name)) {
1140
+ this.#logger.debug("dropped a command that arrived before crypto init", {
1141
+ command: cmd.name,
1142
+ });
1143
+ return;
1144
+ }
1145
+ if (incomingChannel.hasSubscribers)
1146
+ incomingChannel.publish({ command: cmd.name, client: this });
1147
+ // No name means a response row: the server sends data without echoing the
1148
+ // command, so the row belongs to whichever command is awaiting a result.
1149
+ if (cmd.name === "") {
1150
+ if (Object.keys(cmd.params).length > 0)
1151
+ this.#cmdTrack.buffer(cmd.params);
1152
+ return;
1153
+ }
1154
+ // `channellistfinished` ends the server's welcome sequence. It carries no
1155
+ // data of its own, so it is the only reliable marker: the guard used to
1156
+ // trip on our own notifycliententerview, which the server sends *in the
1157
+ // middle* of the sequence — the channellist rows after it were then
1158
+ // buffered as if they were the response to the caller's first command,
1159
+ // which is how a channellist could come back with duplicate and empty rows.
1160
+ if (cmd.name === "channellistfinished") {
1161
+ // The tree and the clients of the own channel are complete here, so the
1162
+ // view is too; the group lists and permissions the server sends next
1163
+ // are not part of it.
1164
+ const revision = this.#status === ClientStatus.Connected ? this.#viewStore.finish() : undefined;
1165
+ if (revision !== undefined)
1166
+ this.#emit("viewReady", { revision });
1167
+ this.#cmdTrack.signalWelcomeComplete();
1168
+ this.#markWelcomeDone();
1169
+ return;
1170
+ }
1171
+ if (cmd.name === "channellist") {
1172
+ const trace = this.#attempt.trace;
1173
+ if (trace !== undefined && !trace.welcomeDone) {
1174
+ trace.channels++;
1175
+ if (this.#status === ClientStatus.Connected) {
1176
+ this.#viewStore.collect(parseChannel(cmd.params));
1177
+ }
1178
+ }
1179
+ }
1180
+ // The server answers our goodbye by announcing that we left, not with an
1181
+ // error line; that announcement is what disconnect() waits for.
1182
+ if (cmd.name === "notifyclientleftview" &&
1183
+ this.#goodbyeAnswered !== undefined &&
1184
+ cmd.params["clid"] === String(this.#clid)) {
1185
+ this.#goodbyeAnswered();
1186
+ }
1187
+ if (cmd.name.startsWith("notify")) {
1188
+ // Some commands are answered by a notification instead of data rows;
1189
+ // it still goes on to be dispatched as an event, since the same
1190
+ // notification also arrives unprompted.
1191
+ if (this.#cmdTrack.expects(cmd.name)) {
1192
+ this.#cmdTrack.buffer(cmd.params, cmd.name);
1193
+ this.#cmdTrack.offerCorrelated(cmd.name, cmd.params);
1194
+ }
1195
+ if (cmd.name === "notifyconnectioninforequest")
1196
+ this.#answerConnectionInfoRequest();
1197
+ const result = handleNotification(cmd, this.#clid, this.#clients);
1198
+ this.#processNotificationResult(result);
1199
+ if (this.#listeners.get("notification")?.length || this.#eventMiddlewares.length > 0) {
1200
+ this.#emitServerEvent({
1201
+ name: "notification",
1202
+ payload: { name: cmd.name, params: { ...cmd.params } },
1203
+ });
1204
+ }
1205
+ return;
1206
+ }
1207
+ switch (cmd.name) {
1208
+ // The handshake's steps happen once: repeated after crypto init, they
1209
+ // could only re-key or re-announce a session that is already set up.
1210
+ case "clientinitiv":
1211
+ if (!this.#crypt.cryptoInitComplete)
1212
+ handleHandshakeInitIV(this.#internals, cmd.params);
1213
+ break;
1214
+ case "initivexpand2":
1215
+ if (!this.#crypt.cryptoInitComplete)
1216
+ handleHandshakeExpand2(this.#internals, cmd.params);
1217
+ break;
1218
+ case "initserver":
1219
+ // Only on a session with its keys: before crypto init a packet needs
1220
+ // nothing but the public dummy key, and an initserver taken then would
1221
+ // report a connection whose next command — clientinit with its password
1222
+ // hashes among them — goes out under that dummy key.
1223
+ if (this.#status === ClientStatus.Connecting && this.#crypt.cryptoInitComplete) {
1224
+ handleInitServer(this.#internals, cmd.params);
1225
+ }
1226
+ break;
1227
+ case "error":
1228
+ this.#handleError(cmd.params);
1229
+ break;
1230
+ default:
1231
+ // A named command the SDK does not model. Held under its own name, so
1232
+ // it answers a command only if that command declared it — not merely by
1233
+ // arriving while one is in flight.
1234
+ this.#cmdTrack.buffer(cmd.params, cmd.name);
1235
+ break;
1236
+ }
1237
+ }
1238
+ /**
1239
+ * Someone asked the server for this client's connection info, and it asks
1240
+ * us for the figures: answer with what this connection has measured. A
1241
+ * failure is only logged, as the request is not one a caller made.
1242
+ */
1243
+ #answerConnectionInfoRequest() {
1244
+ const line = buildSetConnectionInfo(this.#handler.trafficSnapshot(), this.#handler.roundTrip);
1245
+ this.#execNoWait(line, undefined, false).catch((error) => {
1246
+ this.#logger.debug("could not answer a connection info request", { error: String(error) });
1247
+ });
1248
+ }
1249
+ #handleError(params) {
1250
+ const { err, rc } = parseServerError(params);
1251
+ if (rc !== null) {
1252
+ this.#cmdTrack.resolve(rc, err);
1253
+ }
1254
+ else {
1255
+ // No return_code: server is acknowledging an unsolicited command
1256
+ // (welcome-sequence channellist, no-RC commands like clientupdate, etc.).
1257
+ // Discard any rows buffered since the last resolved command.
1258
+ this.#cmdTrack.discardBuffer();
1259
+ }
1260
+ // A server error before the welcome sequence finished is the connection
1261
+ // failure itself — a rejected password, or an invalid clientinit parameter.
1262
+ // Report it as the cause instead of leaving the caller with a bare timeout.
1263
+ if (err && this.#status !== ClientStatus.Connected) {
1264
+ this.#failConnection(err);
1265
+ return;
1266
+ }
1267
+ // Only a connected session gets here, so only after crypto init: an error
1268
+ // under the public dummy key can fail an attempt, never stop the client.
1269
+ if (err?.code === ErrorCode.ConnectFailedBanned) {
1270
+ this.#logger.warn("banned by the server, closing the connection");
1271
+ setImmediate(() => {
1272
+ this.disconnect().catch(() => { });
1273
+ });
1274
+ }
1275
+ }
1276
+ #processNotificationResult(result) {
1277
+ switch (result.kind) {
1278
+ case "clientEnter": {
1279
+ const info = result.info;
1280
+ // Identify self by the clid initserver already assigned rather than by
1281
+ // nickname: any other client whose nickname happens to be "<our
1282
+ // nick><digits>" — which is how the server renames a client whose
1283
+ // nickname was taken — would otherwise be mistaken for us, poisoning
1284
+ // every outgoing packet header with a foreign clientID until the server
1285
+ // drops the connection for command-resend timeout.
1286
+ //
1287
+ // This no longer marks the end of the welcome sequence: the server
1288
+ // sends more welcome data after it, and `channellistfinished` is the
1289
+ // real terminator.
1290
+ if (info.id === this.#clid) {
1291
+ // Carries the rename the server applied if our nickname was taken.
1292
+ this.#nickname = info.nickname;
1293
+ }
1294
+ else if (this.#clid === 0 && isAutoNicknameMatch(this.#nickname, info.nickname)) {
1295
+ // initserver carried no aclid, so the welcome enter is the only way
1296
+ // to learn our clid. Gated on clid still being unknown so a later
1297
+ // lookalike can never hijack an established session.
1298
+ this.#clid = info.id;
1299
+ this.#handler.setClientID(info.id);
1300
+ this.#nickname = info.nickname;
1301
+ }
1302
+ this.#emitServerEvent({ name: "clientEnter", payload: info });
1303
+ break;
1304
+ }
1305
+ case "clientLeave": {
1306
+ const event = result.event;
1307
+ this.#emitServerEvent({ name: "clientLeave", payload: event });
1308
+ if (!result.isSelf)
1309
+ break;
1310
+ if (event.reason === Reason.KickServer || event.reason === Reason.KickServerBan) {
1311
+ this.#emit("kicked", kickedEvent(event));
1312
+ }
1313
+ this.#removedByServer(new RemovedError(event.reason, event.reasonMessage, event.invokerName));
1314
+ break;
1315
+ }
1316
+ case "clientMoved":
1317
+ this.#emitServerEvent({ name: "clientMoved", payload: result.event });
1318
+ // A channel kick moves the client to the default channel; it arrives
1319
+ // as a move, not as leaving view, which is where it used to be looked
1320
+ // for.
1321
+ if (result.event.clientID === this.#clid && result.event.reason === Reason.KickChannel) {
1322
+ this.#emit("kicked", kickedEvent(result.event));
1323
+ }
1324
+ break;
1325
+ case "textMessage":
1326
+ this.#emitServerEvent({ name: "textMessage", payload: result.message });
1327
+ break;
1328
+ case "clientUpdated":
1329
+ // The server may rename a client, this one included.
1330
+ if (result.event.clientID === this.#clid && "nickname" in result.event.changes) {
1331
+ this.#nickname = result.event.changes.nickname ?? this.#nickname;
1332
+ }
1333
+ this.#emitServerEvent({ name: "clientUpdated", payload: result.event });
1334
+ break;
1335
+ case "clientServerGroupAdded":
1336
+ this.#emitServerEvent({ name: "clientServerGroupAdded", payload: result.event });
1337
+ break;
1338
+ case "clientServerGroupRemoved":
1339
+ this.#emitServerEvent({ name: "clientServerGroupRemoved", payload: result.event });
1340
+ break;
1341
+ case "clientChannelGroupChanged":
1342
+ this.#emitServerEvent({ name: "clientChannelGroupChanged", payload: result.event });
1343
+ break;
1344
+ case "channelCreated":
1345
+ this.#viewStore.applyCreated(result.event);
1346
+ this.#emitServerEvent({ name: "channelCreated", payload: result.event });
1347
+ break;
1348
+ case "channelEdited":
1349
+ this.#viewStore.applyEdited(result.event);
1350
+ this.#emitServerEvent({ name: "channelEdited", payload: result.event });
1351
+ break;
1352
+ case "channelMoved":
1353
+ this.#viewStore.applyMoved(result.event);
1354
+ this.#emitServerEvent({ name: "channelMoved", payload: result.event });
1355
+ break;
1356
+ case "channelDeleted":
1357
+ this.#viewStore.applyDeleted(result.event);
1358
+ this.#emitServerEvent({ name: "channelDeleted", payload: result.event });
1359
+ break;
1360
+ case "channelDescriptionChanged":
1361
+ this.#emitServerEvent({ name: "channelDescriptionChanged", payload: result.event });
1362
+ break;
1363
+ case "channelPasswordChanged":
1364
+ this.#emitServerEvent({ name: "channelPasswordChanged", payload: result.event });
1365
+ break;
1366
+ case "serverEdited":
1367
+ this.#emitServerEvent({ name: "serverEdited", payload: result.event });
1368
+ break;
1369
+ case "channelSubscribed":
1370
+ this.#emitServerEvent({ name: "channelSubscribed", payload: result.event });
1371
+ break;
1372
+ case "channelUnsubscribed":
1373
+ // The server does not say the clients of the channel left.
1374
+ this.#viewStore.dropChannel(result.event.channelID);
1375
+ this.#emitServerEvent({ name: "channelUnsubscribed", payload: result.event });
1376
+ break;
1377
+ case "pluginCommand":
1378
+ this.#emitServerEvent({ name: "pluginCommand", payload: result.event });
1379
+ break;
1380
+ case "clientChatComposing":
1381
+ this.#emitServerEvent({ name: "clientChatComposing", payload: result.event });
1382
+ break;
1383
+ case "poked":
1384
+ this.#emitServerEvent({ name: "poked", payload: result.event });
1385
+ break;
1386
+ case "unknown":
1387
+ break;
1388
+ }
1389
+ }
1390
+ /**
1391
+ * End a session the server has ended. The server stops answering a client it
1392
+ * has removed, so waiting for the transport to notice reported the end a
1393
+ * minute late, without its cause, and with the client claiming to be
1394
+ * connected meanwhile.
1395
+ */
1396
+ #removedByServer(cause) {
1397
+ this.#handleConnectionClosed(cause);
1398
+ this.#handler.close();
1399
+ }
1400
+ /**
1401
+ * Count a dropped voice delivery, and report the drops now and then: at most
1402
+ * once every ten seconds, with how many were dropped since the last report.
1403
+ */
1404
+ #dropVoice() {
1405
+ this.#voiceDropped++;
1406
+ const now = performance.now();
1407
+ if (now - this.#voiceDropWarnedAt < VOICE_DROP_WARN_INTERVAL_MS)
1408
+ return;
1409
+ this.#voiceDropWarnedAt = now;
1410
+ // The count since the last warning, so each one says how bad it is now.
1411
+ this.#logger.warn("voice dropped: listeners are not keeping up", {
1412
+ dropped: this.#voiceDropped,
1413
+ });
1414
+ this.#voiceDropped = 0;
1415
+ }
1416
+ /** Queue a server event, through the event middleware. */
1417
+ #emitServerEvent(event) {
1418
+ this.#enqueue(() => this.#eventChain(event));
1419
+ }
1420
+ /**
1421
+ * Queue `disconnected`; the promise settles once it has been delivered, and
1422
+ * a connect() waits for it until then.
1423
+ */
1424
+ #announceDisconnected(cause, willReconnect) {
1425
+ const delivered = new Promise((resolve) => this.#enqueue(() => {
1426
+ try {
1427
+ this.#deliver("disconnected", { error: cause, willReconnect });
1428
+ }
1429
+ finally {
1430
+ resolve();
1431
+ }
1432
+ }));
1433
+ this.#disconnectedDelivered = delivered;
1434
+ void delivered.then(() => {
1435
+ if (this.#disconnectedDelivered === delivered)
1436
+ this.#disconnectedDelivered = undefined;
1437
+ });
1438
+ return delivered;
1439
+ }
1440
+ /** Queue a lifecycle event, which middleware does not see. */
1441
+ #emit(event, payload) {
1442
+ this.#enqueue(() => this.#deliver(event, payload));
1443
+ }
1444
+ #deliver(event, payload) {
1445
+ for (const subscription of this.#listeners.get(event) ?? []) {
1446
+ // Checked at delivery: a listener removed while its event was queued
1447
+ // does not get it.
1448
+ if (subscription.removed)
1449
+ continue;
1450
+ try {
1451
+ subscription.listener(payload);
1452
+ }
1453
+ catch (error) {
1454
+ // Rethrown on its own, as an uncaught exception, so it surfaces as one
1455
+ // from an EventEmitter listener would, and the listeners after it
1456
+ // still get the event.
1457
+ queueMicrotask(() => {
1458
+ throw error;
1459
+ });
1460
+ }
1461
+ }
1462
+ }
1463
+ /**
1464
+ * Run deliveries one at a time, in order, once the packet being handled is
1465
+ * done with. A listener that throws does so as an uncaught exception, as it
1466
+ * would from an EventEmitter, and the listeners and deliveries behind it
1467
+ * still run.
1468
+ */
1469
+ #enqueue(delivery) {
1470
+ this.#queue.push(delivery);
1471
+ if (this.#draining)
1472
+ return;
1473
+ this.#draining = true;
1474
+ setImmediate(this.#drain);
1475
+ }
1476
+ #drain = () => {
1477
+ try {
1478
+ while (this.#queue.length > 0)
1479
+ this.#queue.shift()();
1480
+ }
1481
+ finally {
1482
+ if (this.#queue.length > 0)
1483
+ setImmediate(this.#drain);
1484
+ else
1485
+ this.#draining = false;
1486
+ }
1487
+ };
1488
+ #handleConnectionClosed(err) {
1489
+ if (this.#status === ClientStatus.Disconnected)
1490
+ return;
1491
+ // Nothing in flight can be answered any more; failing it now, with the
1492
+ // reason, beats every caller timing out one by one.
1493
+ this.#cmdTrack.reset(new NotConnectedError(err ? { cause: err } : undefined));
1494
+ if (this.#status !== ClientStatus.Connected) {
1495
+ // The socket closed before the handshake finished, so this is a failed
1496
+ // connection attempt rather than a disconnect of a live session.
1497
+ this.#failConnection(err ?? new TeamspeakError("connection closed before the TeamSpeak handshake completed"));
1498
+ return;
1499
+ }
1500
+ this.#status = ClientStatus.Disconnected;
1501
+ // Never undefined for a drop: undefined means disconnect() only.
1502
+ const cause = err ?? new TeamspeakError("connection closed");
1503
+ this.#lastDrop = cause;
1504
+ this.#endSession(cause);
1505
+ // Announced first, then retried: a caller watching `disconnected` should
1506
+ // hear about the drop even when a reconnect is already on its way, because
1507
+ // a silent recovery hides an outage.
1508
+ void this.#announceDisconnected(cause, this.#willReconnect(cause, this.#loopAttempt + 1));
1509
+ this.#scheduleReconnect(cause).catch((error) => {
1510
+ this.#logger.error("reconnecting failed", { error: String(error) });
1511
+ });
1512
+ }
1513
+ /**
1514
+ * Whether the reconnect loop will make attempt number `attempt` after `err`.
1515
+ * `disconnected` reports it, and the loop acts on the same answer: the
1516
+ * caller's `shouldRetry` is asked once per failure, not once to announce it
1517
+ * and again to act on it.
1518
+ */
1519
+ #willReconnect(err, attempt) {
1520
+ const options = this.#reconnectOptions;
1521
+ if (options === undefined || this.#stopped)
1522
+ return false;
1523
+ const { maxAttempts } = resolveBackoffPolicy(options);
1524
+ if (maxAttempts !== 0 && attempt > maxAttempts)
1525
+ return false;
1526
+ const asked = this.#retryAnswer;
1527
+ if (asked?.err === err && asked.attempt === attempt)
1528
+ return asked.retry;
1529
+ let retry;
1530
+ let threw = false;
1531
+ try {
1532
+ retry = (options.shouldRetry ?? defaultShouldRetry)(err, attempt);
1533
+ }
1534
+ catch (error) {
1535
+ this.#logger.error("shouldRetry threw; not reconnecting", { error: String(error) });
1536
+ retry = false;
1537
+ threw = true;
1538
+ }
1539
+ this.#retryAnswer = { err, attempt, retry, threw };
1540
+ return retry;
1541
+ }
1542
+ /**
1543
+ * Retry a dropped connection according to the reconnect policy.
1544
+ *
1545
+ * Never runs after an explicit disconnect, and never more than one at a time:
1546
+ * two failures in quick succession would otherwise start competing loops, and
1547
+ * a loop shorter than the server's antiflood window bans the IP rather than
1548
+ * reconnecting it. A drop while the loop runs is the loop's to retry: it
1549
+ * ends only once its attempt is still connected when it resumes.
1550
+ */
1551
+ async #scheduleReconnect(err) {
1552
+ const options = this.#reconnectOptions;
1553
+ if (options === undefined || this.#stopped || this.#reconnecting)
1554
+ return;
1555
+ const policy = resolveBackoffPolicy(options);
1556
+ this.#reconnecting = true;
1557
+ const stop = new AbortController();
1558
+ this.#stopReconnecting = stop;
1559
+ // The attempts the loop has made, and why it stopped, for `reconnectStopped`.
1560
+ let made = 0;
1561
+ let stoppedBy;
1562
+ try {
1563
+ for (let attempt = 1; policy.maxAttempts === 0 || attempt <= policy.maxAttempts; attempt++) {
1564
+ if (this.#stopped) {
1565
+ stoppedBy = { cause: undefined };
1566
+ return;
1567
+ }
1568
+ if (!this.#willReconnect(err, attempt)) {
1569
+ if (!this.#retryAnswer?.threw) {
1570
+ this.#logger.info("not retrying", { attempt, reason: err.message });
1571
+ }
1572
+ stoppedBy = { cause: err };
1573
+ return;
1574
+ }
1575
+ const delay = backoffDelay(policy, attempt);
1576
+ this.#logger.info("reconnecting", { attempt, delayMs: delay });
1577
+ this.#callHook(this.#trace?.reconnectScheduled, () => ({
1578
+ attempt,
1579
+ delayMs: delay,
1580
+ cause: err,
1581
+ }));
1582
+ // A timer that keeps the process alive: an application that does
1583
+ // nothing but stay connected would otherwise exit during the wait.
1584
+ await sleep(delay, stop.signal);
1585
+ if (this.#stopped) {
1586
+ stoppedBy = { cause: undefined };
1587
+ return;
1588
+ }
1589
+ // Connected, or connecting, by hand during the wait: nothing is left to
1590
+ // retry, and an attempt now would only fail with AlreadyConnectedError.
1591
+ if (this.#status !== ClientStatus.Disconnected) {
1592
+ this.#logger.info("reconnect loop ended: connected by other means", { attempt });
1593
+ return;
1594
+ }
1595
+ try {
1596
+ made++;
1597
+ this.#loopAttempt = attempt;
1598
+ await this.#connect({ signal: AbortSignal.timeout(RECONNECT_ATTEMPT_TIMEOUT_MS) }, attempt, true);
1599
+ // Read afresh: the status may have changed while connect() resolved.
1600
+ if (this.status === ClientStatus.Connected) {
1601
+ this.#logger.info("reconnected", { attempt });
1602
+ return;
1603
+ }
1604
+ // The new session dropped before this loop resumed — initserver and
1605
+ // the server's goodbye in one datagram, say. That drop found this
1606
+ // loop running and left the retrying to it.
1607
+ err = this.#lastDrop ?? new TeamspeakError("connection closed");
1608
+ }
1609
+ catch (retryErr) {
1610
+ if (retryErr instanceof AlreadyConnectedError)
1611
+ return;
1612
+ err =
1613
+ retryErr instanceof Error
1614
+ ? retryErr
1615
+ : new TeamspeakError(String(retryErr), { cause: retryErr });
1616
+ }
1617
+ }
1618
+ this.#logger.warn("reconnect attempts exhausted", { attempts: policy.maxAttempts });
1619
+ stoppedBy = { cause: err };
1620
+ }
1621
+ finally {
1622
+ if (stoppedBy !== undefined) {
1623
+ const { cause } = stoppedBy;
1624
+ this.#callHook(this.#trace?.reconnectStopped, () => ({ attempts: made, cause }));
1625
+ }
1626
+ this.#reconnecting = false;
1627
+ this.#loopAttempt = 0;
1628
+ if (this.#stopReconnecting === stop)
1629
+ this.#stopReconnecting = undefined;
1630
+ }
1631
+ }
1632
+ /**
1633
+ * A connection attempt has failed for good: settle it with the cause, and
1634
+ * tear the connection down. Does nothing once the handshake has completed,
1635
+ * so it cannot end a live session.
1636
+ *
1637
+ * Without this, a handshake the server rejects (a wrong password, say, which
1638
+ * the server answers with an error instead of closing the socket) surfaces
1639
+ * only as the caller's own timeout, and the client stays in `Connecting`
1640
+ * forever without ever emitting `disconnected`.
1641
+ */
1642
+ #failConnection(error) {
1643
+ if (this.#status === ClientStatus.Connected)
1644
+ return;
1645
+ this.#attempt.reject(error);
1646
+ this.#viewStore.end(error);
1647
+ if (this.#status === ClientStatus.Disconnected)
1648
+ return;
1649
+ this.#status = ClientStatus.Disconnected;
1650
+ this.#handler.close();
1651
+ const cause = error instanceof Error ? error : new TeamspeakError(String(error), { cause: error });
1652
+ // Only an attempt of the reconnect loop can be followed by another; a
1653
+ // connect() that fails is the caller's to retry.
1654
+ void this.#announceDisconnected(cause, this.#reconnecting && this.#willReconnect(cause, this.#loopAttempt + 1));
1655
+ }
1656
+ }
1657
+ function kickedEvent(event) {
1658
+ return {
1659
+ reason: event.reason,
1660
+ reasonMessage: event.reasonMessage,
1661
+ invokerName: event.invokerName,
1662
+ invokerUID: event.invokerUID,
1663
+ invokerID: event.invokerID,
1664
+ };
1665
+ }
1666
+ //# sourceMappingURL=client.js.map