@honeybbq/teamspeak 0.0.1 → 1.0.0-beta.7

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 (393) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +726 -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/voice.d.ts +24 -0
  12. package/dist/client/api/voice.d.ts.map +1 -0
  13. package/dist/client/api/voice.js +33 -0
  14. package/dist/client/api/voice.js.map +1 -0
  15. package/dist/client/capabilities.d.ts +68 -0
  16. package/dist/client/capabilities.d.ts.map +1 -0
  17. package/dist/client/capabilities.js +59 -0
  18. package/dist/client/capabilities.js.map +1 -0
  19. package/dist/client/client.d.ts +196 -0
  20. package/dist/client/client.d.ts.map +1 -0
  21. package/dist/client/client.js +1523 -0
  22. package/dist/client/client.js.map +1 -0
  23. package/dist/client/commands.d.ts +66 -0
  24. package/dist/client/commands.d.ts.map +1 -0
  25. package/dist/client/commands.js +162 -0
  26. package/dist/client/commands.js.map +1 -0
  27. package/dist/client/crypto/crypt-ops.d.ts +7 -0
  28. package/dist/client/crypto/crypt-ops.d.ts.map +1 -0
  29. package/dist/client/crypto/crypt-ops.js +2 -0
  30. package/dist/client/crypto/crypt-ops.js.map +1 -0
  31. package/dist/client/crypto/crypt.d.ts +37 -0
  32. package/dist/client/crypto/crypt.d.ts.map +1 -0
  33. package/dist/client/crypto/crypt.js +203 -0
  34. package/dist/client/crypto/crypt.js.map +1 -0
  35. package/dist/client/crypto/eax.d.ts +12 -0
  36. package/dist/client/crypto/eax.d.ts.map +1 -0
  37. package/dist/client/crypto/eax.js +118 -0
  38. package/dist/client/crypto/eax.js.map +1 -0
  39. package/dist/client/crypto/identity.d.ts +97 -0
  40. package/dist/client/crypto/identity.d.ts.map +1 -0
  41. package/dist/client/crypto/identity.js +412 -0
  42. package/dist/client/crypto/identity.js.map +1 -0
  43. package/dist/client/crypto/index.d.ts +7 -0
  44. package/dist/client/crypto/index.d.ts.map +1 -0
  45. package/dist/client/crypto/index.js +5 -0
  46. package/dist/client/crypto/index.js.map +1 -0
  47. package/dist/client/crypto/primitives.d.ts +43 -0
  48. package/dist/client/crypto/primitives.d.ts.map +1 -0
  49. package/dist/client/crypto/primitives.js +116 -0
  50. package/dist/client/crypto/primitives.js.map +1 -0
  51. package/dist/client/diagnostics.d.ts +45 -0
  52. package/dist/client/diagnostics.d.ts.map +1 -0
  53. package/dist/client/diagnostics.js +30 -0
  54. package/dist/client/diagnostics.js.map +1 -0
  55. package/dist/client/discovery/index.d.ts +2 -0
  56. package/dist/client/discovery/index.d.ts.map +1 -0
  57. package/dist/client/discovery/index.js +2 -0
  58. package/dist/client/discovery/index.js.map +1 -0
  59. package/dist/client/discovery/resolver.d.ts +26 -0
  60. package/dist/client/discovery/resolver.d.ts.map +1 -0
  61. package/dist/client/discovery/resolver.js +275 -0
  62. package/dist/client/discovery/resolver.js.map +1 -0
  63. package/dist/client/errors.d.ts +82 -0
  64. package/dist/client/errors.d.ts.map +1 -0
  65. package/dist/client/errors.js +201 -0
  66. package/dist/client/errors.js.map +1 -0
  67. package/dist/client/events.d.ts +3 -0
  68. package/dist/client/events.d.ts.map +1 -0
  69. package/dist/client/events.js +8 -0
  70. package/dist/client/events.js.map +1 -0
  71. package/dist/client/handshake/crypt-handshake.d.ts +17 -0
  72. package/dist/client/handshake/crypt-handshake.d.ts.map +1 -0
  73. package/dist/client/handshake/crypt-handshake.js +128 -0
  74. package/dist/client/handshake/crypt-handshake.js.map +1 -0
  75. package/dist/client/handshake/crypt-init2.d.ts +7 -0
  76. package/dist/client/handshake/crypt-init2.d.ts.map +1 -0
  77. package/dist/client/handshake/crypt-init2.js +37 -0
  78. package/dist/client/handshake/crypt-init2.js.map +1 -0
  79. package/dist/client/handshake/index.d.ts +4 -0
  80. package/dist/client/handshake/index.d.ts.map +1 -0
  81. package/dist/client/handshake/index.js +4 -0
  82. package/dist/client/handshake/index.js.map +1 -0
  83. package/dist/client/handshake/license.d.ts +35 -0
  84. package/dist/client/handshake/license.d.ts.map +1 -0
  85. package/dist/client/handshake/license.js +169 -0
  86. package/dist/client/handshake/license.js.map +1 -0
  87. package/dist/client/handshake.d.ts +27 -0
  88. package/dist/client/handshake.d.ts.map +1 -0
  89. package/dist/client/handshake.js +126 -0
  90. package/dist/client/handshake.js.map +1 -0
  91. package/dist/client/helpers.d.ts +6 -0
  92. package/dist/client/helpers.d.ts.map +1 -0
  93. package/dist/client/helpers.js +13 -0
  94. package/dist/client/helpers.js.map +1 -0
  95. package/dist/client/hooks.d.ts +7 -0
  96. package/dist/client/hooks.d.ts.map +1 -0
  97. package/dist/client/hooks.js +28 -0
  98. package/dist/client/hooks.js.map +1 -0
  99. package/dist/client/index.d.ts +26 -0
  100. package/dist/client/index.d.ts.map +1 -0
  101. package/dist/client/index.js +28 -0
  102. package/dist/client/index.js.map +1 -0
  103. package/dist/client/reconnect.d.ts +12 -0
  104. package/dist/client/reconnect.d.ts.map +1 -0
  105. package/dist/client/reconnect.js +44 -0
  106. package/dist/client/reconnect.js.map +1 -0
  107. package/dist/client/throttle.d.ts +20 -0
  108. package/dist/client/throttle.d.ts.map +1 -0
  109. package/dist/client/throttle.js +83 -0
  110. package/dist/client/throttle.js.map +1 -0
  111. package/dist/client/trace.d.ts +223 -0
  112. package/dist/client/trace.d.ts.map +1 -0
  113. package/dist/client/trace.js +13 -0
  114. package/dist/client/trace.js.map +1 -0
  115. package/dist/client/transfer.d.ts +111 -0
  116. package/dist/client/transfer.d.ts.map +1 -0
  117. package/dist/client/transfer.js +243 -0
  118. package/dist/client/transfer.js.map +1 -0
  119. package/dist/client/transport/generation-window.d.ts +30 -0
  120. package/dist/client/transport/generation-window.d.ts.map +1 -0
  121. package/dist/client/transport/generation-window.js +97 -0
  122. package/dist/client/transport/generation-window.js.map +1 -0
  123. package/dist/client/transport/handler.d.ts +61 -0
  124. package/dist/client/transport/handler.d.ts.map +1 -0
  125. package/dist/client/transport/handler.js +793 -0
  126. package/dist/client/transport/handler.js.map +1 -0
  127. package/dist/client/transport/index.d.ts +6 -0
  128. package/dist/client/transport/index.d.ts.map +1 -0
  129. package/dist/client/transport/index.js +5 -0
  130. package/dist/client/transport/index.js.map +1 -0
  131. package/dist/client/transport/packet.d.ts +36 -0
  132. package/dist/client/transport/packet.d.ts.map +1 -0
  133. package/dist/client/transport/packet.js +55 -0
  134. package/dist/client/transport/packet.js.map +1 -0
  135. package/dist/client/transport/quicklz.d.ts +24 -0
  136. package/dist/client/transport/quicklz.d.ts.map +1 -0
  137. package/dist/client/transport/quicklz.js +179 -0
  138. package/dist/client/transport/quicklz.js.map +1 -0
  139. package/dist/client/types.d.ts +226 -0
  140. package/dist/client/types.d.ts.map +1 -0
  141. package/dist/client/types.js +39 -0
  142. package/dist/client/types.js.map +1 -0
  143. package/dist/core/api/channels.d.ts +182 -0
  144. package/dist/core/api/channels.d.ts.map +1 -0
  145. package/dist/core/api/channels.js +155 -0
  146. package/dist/core/api/channels.js.map +1 -0
  147. package/dist/core/api/clients.d.ts +304 -0
  148. package/dist/core/api/clients.d.ts.map +1 -0
  149. package/dist/core/api/clients.js +260 -0
  150. package/dist/core/api/clients.js.map +1 -0
  151. package/dist/core/api/command.d.ts +305 -0
  152. package/dist/core/api/command.d.ts.map +1 -0
  153. package/dist/core/api/command.js +363 -0
  154. package/dist/core/api/command.js.map +1 -0
  155. package/dist/core/api/files.d.ts +153 -0
  156. package/dist/core/api/files.d.ts.map +1 -0
  157. package/dist/core/api/files.js +181 -0
  158. package/dist/core/api/files.js.map +1 -0
  159. package/dist/core/api/groups.d.ts +248 -0
  160. package/dist/core/api/groups.d.ts.map +1 -0
  161. package/dist/core/api/groups.js +271 -0
  162. package/dist/core/api/groups.js.map +1 -0
  163. package/dist/core/api/moderation.d.ts +205 -0
  164. package/dist/core/api/moderation.d.ts.map +1 -0
  165. package/dist/core/api/moderation.js +209 -0
  166. package/dist/core/api/moderation.js.map +1 -0
  167. package/dist/core/api/parse.d.ts +16 -0
  168. package/dist/core/api/parse.d.ts.map +1 -0
  169. package/dist/core/api/parse.js +40 -0
  170. package/dist/core/api/parse.js.map +1 -0
  171. package/dist/core/api/server.d.ts +310 -0
  172. package/dist/core/api/server.d.ts.map +1 -0
  173. package/dist/core/api/server.js +290 -0
  174. package/dist/core/api/server.js.map +1 -0
  175. package/dist/core/command/command.d.ts +20 -0
  176. package/dist/core/command/command.d.ts.map +1 -0
  177. package/dist/core/command/command.js +112 -0
  178. package/dist/core/command/command.js.map +1 -0
  179. package/dist/core/command/ids.d.ts +125 -0
  180. package/dist/core/command/ids.d.ts.map +1 -0
  181. package/dist/core/command/ids.js +85 -0
  182. package/dist/core/command/ids.js.map +1 -0
  183. package/dist/core/command/parser.d.ts +17 -0
  184. package/dist/core/command/parser.d.ts.map +1 -0
  185. package/dist/core/command/parser.js +65 -0
  186. package/dist/core/command/parser.js.map +1 -0
  187. package/dist/core/command/request.d.ts +61 -0
  188. package/dist/core/command/request.d.ts.map +1 -0
  189. package/dist/core/command/request.js +64 -0
  190. package/dist/core/command/request.js.map +1 -0
  191. package/dist/core/command/responses.d.ts +59 -0
  192. package/dist/core/command/responses.d.ts.map +1 -0
  193. package/dist/core/command/responses.js +74 -0
  194. package/dist/core/command/responses.js.map +1 -0
  195. package/dist/core/command-name.d.ts +333 -0
  196. package/dist/core/command-name.d.ts.map +1 -0
  197. package/dist/core/command-name.js +331 -0
  198. package/dist/core/command-name.js.map +1 -0
  199. package/dist/core/enums.d.ts +146 -0
  200. package/dist/core/enums.d.ts.map +1 -0
  201. package/dist/core/enums.js +132 -0
  202. package/dist/core/enums.js.map +1 -0
  203. package/dist/core/errorcode.d.ts +413 -0
  204. package/dist/core/errorcode.d.ts.map +1 -0
  205. package/dist/core/errorcode.js +410 -0
  206. package/dist/core/errorcode.js.map +1 -0
  207. package/dist/core/errors.d.ts +111 -0
  208. package/dist/core/errors.d.ts.map +1 -0
  209. package/dist/core/errors.js +233 -0
  210. package/dist/core/errors.js.map +1 -0
  211. package/dist/core/events.d.ts +207 -0
  212. package/dist/core/events.d.ts.map +1 -0
  213. package/dist/core/events.js +6 -0
  214. package/dist/core/events.js.map +1 -0
  215. package/dist/core/helpers.d.ts +42 -0
  216. package/dist/core/helpers.d.ts.map +1 -0
  217. package/dist/core/helpers.js +156 -0
  218. package/dist/core/helpers.js.map +1 -0
  219. package/dist/core/index.d.ts +35 -0
  220. package/dist/core/index.d.ts.map +1 -0
  221. package/dist/core/index.js +34 -0
  222. package/dist/core/index.js.map +1 -0
  223. package/dist/core/internal.d.ts +23 -0
  224. package/dist/core/internal.d.ts.map +1 -0
  225. package/dist/core/internal.js +20 -0
  226. package/dist/core/internal.js.map +1 -0
  227. package/dist/core/middleware.d.ts +14 -0
  228. package/dist/core/middleware.d.ts.map +1 -0
  229. package/dist/core/middleware.js +18 -0
  230. package/dist/core/middleware.js.map +1 -0
  231. package/dist/core/notification-name.d.ts +154 -0
  232. package/dist/core/notification-name.d.ts.map +1 -0
  233. package/dist/core/notification-name.js +152 -0
  234. package/dist/core/notification-name.js.map +1 -0
  235. package/dist/core/notifications.d.ts +43 -0
  236. package/dist/core/notifications.d.ts.map +1 -0
  237. package/dist/core/notifications.js +116 -0
  238. package/dist/core/notifications.js.map +1 -0
  239. package/dist/core/permission-name.d.ts +533 -0
  240. package/dist/core/permission-name.d.ts.map +1 -0
  241. package/dist/core/permission-name.js +531 -0
  242. package/dist/core/permission-name.js.map +1 -0
  243. package/dist/core/permission.d.ts +31 -0
  244. package/dist/core/permission.d.ts.map +1 -0
  245. package/dist/core/permission.js +20 -0
  246. package/dist/core/permission.js.map +1 -0
  247. package/dist/core/property-name.d.ts +403 -0
  248. package/dist/core/property-name.d.ts.map +1 -0
  249. package/dist/core/property-name.js +405 -0
  250. package/dist/core/property-name.js.map +1 -0
  251. package/dist/core/reason.d.ts +39 -0
  252. package/dist/core/reason.d.ts.map +1 -0
  253. package/dist/core/reason.js +36 -0
  254. package/dist/core/reason.js.map +1 -0
  255. package/dist/core/reconnect.d.ts +59 -0
  256. package/dist/core/reconnect.d.ts.map +1 -0
  257. package/dist/core/reconnect.js +55 -0
  258. package/dist/core/reconnect.js.map +1 -0
  259. package/dist/core/types.d.ts +110 -0
  260. package/dist/core/types.d.ts.map +1 -0
  261. package/dist/core/types.js +27 -0
  262. package/dist/core/types.js.map +1 -0
  263. package/dist/query/client.d.ts +194 -0
  264. package/dist/query/client.d.ts.map +1 -0
  265. package/dist/query/client.js +967 -0
  266. package/dist/query/client.js.map +1 -0
  267. package/dist/query/errors.d.ts +61 -0
  268. package/dist/query/errors.d.ts.map +1 -0
  269. package/dist/query/errors.js +100 -0
  270. package/dist/query/errors.js.map +1 -0
  271. package/dist/query/index.d.ts +36 -0
  272. package/dist/query/index.d.ts.map +1 -0
  273. package/dist/query/index.js +34 -0
  274. package/dist/query/index.js.map +1 -0
  275. package/dist/query/knownhosts.d.ts +61 -0
  276. package/dist/query/knownhosts.d.ts.map +1 -0
  277. package/dist/query/knownhosts.js +223 -0
  278. package/dist/query/knownhosts.js.map +1 -0
  279. package/dist/query/lines.d.ts +14 -0
  280. package/dist/query/lines.d.ts.map +1 -0
  281. package/dist/query/lines.js +29 -0
  282. package/dist/query/lines.js.map +1 -0
  283. package/dist/query/options.d.ts +187 -0
  284. package/dist/query/options.d.ts.map +1 -0
  285. package/dist/query/options.js +213 -0
  286. package/dist/query/options.js.map +1 -0
  287. package/dist/query/reconnect.d.ts +29 -0
  288. package/dist/query/reconnect.d.ts.map +1 -0
  289. package/dist/query/reconnect.js +87 -0
  290. package/dist/query/reconnect.js.map +1 -0
  291. package/dist/query/ssh.d.ts +345 -0
  292. package/dist/query/ssh.d.ts.map +1 -0
  293. package/dist/query/ssh.js +241 -0
  294. package/dist/query/ssh.js.map +1 -0
  295. package/dist/query/stream.d.ts +38 -0
  296. package/dist/query/stream.d.ts.map +1 -0
  297. package/dist/query/stream.js +70 -0
  298. package/dist/query/stream.js.map +1 -0
  299. package/dist/query/tcp.d.ts +38 -0
  300. package/dist/query/tcp.d.ts.map +1 -0
  301. package/dist/query/tcp.js +53 -0
  302. package/dist/query/tcp.js.map +1 -0
  303. package/dist/query/transport.d.ts +36 -0
  304. package/dist/query/transport.d.ts.map +1 -0
  305. package/dist/query/transport.js +2 -0
  306. package/dist/query/transport.js.map +1 -0
  307. package/dist/query/web.d.ts +54 -0
  308. package/dist/query/web.d.ts.map +1 -0
  309. package/dist/query/web.js +146 -0
  310. package/dist/query/web.js.map +1 -0
  311. package/dist/versions/index.d.ts +3094 -0
  312. package/dist/versions/index.d.ts.map +1 -0
  313. package/dist/versions/index.js +9237 -0
  314. package/dist/versions/index.js.map +1 -0
  315. package/package.json +118 -4
  316. package/src/client/abort.ts +24 -0
  317. package/src/client/address.ts +74 -0
  318. package/src/client/api/voice.ts +61 -0
  319. package/src/client/capabilities.ts +106 -0
  320. package/src/client/client.ts +1809 -0
  321. package/src/client/commands.ts +193 -0
  322. package/src/client/crypto/crypt-ops.ts +6 -0
  323. package/src/client/crypto/crypt.ts +275 -0
  324. package/src/client/crypto/eax.ts +152 -0
  325. package/src/client/crypto/identity.ts +484 -0
  326. package/src/client/crypto/index.ts +21 -0
  327. package/src/client/crypto/primitives.ts +141 -0
  328. package/src/client/diagnostics.ts +58 -0
  329. package/src/client/discovery/index.ts +1 -0
  330. package/src/client/discovery/resolver.ts +325 -0
  331. package/src/client/errors.ts +217 -0
  332. package/src/client/events.ts +12 -0
  333. package/src/client/handshake/crypt-handshake.ts +157 -0
  334. package/src/client/handshake/crypt-init2.ts +48 -0
  335. package/src/client/handshake/index.ts +3 -0
  336. package/src/client/handshake/license.ts +208 -0
  337. package/src/client/handshake.ts +150 -0
  338. package/src/client/helpers.ts +10 -0
  339. package/src/client/hooks.ts +30 -0
  340. package/src/client/index.ts +93 -0
  341. package/src/client/reconnect.ts +43 -0
  342. package/src/client/throttle.ts +92 -0
  343. package/src/client/trace.ts +237 -0
  344. package/src/client/transfer.ts +436 -0
  345. package/src/client/transport/generation-window.ts +108 -0
  346. package/src/client/transport/handler.ts +926 -0
  347. package/src/client/transport/index.ts +14 -0
  348. package/src/client/transport/packet.ts +69 -0
  349. package/src/client/transport/quicklz.ts +209 -0
  350. package/src/client/types.ts +268 -0
  351. package/src/core/api/channels.ts +396 -0
  352. package/src/core/api/clients.ts +654 -0
  353. package/src/core/api/command.ts +563 -0
  354. package/src/core/api/files.ts +405 -0
  355. package/src/core/api/groups.ts +622 -0
  356. package/src/core/api/moderation.ts +525 -0
  357. package/src/core/api/parse.ts +52 -0
  358. package/src/core/api/server.ts +709 -0
  359. package/src/core/command/command.ts +125 -0
  360. package/src/core/command/ids.ts +149 -0
  361. package/src/core/command/parser.ts +68 -0
  362. package/src/core/command/request.ts +119 -0
  363. package/src/core/command/responses.ts +114 -0
  364. package/src/core/command-name.ts +334 -0
  365. package/src/core/enums.ts +167 -0
  366. package/src/core/errorcode.ts +412 -0
  367. package/src/core/errors.ts +258 -0
  368. package/src/core/events.ts +219 -0
  369. package/src/core/helpers.ts +161 -0
  370. package/src/core/index.ts +126 -0
  371. package/src/core/internal.ts +23 -0
  372. package/src/core/middleware.ts +24 -0
  373. package/src/core/notification-name.ts +155 -0
  374. package/src/core/notifications.ts +157 -0
  375. package/src/core/permission-name.ts +538 -0
  376. package/src/core/permission.ts +34 -0
  377. package/src/core/property-name.ts +411 -0
  378. package/src/core/reason.ts +38 -0
  379. package/src/core/reconnect.ts +98 -0
  380. package/src/core/types.ts +138 -0
  381. package/src/query/client.ts +1132 -0
  382. package/src/query/errors.ts +108 -0
  383. package/src/query/index.ts +65 -0
  384. package/src/query/knownhosts.ts +257 -0
  385. package/src/query/lines.ts +28 -0
  386. package/src/query/options.ts +388 -0
  387. package/src/query/reconnect.ts +84 -0
  388. package/src/query/ssh.ts +614 -0
  389. package/src/query/stream.ts +91 -0
  390. package/src/query/tcp.ts +85 -0
  391. package/src/query/transport.ts +35 -0
  392. package/src/query/web.ts +195 -0
  393. package/src/versions/index.ts +10774 -0
@@ -0,0 +1 @@
1
+ export { Resolver } from "./resolver.js";
@@ -0,0 +1,325 @@
1
+ import { resolveSrv } from "node:dns/promises";
2
+ import { createConnection } from "node:net";
3
+ import type { ResolvedAddr } from "../types.js";
4
+ import { isIpAddress, joinHostPort, splitHostPort } from "../address.js";
5
+ import { abortable } from "../abort.js";
6
+ import { InvalidAddressError } from "../errors.js";
7
+
8
+ const TS_DNS_DEFAULT_PORT = 41144;
9
+ const NICKNAME_LOOKUP_URL = "https://named.myteamspeak.com/lookup";
10
+ const CACHE_TTL_MS = 10 * 60 * 1000;
11
+ /**
12
+ * How long resolution may take in all; past it, the address is used as given.
13
+ * A Client holds any resolver, its own or a custom one, to the same budget.
14
+ *
15
+ * @internal
16
+ */
17
+ export const RESOLVE_BUDGET_MS = 10_000;
18
+ /** How long one TSDNS server may take to answer. */
19
+ const TSDNS_TIMEOUT_MS = 3_000;
20
+
21
+ /**
22
+ * The most a TSDNS server or the nickname service is read for. Either answers
23
+ * with one short line; without a bound, one that kept sending would be held in
24
+ * memory until the resolution budget ran out.
25
+ */
26
+ export const MAX_DISCOVERY_RESPONSE: number = 8 * 1024;
27
+
28
+ /** How the default resolver resolves. */
29
+ export interface ResolverOptions {
30
+ /** Look dot-less names up at named.myteamspeak.com; off by default. */
31
+ readonly nicknameLookup?: boolean | undefined;
32
+ }
33
+
34
+ export class Resolver {
35
+ readonly #cache = new Map<string, ResolvedAddr[]>();
36
+ readonly #nicknameLookup: boolean;
37
+
38
+ constructor(options: ResolverOptions = {}) {
39
+ this.#nicknameLookup = options.nicknameLookup ?? false;
40
+ }
41
+
42
+ /**
43
+ * Resolve as the TeamSpeak application does: a server nickname (only with
44
+ * `nicknameLookup`), then SRV, then TSDNS, then the address as given. The
45
+ * whole resolution is bounded: past the budget, or when a step fails, the
46
+ * address as given is the answer.
47
+ */
48
+ async resolve(
49
+ inputAddr: string,
50
+ options: { readonly signal?: AbortSignal | undefined } = {},
51
+ ): Promise<ResolvedAddr[]> {
52
+ if (!inputAddr) throw new InvalidAddressError(inputAddr, "empty address");
53
+ const budget = AbortSignal.timeout(RESOLVE_BUDGET_MS);
54
+ const signal = options.signal ? AbortSignal.any([options.signal, budget]) : budget;
55
+ try {
56
+ return await this.#resolve(inputAddr, signal, true);
57
+ } catch (error) {
58
+ options.signal?.throwIfAborted();
59
+ if (!budget.aborted) throw error;
60
+ const { host, port } = splitHostPort(inputAddr);
61
+ return [{ addr: joinHostPort(host, port), source: "Direct" }];
62
+ }
63
+ }
64
+
65
+ async #resolve(
66
+ inputAddr: string,
67
+ signal: AbortSignal,
68
+ nicknames: boolean,
69
+ ): Promise<ResolvedAddr[]> {
70
+ const cached = this.#getValidCache(inputAddr);
71
+ if (cached) return cached;
72
+
73
+ const { host, port } = splitHostPort(inputAddr);
74
+
75
+ if (isIpAddress(host)) {
76
+ return [{ addr: joinHostPort(host, port), source: "Direct" }];
77
+ }
78
+
79
+ // A dot-less name may be a server nickname, which only the TeamSpeak
80
+ // service can answer: asking it sends the name to a third party, so it is
81
+ // opt-in. Otherwise the name is resolved like any host.
82
+ if (nicknames && this.#nicknameLookup && !host.includes(".") && host !== "localhost") {
83
+ const nickAddr = await resolveNickname(host, signal);
84
+ signal.throwIfAborted();
85
+ if (nickAddr) return this.#resolve(nickAddr, signal, false);
86
+ }
87
+
88
+ // SRV _ts3._udp.<host>
89
+ const srvResults = await this.#resolveSRV(host, signal);
90
+ if (srvResults) return this.#setCache(inputAddr, srvResults);
91
+
92
+ const domainList = getDomainList(host);
93
+
94
+ // TSDNS via SRV _tsdns._tcp.<domain>
95
+ const tsdnsSrv = await this.#resolveTSDNSSRV(domainList, host, signal);
96
+ if (tsdnsSrv) {
97
+ return this.#setCache(inputAddr, [{ addr: tsdnsSrv, source: "TSDNS-SRV" }]);
98
+ }
99
+
100
+ // TSDNS direct :41144
101
+ const tsdnsDirect = await this.#resolveTSDNSDirect(domainList, host, signal);
102
+ if (tsdnsDirect) {
103
+ return this.#setCache(inputAddr, [{ addr: tsdnsDirect, source: "TSDNS-Direct" }]);
104
+ }
105
+
106
+ signal.throwIfAborted();
107
+ // Plain DNS fallback
108
+ const fallback: ResolvedAddr[] = [{ addr: joinHostPort(host, port), source: "Direct" }];
109
+ return this.#setCache(inputAddr, fallback);
110
+ }
111
+
112
+ #getValidCache(inputAddr: string): ResolvedAddr[] | null {
113
+ const cached = this.#cache.get(inputAddr);
114
+ if (!cached || cached.length === 0) return null;
115
+ const first = cached[0]!;
116
+ if (first.expiry !== undefined && Date.now() > first.expiry.getTime()) return null;
117
+ return cached;
118
+ }
119
+
120
+ #setCache(key: string, results: ResolvedAddr[]): ResolvedAddr[] {
121
+ const expiry = new Date(Date.now() + CACHE_TTL_MS);
122
+ const withExpiry = results.map((r) => ({ ...r, expiry }));
123
+ this.#cache.set(key, withExpiry);
124
+ return withExpiry;
125
+ }
126
+
127
+ async #resolveSRV(host: string, signal?: AbortSignal): Promise<ResolvedAddr[] | null> {
128
+ try {
129
+ const srvs = await abortable(resolveSrv(`_ts3._udp.${host}`), signal);
130
+ if (!srvs || srvs.length === 0) return null;
131
+ return orderSrvRecords(srvs).map((srv) => ({
132
+ addr: joinHostPort(srv.name.replace(/\.$/, ""), String(srv.port)),
133
+ source: "SRV",
134
+ }));
135
+ } catch {
136
+ return null;
137
+ }
138
+ }
139
+
140
+ async #resolveTSDNSSRV(
141
+ domains: string[],
142
+ queryHost: string,
143
+ signal?: AbortSignal,
144
+ ): Promise<string | null> {
145
+ for (const domain of domains) {
146
+ try {
147
+ const srvs = await abortable(resolveSrv(`_tsdns._tcp.${domain}`), signal);
148
+ if (!srvs || srvs.length === 0) continue;
149
+ for (const srv of orderSrvRecords(srvs)) {
150
+ const tsdnsAddr = joinHostPort(srv.name.replace(/\.$/, ""), String(srv.port));
151
+ const result = await queryTSDNS(tsdnsAddr, queryHost, signal);
152
+ if (result) return result;
153
+ }
154
+ } catch {
155
+ continue;
156
+ }
157
+ }
158
+ return null;
159
+ }
160
+
161
+ async #resolveTSDNSDirect(
162
+ domains: string[],
163
+ queryHost: string,
164
+ signal?: AbortSignal,
165
+ ): Promise<string | null> {
166
+ // Asked at once rather than in turn, so the probes cost one timeout in
167
+ // all; the most specific domain's answer wins.
168
+ const answers = await Promise.all(
169
+ domains.map((domain) =>
170
+ queryTSDNS(joinHostPort(domain, String(TS_DNS_DEFAULT_PORT)), queryHost, signal),
171
+ ),
172
+ );
173
+ return answers.find((answer) => answer !== null) ?? null;
174
+ }
175
+ }
176
+
177
+ // ---- Helpers ----------------------------------------------------------------
178
+
179
+ /** The part of an SRV record that decides the order they are tried in. */
180
+ interface SrvRecord {
181
+ readonly priority: number;
182
+ readonly weight: number;
183
+ }
184
+
185
+ /**
186
+ * Put SRV records in the order RFC 2782 has a client try them: by priority,
187
+ * lowest first, and within a priority by weighted random selection, so a
188
+ * record of weight 2 is first twice as often as one of weight 1. Records of
189
+ * weight 0 follow the others of their priority, in the order given. Node's
190
+ * `resolveSrv` returns the records unsorted.
191
+ *
192
+ * @param random Returns a number in [0, 1); injected to make the order testable.
193
+ * @internal
194
+ */
195
+ export function orderSrvRecords<T extends SrvRecord>(
196
+ records: readonly T[],
197
+ random: () => number = Math.random,
198
+ ): T[] {
199
+ const byPriority = new Map<number, T[]>();
200
+ for (const record of records) {
201
+ const group = byPriority.get(record.priority);
202
+ if (group) group.push(record);
203
+ else byPriority.set(record.priority, [record]);
204
+ }
205
+ const ordered: T[] = [];
206
+ for (const priority of [...byPriority.keys()].sort((a, b) => a - b)) {
207
+ const group = byPriority.get(priority)!;
208
+ const weighted = group.filter((r) => r.weight > 0);
209
+ const unweighted = group.filter((r) => r.weight <= 0);
210
+ let sum = weighted.reduce((total, r) => total + r.weight, 0);
211
+ while (weighted.length > 0) {
212
+ const target = random() * sum;
213
+ let running = 0;
214
+ let pick = weighted.length - 1;
215
+ for (let i = 0; i < weighted.length; i++) {
216
+ running += weighted[i]!.weight;
217
+ if (running > target) {
218
+ pick = i;
219
+ break;
220
+ }
221
+ }
222
+ const [chosen] = weighted.splice(pick, 1);
223
+ ordered.push(chosen!);
224
+ sum -= chosen!.weight;
225
+ }
226
+ ordered.push(...unweighted);
227
+ }
228
+ return ordered;
229
+ }
230
+
231
+ /** @internal Ask the nickname service for `nickname`'s address. */
232
+ export async function resolveNickname(
233
+ nickname: string,
234
+ signal?: AbortSignal,
235
+ ): Promise<string | null> {
236
+ try {
237
+ const url = new URL(NICKNAME_LOOKUP_URL);
238
+ url.searchParams.set("name", nickname);
239
+ const init: RequestInit = signal ? { signal } : {};
240
+ const resp = await fetch(url.toString(), init);
241
+ if (!resp.ok) return null;
242
+ const text = await readLimited(resp, MAX_DISCOVERY_RESPONSE);
243
+ const line = text.split("\n")[0]?.trim();
244
+ return line || null;
245
+ } catch {
246
+ return null;
247
+ }
248
+ }
249
+
250
+ /** A response body's first `limit` bytes, as text; the rest is not read. */
251
+ async function readLimited(resp: Response, limit: number): Promise<string> {
252
+ if (resp.body === null) return "";
253
+ const reader = resp.body.getReader();
254
+ const chunks: Uint8Array[] = [];
255
+ let size = 0;
256
+ try {
257
+ while (size < limit) {
258
+ const { done, value } = await reader.read();
259
+ if (done) break;
260
+ const part = value.subarray(0, limit - size);
261
+ chunks.push(part);
262
+ size += part.length;
263
+ }
264
+ } finally {
265
+ reader.cancel().catch(() => {});
266
+ }
267
+ return Buffer.concat(chunks).toString("utf8");
268
+ }
269
+
270
+ /** @internal Ask the TSDNS server at `tsdnsAddr` for `queryHost`'s address. */
271
+ export function queryTSDNS(
272
+ tsdnsAddr: string,
273
+ queryHost: string,
274
+ signal?: AbortSignal,
275
+ ): Promise<string | null> {
276
+ const [host, portStr] = splitHostPortParts(tsdnsAddr);
277
+ const port = parseInt(portStr, 10);
278
+
279
+ return new Promise((resolve) => {
280
+ if (signal?.aborted) return resolve(null);
281
+ const finish = (answer: string | null): void => {
282
+ clearTimeout(timeout);
283
+ signal?.removeEventListener("abort", onAbort);
284
+ socket.destroy();
285
+ resolve(answer);
286
+ };
287
+ const onAbort = (): void => finish(null);
288
+ const timeout = setTimeout(() => finish(null), TSDNS_TIMEOUT_MS);
289
+
290
+ const socket = createConnection({ host, port, timeout: 2_000 }, () => {
291
+ socket.write(`${queryHost}\n`);
292
+ });
293
+
294
+ let buf = "";
295
+ socket.on("data", (chunk: Buffer) => {
296
+ buf += chunk.toString("latin1");
297
+ const idx = buf.indexOf("\n");
298
+ if (idx >= 0 && idx < MAX_DISCOVERY_RESPONSE) {
299
+ const line = Buffer.from(buf.slice(0, idx), "latin1").toString("utf8").trim();
300
+ finish(!line || line === "404" || line === "errors" ? null : line);
301
+ } else if (buf.length >= MAX_DISCOVERY_RESPONSE) {
302
+ // No answer is that long.
303
+ finish(null);
304
+ }
305
+ });
306
+
307
+ socket.on("error", () => finish(null));
308
+ socket.on("timeout", () => finish(null));
309
+ signal?.addEventListener("abort", onAbort, { once: true });
310
+ });
311
+ }
312
+
313
+ function splitHostPortParts(addr: string): [string, string] {
314
+ const { host, port } = splitHostPort(addr);
315
+ return [host, port];
316
+ }
317
+
318
+ function getDomainList(host: string): string[] {
319
+ const parts = host.split(".");
320
+ const list: string[] = [];
321
+ for (let i = 0; i < parts.length - 1; i++) {
322
+ list.push(parts.slice(i).join("."));
323
+ }
324
+ return list.slice(0, 3);
325
+ }
@@ -0,0 +1,217 @@
1
+ import { Reason, TeamspeakError } from "@honeybbq/teamspeak";
2
+
3
+ /**
4
+ * The address a client was given cannot be connected to: no host, a host with
5
+ * characters no host name or IP address has, or a port outside 1–65535.
6
+ * `connect()` rejects with it before it starts, and the client stays
7
+ * disconnected. A name that does not resolve is not this: that fails with a
8
+ * TeamspeakError whose `cause` is the lookup's error, since DNS failures are
9
+ * often transient.
10
+ */
11
+ export class InvalidAddressError extends TeamspeakError {
12
+ /** {@inheritDoc @honeybbq/teamspeak!TeamspeakError.kind} */
13
+ static override readonly kind: string = "InvalidAddressError";
14
+ static {
15
+ // On the prototype, not the instance, as built-in errors do: an own `name`
16
+ // would be enumerable and show up in JSON.stringify and object spread.
17
+ Object.defineProperty(this.prototype, "name", {
18
+ value: "InvalidAddressError",
19
+ writable: true,
20
+ configurable: true,
21
+ enumerable: false,
22
+ });
23
+ }
24
+
25
+ /** The address as given. */
26
+ readonly address: string;
27
+
28
+ constructor(address: string, reason: string) {
29
+ super(`invalid address ${JSON.stringify(address)}: ${reason}`);
30
+ this.address = address;
31
+ }
32
+ }
33
+
34
+ /**
35
+ * A packet failed authentication: it was corrupted, or not from the server.
36
+ * Internal: the packet is dropped, and this never reaches a caller.
37
+ */
38
+ export class EAXTagMismatchError extends TeamspeakError {
39
+ /** {@inheritDoc @honeybbq/teamspeak!TeamspeakError.kind} */
40
+ static override readonly kind: string = "EAXTagMismatchError";
41
+ static {
42
+ // On the prototype, not the instance, as built-in errors do: an own `name`
43
+ // would be enumerable and show up in JSON.stringify and object spread.
44
+ Object.defineProperty(this.prototype, "name", {
45
+ value: "EAXTagMismatchError",
46
+ writable: true,
47
+ configurable: true,
48
+ enumerable: false,
49
+ });
50
+ }
51
+
52
+ constructor() {
53
+ super("EAX tag mismatch");
54
+ }
55
+ }
56
+
57
+ /**
58
+ * An unencrypted packet failed its integrity check. Internal: the packet is
59
+ * dropped, and this never reaches a caller.
60
+ */
61
+ export class FakeSignatureMismatchError extends TeamspeakError {
62
+ /** {@inheritDoc @honeybbq/teamspeak!TeamspeakError.kind} */
63
+ static override readonly kind: string = "FakeSignatureMismatchError";
64
+ static {
65
+ // On the prototype, not the instance, as built-in errors do: an own `name`
66
+ // would be enumerable and show up in JSON.stringify and object spread.
67
+ Object.defineProperty(this.prototype, "name", {
68
+ value: "FakeSignatureMismatchError",
69
+ writable: true,
70
+ configurable: true,
71
+ enumerable: false,
72
+ });
73
+ }
74
+
75
+ constructor() {
76
+ super("fake signature mismatch");
77
+ }
78
+ }
79
+
80
+ /** A file transfer connection failed: it could not be opened, or broke during the transfer. */
81
+ export class FileTransferError extends TeamspeakError {
82
+ /** {@inheritDoc @honeybbq/teamspeak!TeamspeakError.kind} */
83
+ static override readonly kind: string = "FileTransferError";
84
+ static {
85
+ // On the prototype, not the instance, as built-in errors do: an own `name`
86
+ // would be enumerable and show up in JSON.stringify and object spread.
87
+ Object.defineProperty(this.prototype, "name", {
88
+ value: "FileTransferError",
89
+ writable: true,
90
+ configurable: true,
91
+ enumerable: false,
92
+ });
93
+ }
94
+
95
+ constructor(message: string, options?: ErrorOptions) {
96
+ super(message, options);
97
+ }
98
+ }
99
+
100
+ /**
101
+ * The handshake failed on what the server sent: a malformed or out-of-range
102
+ * Init1 step, public key, license or proof. `connect()` rejects with it, the
103
+ * underlying failure as its `cause`.
104
+ */
105
+ export class HandshakeError extends TeamspeakError {
106
+ /** {@inheritDoc @honeybbq/teamspeak!TeamspeakError.kind} */
107
+ static override readonly kind: string = "HandshakeError";
108
+ static {
109
+ // On the prototype, not the instance, as built-in errors do: an own `name`
110
+ // would be enumerable and show up in JSON.stringify and object spread.
111
+ Object.defineProperty(this.prototype, "name", {
112
+ value: "HandshakeError",
113
+ writable: true,
114
+ configurable: true,
115
+ enumerable: false,
116
+ });
117
+ }
118
+
119
+ constructor(message: string, options?: ErrorOptions) {
120
+ super(message, options);
121
+ }
122
+ }
123
+
124
+ /** An identity string `parseIdentity()` cannot read. */
125
+ export class InvalidIdentityError extends TeamspeakError {
126
+ /** {@inheritDoc @honeybbq/teamspeak!TeamspeakError.kind} */
127
+ static override readonly kind: string = "InvalidIdentityError";
128
+ static {
129
+ // On the prototype, not the instance, as built-in errors do: an own `name`
130
+ // would be enumerable and show up in JSON.stringify and object spread.
131
+ Object.defineProperty(this.prototype, "name", {
132
+ value: "InvalidIdentityError",
133
+ writable: true,
134
+ configurable: true,
135
+ enumerable: false,
136
+ });
137
+ }
138
+
139
+ constructor(message = "invalid identity format", options?: ErrorOptions) {
140
+ super(message, options);
141
+ }
142
+ }
143
+
144
+ /**
145
+ * The server stopped answering: nothing arrived, or a packet went
146
+ * unacknowledged, for a minute.
147
+ */
148
+ export class ConnectionTimeoutError extends TeamspeakError {
149
+ /** {@inheritDoc @honeybbq/teamspeak!TeamspeakError.kind} */
150
+ static override readonly kind: string = "ConnectionTimeoutError";
151
+ static {
152
+ // On the prototype, not the instance, as built-in errors do: an own `name`
153
+ // would be enumerable and show up in JSON.stringify and object spread.
154
+ Object.defineProperty(this.prototype, "name", {
155
+ value: "ConnectionTimeoutError",
156
+ writable: true,
157
+ configurable: true,
158
+ enumerable: false,
159
+ });
160
+ }
161
+
162
+ constructor() {
163
+ super("connection timed out: the server stopped answering");
164
+ }
165
+ }
166
+
167
+ /**
168
+ * The server ended this client's session: a kick, a ban, or the server
169
+ * stopping. The default reconnect policy retries a server that stopped, but
170
+ * not a kick or a ban.
171
+ */
172
+ export class RemovedError extends TeamspeakError {
173
+ /** {@inheritDoc @honeybbq/teamspeak!TeamspeakError.kind} */
174
+ static override readonly kind: string = "RemovedError";
175
+ static {
176
+ // On the prototype, not the instance, as built-in errors do: an own `name`
177
+ // would be enumerable and show up in JSON.stringify and object spread.
178
+ Object.defineProperty(this.prototype, "name", {
179
+ value: "RemovedError",
180
+ writable: true,
181
+ configurable: true,
182
+ enumerable: false,
183
+ });
184
+ }
185
+
186
+ /** Why: `Reason.KickServer`, `KickServerBan`, `ServerStop` or `ServerShutdown`. */
187
+ readonly reason: number;
188
+ /** The reason the server or the invoker gave, if any. */
189
+ readonly reasonMessage: string;
190
+ /** Who removed the client, for a kick or a ban. */
191
+ readonly invokerName: string;
192
+
193
+ constructor(reason: number, reasonMessage: string, invokerName: string) {
194
+ let message = `removed from the server: ${reasonName(reason)}`;
195
+ if (invokerName) message += ` by ${invokerName}`;
196
+ if (reasonMessage) message += `: ${reasonMessage}`;
197
+ super(message);
198
+ this.reason = reason;
199
+ this.reasonMessage = reasonMessage;
200
+ this.invokerName = invokerName;
201
+ }
202
+ }
203
+
204
+ function reasonName(reason: number): string {
205
+ switch (reason) {
206
+ case Reason.KickServer:
207
+ return "kicked from server";
208
+ case Reason.KickServerBan:
209
+ return "banned from server";
210
+ case Reason.ServerStop:
211
+ return "server stopped";
212
+ case Reason.ServerShutdown:
213
+ return "server shut down";
214
+ default:
215
+ return `reason ${reason}`;
216
+ }
217
+ }
@@ -0,0 +1,12 @@
1
+ import type { EventMiddleware, ServerEvent } from "./types.js";
2
+
3
+ export function buildEventChain(
4
+ middlewares: EventMiddleware[],
5
+ base: (event: ServerEvent) => void,
6
+ ): (event: ServerEvent) => void {
7
+ let handler = base;
8
+ for (let i = middlewares.length - 1; i >= 0; i--) {
9
+ handler = middlewares[i]!(handler);
10
+ }
11
+ return handler;
12
+ }