@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,614 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { MissingDependencyError, QueryAuthError, SshHostKeyError } from "./errors.js";
3
+ import { KnownHosts, fingerprintOf, keyTypeOf } from "./knownhosts.js";
4
+ import { StreamTransport } from "./stream.js";
5
+ import type { QueryStream } from "./stream.js";
6
+
7
+ /**
8
+ * The part of an SSH client this transport uses: `ssh2`'s `Client` is one, and
9
+ * so is anything else with these methods.
10
+ *
11
+ * Declared here rather than imported from `@types/ssh2`, which a caller
12
+ * without the optional peer dependency does not have: the types are a
13
+ * devDependency and are not published with the package. A later release may
14
+ * widen this, and every new member will be optional, so an implementation
15
+ * written today keeps compiling.
16
+ */
17
+ export interface SshClient {
18
+ /**
19
+ * Begin the connection; `ready` is emitted once the handshake is done.
20
+ *
21
+ * `hostVerifier` is asked about the server's key before anything is
22
+ * authenticated, so before the password is sent. It is given the key's wire
23
+ * encoding and returns whether to go on: a client must end the connection
24
+ * when it returns `false`, and must not treat a missing answer as a yes.
25
+ */
26
+ connect(config: {
27
+ host: string;
28
+ port: number;
29
+ username: string;
30
+ password: string;
31
+ readyTimeout: number;
32
+ hostVerifier: (key: Uint8Array) => boolean;
33
+ }): void;
34
+ /** Open a shell channel, or report why it could not be opened. */
35
+ shell(pty: false, callback: (error: Error | undefined, channel: SshChannel) => void): void;
36
+ /** End the connection. */
37
+ end(): void;
38
+ /** Listen for the handshake completing, once. */
39
+ once(event: "ready", listener: () => void): unknown;
40
+ /** Listen for the next failure, once. */
41
+ once(event: "error", listener: (error: Error) => void): unknown;
42
+ /** Listen for each failure. */
43
+ on(event: "error", listener: (error: Error) => void): unknown;
44
+ /** Stop listening for failures. */
45
+ off(event: "error", listener: (error: Error) => void): unknown;
46
+ }
47
+
48
+ /** The channel an SSH client's shell opens: a byte stream, and the errors on it. */
49
+ export interface SshChannel extends QueryStream {
50
+ /** Report a failure on the stream, as a socket would: the transport reads it. */
51
+ emit(event: "error", error: Error): unknown;
52
+ /** Listen for the stream closing, once. */
53
+ once(event: "close", listener: () => void): unknown;
54
+ }
55
+
56
+ /**
57
+ * An SSH client this transport uses instead of loading `ssh2` itself: either
58
+ * one that is connected already, or a factory that makes one.
59
+ *
60
+ * A caller that holds its own connected client — authenticated through an
61
+ * agent, a jump host or a key file this transport knows nothing about — hands
62
+ * it over. A factory is asked for one per connection attempt, which a
63
+ * reconnection needs.
64
+ */
65
+ export type SshConnection = SshClient | (() => Promise<SshClient>);
66
+
67
+ /**
68
+ * The key an SSH server presented, as the check a caller supplies sees it.
69
+ *
70
+ * A type of this package's own rather than the SSH library's, so a policy can
71
+ * be written without importing that library.
72
+ *
73
+ * @experimental Part of the `@honeybbq/teamspeak/query` entry, which may
74
+ * change in a minor release.
75
+ */
76
+ export interface SshHostKey {
77
+ /** The host that was dialled, as it was given. */
78
+ readonly host: string;
79
+ /** The port that was dialled. */
80
+ readonly port: number;
81
+ /** The key's algorithm, such as `ssh-ed25519`, as its own encoding names it. */
82
+ readonly keyType: string;
83
+ /** The key's wire encoding: the blob a `known_hosts` line carries in base64. */
84
+ readonly key: Uint8Array;
85
+ /**
86
+ * The key's SHA-256 fingerprint in OpenSSH form, such as `SHA256:HR5y2ZQ…`.
87
+ * It is what `ssh-keygen -lf` and `ssh-keyscan` print, so it can be compared
88
+ * with the one the server's operator published.
89
+ */
90
+ readonly fingerprint: string;
91
+ }
92
+
93
+ /**
94
+ * A caller's check of an SSH server's host key: returns to accept the key and
95
+ * throws to refuse it.
96
+ *
97
+ * Throwing is how a callback with no error channel says no, and the error it
98
+ * throws is kept as the `cause` of the {@link SshHostKeyError} the connection
99
+ * fails with. It runs before the login is sent, and it has to be synchronous
100
+ * and return nothing: a return value, a `false` included, is not an answer this
101
+ * can read, so it refuses the key rather than guess, as does a promise, whose
102
+ * rejection would arrive too late to stop anything.
103
+ *
104
+ * @experimental Part of the `@honeybbq/teamspeak/query` entry, which may
105
+ * change in a minor release.
106
+ */
107
+ export type SshHostKeyVerifier = (key: SshHostKey) => void;
108
+
109
+ /**
110
+ * Your own check of the server's host key. Use it when the key you trust does
111
+ * not live in a `known_hosts` file: a fingerprint from configuration, a pinned
112
+ * key, a lookup somewhere else.
113
+ *
114
+ * One of the three arms of {@link SshHostKeyPolicy}: giving it excludes the
115
+ * other two.
116
+ *
117
+ * @experimental Part of the `@honeybbq/teamspeak/query` entry, which may
118
+ * change in a minor release.
119
+ */
120
+ export interface SshHostKeyCallbackPolicy {
121
+ /**
122
+ * The check; see {@link SshHostKeyVerifier}. Called once per connection
123
+ * attempt, before the login is sent.
124
+ */
125
+ readonly hostKey: SshHostKeyVerifier;
126
+ /** Left out: `hostKey` is the policy, and a second one would not say which decides. */
127
+ readonly knownHosts?: never;
128
+ /** Left out: `hostKey` is the policy, and a second one would not say which decides. */
129
+ readonly insecureIgnoreHostKey?: never;
130
+ }
131
+
132
+ /**
133
+ * Accept whatever key the server presents. Use it for a test or a throwaway
134
+ * server on a network that is already trusted, and for nothing else.
135
+ *
136
+ * One of the three arms of {@link SshHostKeyPolicy}: giving it excludes the
137
+ * other two.
138
+ *
139
+ * @experimental Part of the `@honeybbq/teamspeak/query` entry, which may
140
+ * change in a minor release.
141
+ */
142
+ export interface SshInsecureHostKeyPolicy {
143
+ /**
144
+ * Only `true` is a value: this is not a switch to be left `false`, but a
145
+ * decision a call site makes out loud.
146
+ *
147
+ * It gives up the one thing the host key is for. Anyone who can answer the
148
+ * port, on the path or at the address, receives the query login and its
149
+ * password and is indistinguishable from the server. Use
150
+ * {@link SshKnownHostsPolicy.knownHosts} otherwise.
151
+ */
152
+ readonly insecureIgnoreHostKey: true;
153
+ /** Left out: accepting any key leaves nothing for a second policy to decide. */
154
+ readonly hostKey?: never;
155
+ /** Left out: accepting any key leaves nothing for a second policy to decide. */
156
+ readonly knownHosts?: never;
157
+ }
158
+
159
+ // Declared last, on purpose. When no arm matches, TypeScript names one arm's
160
+ // missing property in the error, and which one depends on the compiler: 5.x names
161
+ // the arm declared last. This is the arm to point a user at, and the others are
162
+ // not: `insecureIgnoreHostKey` is the one to be least likely to be added to make
163
+ // an error go away.
164
+ /**
165
+ * Check the server against an OpenSSH `known_hosts` file. Use it for a server
166
+ * whose key you have recorded with `ssh-keyscan`, or already trust through
167
+ * `ssh`.
168
+ *
169
+ * One of the three arms of {@link SshHostKeyPolicy}: giving it excludes the
170
+ * other two.
171
+ *
172
+ * @experimental Part of the `@honeybbq/teamspeak/query` entry, which may
173
+ * change in a minor release.
174
+ */
175
+ export interface SshKnownHostsPolicy {
176
+ /**
177
+ * A path to the `known_hosts` file, in the format `ssh-keyscan` writes and
178
+ * `ssh-keygen -H` hashes.
179
+ *
180
+ * Read when each connection is attempted, so a reconnection sees the file as
181
+ * it is then, and only from the path given: `~/.ssh/known_hosts` is never
182
+ * assumed, because a library reading the home directory on its own is a
183
+ * surprise. The check is what `ssh` does with `StrictHostKeyChecking=yes`: a
184
+ * host the file does not mention is refused, so is one recorded with another
185
+ * key, and so is a key a `@revoked` line names. Plain, `[host]:port` and
186
+ * hashed entries are read; a line that cannot be read fails the connection
187
+ * rather than being skipped.
188
+ *
189
+ * A port other than 22 is recorded as `[host]:port`, which is what
190
+ * `ssh-keyscan -p` writes: an entry for port 22 does not vouch for the query
191
+ * port.
192
+ */
193
+ readonly knownHosts: string;
194
+ /** Left out: `knownHosts` is the policy, and a second one would not say which decides. */
195
+ readonly hostKey?: never;
196
+ /** Left out: `knownHosts` is the policy, and a second one would not say which decides. */
197
+ readonly insecureIgnoreHostKey?: never;
198
+ }
199
+
200
+ /**
201
+ * How the server's host key is checked: exactly one of `hostKey`, `knownHosts`
202
+ * and `insecureIgnoreHostKey`.
203
+ *
204
+ * There is no default, and a missing or doubled choice does not compile. The
205
+ * query login is a credential, normally `serveradmin`'s, and a client that
206
+ * accepts any key hands it to whatever answers the port. The same rule is
207
+ * checked when the program runs, for callers that are not type checked.
208
+ *
209
+ * @experimental Part of the `@honeybbq/teamspeak/query` entry, which may
210
+ * change in a minor release.
211
+ */
212
+ export type SshHostKeyPolicy =
213
+ | SshHostKeyCallbackPolicy
214
+ | SshKnownHostsPolicy
215
+ | SshInsecureHostKeyPolicy;
216
+
217
+ /**
218
+ * The address and the login of a server this transport dials itself: the part
219
+ * of {@link SshDialOptions} that is not the host key policy.
220
+ *
221
+ * @experimental Part of the `@honeybbq/teamspeak/query` entry, which may
222
+ * change in a minor release.
223
+ */
224
+ export interface SshDialTarget {
225
+ /** The server's host. */
226
+ readonly host: string;
227
+ /** The SSH query port; 10022 by default. */
228
+ readonly port?: number | undefined;
229
+ /** The query login name, such as `serveradmin`. */
230
+ readonly username: string;
231
+ /** The query login's password. */
232
+ readonly password: string;
233
+ /** Left out: a connection of your own is {@link SshSuppliedConnectionOptions}. */
234
+ readonly connection?: never;
235
+ }
236
+
237
+ /**
238
+ * Connect to a server this transport dials itself: with a login, and with a
239
+ * host key policy. The usual way, and the one to use unless you already hold a
240
+ * connected SSH client.
241
+ *
242
+ * It is {@link SshHostKeyPolicy} together with the address and the login, so
243
+ * leaving the policy out is a type error that names the three ways to give one.
244
+ *
245
+ * @experimental Part of the `@honeybbq/teamspeak/query` entry, which may
246
+ * change in a minor release.
247
+ */
248
+ export type SshDialOptions = SshDialTarget & SshHostKeyPolicy;
249
+
250
+ /**
251
+ * Use an SSH client you made and connected yourself, or a factory for one:
252
+ * authenticated through an agent, a jump host or a key file this transport
253
+ * knows nothing about.
254
+ *
255
+ * Only a shell is opened on it, so no login and no host key policy are taken:
256
+ * the client you supply has done both already, and an option nothing would
257
+ * read is a type error rather than something that could suggest the key was
258
+ * verified here. The host and port are the server's identity, for messages.
259
+ *
260
+ * @experimental Part of the `@honeybbq/teamspeak/query` entry, which may
261
+ * change in a minor release.
262
+ */
263
+ export interface SshSuppliedConnectionOptions {
264
+ /** The server's host, for identity and messages: nothing is dialled. */
265
+ readonly host: string;
266
+ /** The server's SSH query port, for identity and messages; 10022 by default. */
267
+ readonly port?: number | undefined;
268
+ /**
269
+ * The SSH client to use, or a factory for one. A client that is already
270
+ * connected skips the handshake; a factory is asked for one per connection
271
+ * attempt, which a reconnection needs.
272
+ *
273
+ * Deliberately narrow, so a later release can widen it without breaking an
274
+ * implementation written today. Its host key is checked by whoever made it.
275
+ */
276
+ readonly connection: SshConnection;
277
+ /** Left out: the supplied client is authenticated already. */
278
+ readonly username?: never;
279
+ /** Left out: the supplied client is authenticated already. */
280
+ readonly password?: never;
281
+ /** Left out: the supplied client's host key is checked by whoever made it. */
282
+ readonly hostKey?: never;
283
+ /** Left out: the supplied client's host key is checked by whoever made it. */
284
+ readonly knownHosts?: never;
285
+ /** Left out: the supplied client's host key is checked by whoever made it. */
286
+ readonly insecureIgnoreHostKey?: never;
287
+ }
288
+
289
+ /**
290
+ * How a ServerQuery over SSH is reached, without the cancellation: one of
291
+ * {@link SshDialOptions} and {@link SshSuppliedConnectionOptions}.
292
+ *
293
+ * What a `QueryClient`'s `ssh` transport is built from, so that the two cannot
294
+ * drift apart.
295
+ *
296
+ * @experimental Part of the `@honeybbq/teamspeak/query` entry, which may
297
+ * change in a minor release.
298
+ */
299
+ export type SshConnectOptions = SshDialOptions | SshSuppliedConnectionOptions;
300
+
301
+ /**
302
+ * Where a ServerQuery over SSH listens, who logs in, and how the server's host
303
+ * key is checked.
304
+ *
305
+ * One of two shapes. The usual one, {@link SshDialOptions}, takes the host, the
306
+ * login and exactly one of `hostKey`, `knownHosts` and `insecureIgnoreHostKey`:
307
+ * there is no default, because the login is a credential and a client that
308
+ * accepts any key hands it to whatever answers the port. The other,
309
+ * {@link SshSuppliedConnectionOptions}, takes an SSH client you connected
310
+ * yourself, and no login or policy.
311
+ *
312
+ * @experimental Part of the `@honeybbq/teamspeak/query` entry, which may
313
+ * change in a minor release.
314
+ */
315
+ export type SshOptions = SshConnectOptions & {
316
+ /** Abandons the connection attempt. */
317
+ readonly signal?: AbortSignal | undefined;
318
+ };
319
+
320
+ /** How the host key is to be decided, once the options have been read. */
321
+ export type HostKeyDecision =
322
+ | { readonly kind: "verifier"; readonly verify: SshHostKeyVerifier }
323
+ | { readonly kind: "knownHosts"; readonly path: string }
324
+ | { readonly kind: "insecure" }
325
+ /** The caller supplied the connection, so the key was theirs to check. */
326
+ | { readonly kind: "connection" };
327
+
328
+ /**
329
+ * What the decision reads. Every member is `unknown`: the types of
330
+ * {@link SshOptions} already make a wrong combination a compile error, and this
331
+ * is for the callers they do not reach, a JavaScript program or an `as` cast.
332
+ */
333
+ export interface SshChoice {
334
+ readonly hostKey?: unknown;
335
+ readonly knownHosts?: unknown;
336
+ readonly insecureIgnoreHostKey?: unknown;
337
+ readonly connection?: unknown;
338
+ readonly username?: unknown;
339
+ readonly password?: unknown;
340
+ }
341
+
342
+ /**
343
+ * Read the trust decision out of the options, or refuse them.
344
+ *
345
+ * Fails closed: with none of the three there is no policy, and this throws
346
+ * rather than choose one. Two is a `TypeError`, since a caller who passed both
347
+ * has not decided which is authoritative. Runs when a client is built as well
348
+ * as when it connects, so a missing decision is heard about before the first
349
+ * attempt.
350
+ *
351
+ * The same rule as the types state, for the callers they do not reach.
352
+ */
353
+ export function hostKeyDecision(options: SshChoice): HostKeyDecision {
354
+ const { hostKey, knownHosts, insecureIgnoreHostKey, connection, username, password } = options;
355
+ if (hostKey !== undefined && typeof hostKey !== "function") {
356
+ throw new TypeError("hostKey is a function that throws to refuse a host key");
357
+ }
358
+ if (knownHosts !== undefined && (typeof knownHosts !== "string" || knownHosts === "")) {
359
+ throw new TypeError("knownHosts is the path of a known_hosts file");
360
+ }
361
+ if (insecureIgnoreHostKey !== undefined && insecureIgnoreHostKey !== true) {
362
+ throw new TypeError(
363
+ "insecureIgnoreHostKey is `true` or left out: it is a decision, not a switch to set to false",
364
+ );
365
+ }
366
+
367
+ const given: string[] = [];
368
+ if (hostKey !== undefined) given.push("hostKey");
369
+ if (knownHosts !== undefined) given.push("knownHosts");
370
+ if (insecureIgnoreHostKey !== undefined) given.push("insecureIgnoreHostKey");
371
+ if (given.length > 1) {
372
+ throw new TypeError(
373
+ `${given.join(", ")} were given together; exactly one of hostKey, knownHosts and insecureIgnoreHostKey decides whether the host key is accepted`,
374
+ );
375
+ }
376
+ if (connection !== undefined) {
377
+ if (given.length > 0) {
378
+ throw new TypeError(
379
+ `${given[0]} has no effect with connection, which makes the connection itself: its host key is checked by whoever made it`,
380
+ );
381
+ }
382
+ if (username !== undefined || password !== undefined) {
383
+ throw new TypeError(
384
+ "username and password have no effect with connection, which is authenticated already: nothing would send them",
385
+ );
386
+ }
387
+ return { kind: "connection" };
388
+ }
389
+ if (hostKey !== undefined) return { kind: "verifier", verify: hostKey as SshHostKeyVerifier };
390
+ if (knownHosts !== undefined) return { kind: "knownHosts", path: knownHosts };
391
+ if (insecureIgnoreHostKey !== undefined) return { kind: "insecure" };
392
+ throw new Error(
393
+ "the SSH host key has to be checked, and none of hostKey, knownHosts or insecureIgnoreHostKey was given: a query login is a credential, normally serveradmin's, and this client will not send it to a host it has not verified",
394
+ );
395
+ }
396
+
397
+ /**
398
+ * `ssh2`, or a `MissingDependencyError` naming it. Loaded on demand: it is an
399
+ * optional peer dependency, so importing it at the top of this module would
400
+ * make it a requirement of the whole entry point — including the TCP and
401
+ * WebQuery transports, which need nothing but Node.
402
+ */
403
+ async function loadSsh2(): Promise<{ Client: new () => SshClient }> {
404
+ try {
405
+ return await import("ssh2");
406
+ } catch (error) {
407
+ if (
408
+ typeof error === "object" &&
409
+ error !== null &&
410
+ (error as { code?: unknown }).code === "ERR_MODULE_NOT_FOUND"
411
+ ) {
412
+ throw new MissingDependencyError();
413
+ }
414
+ throw error;
415
+ }
416
+ }
417
+
418
+ /**
419
+ * Open a ServerQuery over SSH.
420
+ *
421
+ * SSH logs in itself, so there is no `login` command to send afterwards: the
422
+ * session is already the query account, and only `use` is left.
423
+ *
424
+ * The server's host key is checked first, by whichever of `hostKey`,
425
+ * `knownHosts` and `insecureIgnoreHostKey` was given, and the password is sent
426
+ * only to a server whose key was accepted. With none of them, or with two,
427
+ * this rejects before anything is opened or dialled.
428
+ */
429
+ export async function connectSsh(options: SshOptions): Promise<StreamTransport> {
430
+ const { host, port = 10022, signal, connection } = options;
431
+ const decision = hostKeyDecision(options);
432
+ // The login is read only where this module dials: a supplied client is
433
+ // authenticated already. Checked here, so a missing one fails before ssh2 is
434
+ // loaded or anything is opened.
435
+ const login =
436
+ connection === undefined
437
+ ? {
438
+ username: nonEmptyString(options.username, "username"),
439
+ password: nonEmptyString(options.password, "password"),
440
+ }
441
+ : undefined;
442
+ signal?.throwIfAborted();
443
+ const verify = await verifierFor(decision, host, port);
444
+ const ssh = await clientFor(connection);
445
+ signal?.throwIfAborted();
446
+
447
+ // What the caller's check said, so that the failure ssh2 reports for a
448
+ // refused key can be told from any other and carry the reason.
449
+ let refusal: Error | undefined;
450
+
451
+ return new Promise((resolve, reject) => {
452
+ let settled = false;
453
+ const failed = (error: Error): void => {
454
+ if (settled) return;
455
+ settled = true;
456
+ signal?.removeEventListener("abort", onAbort);
457
+ reject(error);
458
+ };
459
+ const onAbort = (): void => {
460
+ ssh.end();
461
+ failed(signal!.reason as Error);
462
+ };
463
+ const onAuthError = (error: Error): void => {
464
+ if (refusal !== undefined) {
465
+ failed(
466
+ new SshHostKeyError(
467
+ host,
468
+ port,
469
+ `the SSH host key of ${host}:${port} was not accepted, so no login was sent: ${refusal.message}`,
470
+ { cause: refusal },
471
+ ),
472
+ );
473
+ return;
474
+ }
475
+ // ssh2 reports a refused credential through `level`, which its types do
476
+ // not name; a wrong password is the one that leaves the client unable to
477
+ // authenticate rather than unable to reach the server.
478
+ const level = (error as { level?: unknown }).level;
479
+ failed(
480
+ level === "client-authentication"
481
+ ? new QueryAuthError(
482
+ login!.username,
483
+ `SSH authentication failed for ${login!.username}`,
484
+ { cause: error },
485
+ )
486
+ : error,
487
+ );
488
+ };
489
+ const openShell = (): void => {
490
+ ssh.shell(false, (error, channel) => {
491
+ if (error) {
492
+ ssh.end();
493
+ failed(error);
494
+ return;
495
+ }
496
+ ssh.off("error", onAuthError);
497
+ settled = true;
498
+ signal?.removeEventListener("abort", onAbort);
499
+ // `ssh2` reports a failure after the handshake on the connection, and the
500
+ // stream closes with it.
501
+ ssh.on("error", (failure) => channel.emit("error", failure));
502
+ channel.once("close", () => ssh.end());
503
+ resolve(new StreamTransport(channel, () => ssh.end()));
504
+ });
505
+ };
506
+
507
+ signal?.addEventListener("abort", onAbort, { once: true });
508
+ if (connection !== undefined) {
509
+ // A client the caller handed over is one they say is connected, and one
510
+ // from their own factory is dialled on their terms: neither is connected
511
+ // here, and neither can be a wrong credential this module knows of.
512
+ openShell();
513
+ return;
514
+ }
515
+ ssh.once("error", onAuthError);
516
+ ssh.once("ready", openShell);
517
+ ssh.connect({
518
+ host,
519
+ port,
520
+ // Set exactly when `connection` is not, and this is the dial path.
521
+ username: login!.username,
522
+ password: login!.password,
523
+ readyTimeout: 10_000,
524
+ hostVerifier: (blob) => {
525
+ const outcome = decide(verify, host, port, blob);
526
+ if (outcome !== undefined) refusal = outcome;
527
+ return outcome === undefined;
528
+ },
529
+ });
530
+ });
531
+ }
532
+
533
+ function nonEmptyString(value: unknown, name: string): string {
534
+ if (typeof value !== "string" || value === "")
535
+ throw new TypeError(`${name} is a non-empty string`);
536
+ return value;
537
+ }
538
+
539
+ /**
540
+ * The check the options chose, ready to run: the caller's own, one over the
541
+ * `known_hosts` file read now, or the one that accepts anything.
542
+ *
543
+ * A file that cannot be read or parsed is a failure to verify, and ends the
544
+ * connection as a refused key does.
545
+ */
546
+ async function verifierFor(
547
+ policy: HostKeyDecision,
548
+ host: string,
549
+ port: number,
550
+ ): Promise<SshHostKeyVerifier> {
551
+ if (policy.kind === "verifier") return policy.verify;
552
+ // `connection` never reaches a handshake of ours, so for it this is not asked.
553
+ if (policy.kind !== "knownHosts") return () => {};
554
+
555
+ let hosts: KnownHosts;
556
+ try {
557
+ hosts = KnownHosts.parse(await readFile(policy.path, "utf8"), policy.path);
558
+ } catch (error) {
559
+ throw new SshHostKeyError(
560
+ host,
561
+ port,
562
+ `the host key of ${host}:${port} cannot be checked: ${(error as Error).message}`,
563
+ { cause: error },
564
+ );
565
+ }
566
+ return (key) => hosts.verify(key);
567
+ }
568
+
569
+ /**
570
+ * Run the check on the key ssh2 found, returning why it was refused, or
571
+ * `undefined` for a key it accepted.
572
+ *
573
+ * Everything that is not a plain return counts as a refusal: a throw, naturally,
574
+ * but also an answer. A check that returns `false` was written in ssh2's
575
+ * convention and means no; one that returns `true` means yes in that
576
+ * convention, and a promise means whatever it later settles to. None of those
577
+ * is the contract, and acting on a guess at which was meant is how a refused key
578
+ * gets through, so each fails closed and says what to write instead.
579
+ */
580
+ function decide(
581
+ verify: SshHostKeyVerifier,
582
+ host: string,
583
+ port: number,
584
+ blob: Uint8Array,
585
+ ): Error | undefined {
586
+ try {
587
+ const key = new Uint8Array(blob);
588
+ const answer: unknown = verify({
589
+ host,
590
+ port,
591
+ keyType: keyTypeOf(key),
592
+ key,
593
+ fingerprint: fingerprintOf(key),
594
+ });
595
+ if (answer === undefined) return undefined;
596
+ // A rejection nobody awaits would otherwise surface as an unhandled one.
597
+ if (typeof (answer as { then?: unknown }).then === "function") {
598
+ void Promise.resolve(answer).catch(() => {});
599
+ }
600
+ return new TypeError(
601
+ "the hostKey check returned a value; it has to return nothing to accept the key and throw to refuse it, and be synchronous",
602
+ );
603
+ } catch (error) {
604
+ return error instanceof Error ? error : new Error(String(error));
605
+ }
606
+ }
607
+
608
+ /** The client to talk over: the caller's, their factory's, or a fresh `ssh2`. */
609
+ async function clientFor(connection: SshConnection | undefined): Promise<SshClient> {
610
+ if (typeof connection === "function") return connection();
611
+ if (connection !== undefined) return connection;
612
+ const { Client } = await loadSsh2();
613
+ return new Client();
614
+ }