@procedures/client 0.2.3 → 0.4.0

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 (369) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/README.md +77 -44
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/adapters/http/client/transport.d.ts.map +1 -1
  5. package/dist/adapters/http/client/transport.js +35 -3
  6. package/dist/adapters/http/client/transport.js.map +1 -1
  7. package/dist/adapters/http/client/types.d.ts +3 -2
  8. package/dist/adapters/http/client/types.d.ts.map +1 -1
  9. package/dist/adapters/http/server/transport.d.ts +2 -2
  10. package/dist/adapters/http/server/transport.d.ts.map +1 -1
  11. package/dist/adapters/http/server/transport.js +54 -22
  12. package/dist/adapters/http/server/transport.js.map +1 -1
  13. package/dist/adapters/http/shared/headers.d.ts +1 -1
  14. package/dist/adapters/http/shared/headers.d.ts.map +1 -1
  15. package/dist/adapters/http/shared/headers.js +7 -6
  16. package/dist/adapters/http/shared/headers.js.map +1 -1
  17. package/dist/adapters/local/client/transport.d.ts +3 -0
  18. package/dist/adapters/local/client/transport.d.ts.map +1 -1
  19. package/dist/adapters/local/client/transport.js +67 -6
  20. package/dist/adapters/local/client/transport.js.map +1 -1
  21. package/dist/adapters/local/client/types.d.ts +15 -0
  22. package/dist/adapters/local/client/types.d.ts.map +1 -1
  23. package/dist/adapters/local/client/types.js.map +1 -1
  24. package/dist/adapters/mock/client/transport.d.ts +3 -2
  25. package/dist/adapters/mock/client/transport.d.ts.map +1 -1
  26. package/dist/adapters/mock/client/transport.js +8 -5
  27. package/dist/adapters/mock/client/transport.js.map +1 -1
  28. package/dist/adapters/mock/client/types.d.ts +7 -6
  29. package/dist/adapters/mock/client/types.d.ts.map +1 -1
  30. package/dist/adapters/port/transport.d.ts +62 -0
  31. package/dist/adapters/port/transport.d.ts.map +1 -0
  32. package/dist/adapters/port/transport.js +94 -0
  33. package/dist/adapters/port/transport.js.map +1 -0
  34. package/dist/adapters/websocket/client/index.d.ts +1 -1
  35. package/dist/adapters/websocket/client/index.d.ts.map +1 -1
  36. package/dist/adapters/websocket/client/index.js.map +1 -1
  37. package/dist/adapters/websocket/client/node.d.ts +30 -0
  38. package/dist/adapters/websocket/client/node.d.ts.map +1 -0
  39. package/dist/adapters/websocket/client/node.js +32 -0
  40. package/dist/adapters/websocket/client/node.js.map +1 -0
  41. package/dist/adapters/websocket/client/transport.d.ts +47 -7
  42. package/dist/adapters/websocket/client/transport.d.ts.map +1 -1
  43. package/dist/adapters/websocket/client/transport.js +229 -75
  44. package/dist/adapters/websocket/client/transport.js.map +1 -1
  45. package/dist/adapters/websocket/client/types.d.ts +71 -21
  46. package/dist/adapters/websocket/client/types.d.ts.map +1 -1
  47. package/dist/adapters/websocket/client/types.js.map +1 -1
  48. package/dist/adapters/websocket/server/transport.d.ts +65 -24
  49. package/dist/adapters/websocket/server/transport.d.ts.map +1 -1
  50. package/dist/adapters/websocket/server/transport.js +192 -263
  51. package/dist/adapters/websocket/server/transport.js.map +1 -1
  52. package/dist/adapters/websocket/server/types.d.ts +20 -1
  53. package/dist/adapters/websocket/server/types.d.ts.map +1 -1
  54. package/dist/adapters/websocket/shared/rpc.d.ts +129 -0
  55. package/dist/adapters/websocket/shared/rpc.d.ts.map +1 -0
  56. package/dist/adapters/websocket/shared/rpc.js +425 -0
  57. package/dist/adapters/websocket/shared/rpc.js.map +1 -0
  58. package/dist/browser.d.ts +29 -7
  59. package/dist/browser.d.ts.map +1 -1
  60. package/dist/browser.js +26 -3
  61. package/dist/browser.js.map +1 -1
  62. package/dist/client/call-types.d.ts +10 -10
  63. package/dist/client/call-types.d.ts.map +1 -1
  64. package/dist/client/call-types.js +1 -6
  65. package/dist/client/call-types.js.map +1 -1
  66. package/dist/client/client.d.ts +6 -1
  67. package/dist/client/client.d.ts.map +1 -1
  68. package/dist/client/client.js +23 -9
  69. package/dist/client/client.js.map +1 -1
  70. package/dist/client/deadline.d.ts +38 -0
  71. package/dist/client/deadline.d.ts.map +1 -0
  72. package/dist/client/deadline.js +60 -0
  73. package/dist/client/deadline.js.map +1 -0
  74. package/dist/client/errors/factory.d.ts.map +1 -1
  75. package/dist/client/errors/factory.js +11 -7
  76. package/dist/client/errors/factory.js.map +1 -1
  77. package/dist/client/errors/http-errors.d.ts.map +1 -1
  78. package/dist/client/errors/http-errors.js +6 -0
  79. package/dist/client/errors/http-errors.js.map +1 -1
  80. package/dist/client/errors/index.d.ts +1 -1
  81. package/dist/client/errors/index.js +1 -1
  82. package/dist/client/errors/registry.d.ts +2 -1
  83. package/dist/client/errors/registry.d.ts.map +1 -1
  84. package/dist/client/errors/registry.js +72 -22
  85. package/dist/client/errors/registry.js.map +1 -1
  86. package/dist/client/index.d.ts +9 -2
  87. package/dist/client/index.d.ts.map +1 -1
  88. package/dist/client/index.js +8 -2
  89. package/dist/client/index.js.map +1 -1
  90. package/dist/client/middleware/cache.d.ts +19 -5
  91. package/dist/client/middleware/cache.d.ts.map +1 -1
  92. package/dist/client/middleware/cache.js +106 -18
  93. package/dist/client/middleware/cache.js.map +1 -1
  94. package/dist/client/middleware/circuit-breaker.d.ts +28 -10
  95. package/dist/client/middleware/circuit-breaker.d.ts.map +1 -1
  96. package/dist/client/middleware/circuit-breaker.js +35 -24
  97. package/dist/client/middleware/circuit-breaker.js.map +1 -1
  98. package/dist/client/middleware/contexts.d.ts +16 -10
  99. package/dist/client/middleware/contexts.d.ts.map +1 -1
  100. package/dist/client/middleware/contexts.js.map +1 -1
  101. package/dist/client/middleware/items.d.ts +31 -2
  102. package/dist/client/middleware/items.d.ts.map +1 -1
  103. package/dist/client/middleware/items.js +86 -6
  104. package/dist/client/middleware/items.js.map +1 -1
  105. package/dist/client/middleware/lru.d.ts +2 -0
  106. package/dist/client/middleware/lru.d.ts.map +1 -1
  107. package/dist/client/middleware/lru.js +11 -0
  108. package/dist/client/middleware/lru.js.map +1 -1
  109. package/dist/client/middleware/rate-limit.d.ts +7 -6
  110. package/dist/client/middleware/rate-limit.d.ts.map +1 -1
  111. package/dist/client/middleware/rate-limit.js +9 -7
  112. package/dist/client/middleware/rate-limit.js.map +1 -1
  113. package/dist/client/middleware/resilience.d.ts +61 -0
  114. package/dist/client/middleware/resilience.d.ts.map +1 -0
  115. package/dist/client/middleware/resilience.js +59 -0
  116. package/dist/client/middleware/resilience.js.map +1 -0
  117. package/dist/client/middleware/retry.d.ts +45 -11
  118. package/dist/client/middleware/retry.d.ts.map +1 -1
  119. package/dist/client/middleware/retry.js +102 -22
  120. package/dist/client/middleware/retry.js.map +1 -1
  121. package/dist/client/middleware/timeout.d.ts +15 -12
  122. package/dist/client/middleware/timeout.d.ts.map +1 -1
  123. package/dist/client/middleware/timeout.js +48 -46
  124. package/dist/client/middleware/timeout.js.map +1 -1
  125. package/dist/client/middleware/tracing.d.ts.map +1 -1
  126. package/dist/client/middleware/tracing.js +11 -3
  127. package/dist/client/middleware/tracing.js.map +1 -1
  128. package/dist/client/types.d.ts +9 -1
  129. package/dist/client/types.d.ts.map +1 -1
  130. package/dist/client/types.js.map +1 -1
  131. package/dist/clock.d.ts +19 -0
  132. package/dist/clock.d.ts.map +1 -0
  133. package/dist/clock.js +18 -0
  134. package/dist/clock.js.map +1 -0
  135. package/dist/duration.d.ts +37 -0
  136. package/dist/duration.d.ts.map +1 -0
  137. package/dist/duration.js +44 -0
  138. package/dist/duration.js.map +1 -0
  139. package/dist/index.d.ts +4 -0
  140. package/dist/index.d.ts.map +1 -1
  141. package/dist/index.js +4 -0
  142. package/dist/index.js.map +1 -1
  143. package/dist/machines.d.ts +90 -0
  144. package/dist/machines.d.ts.map +1 -0
  145. package/dist/machines.js +127 -0
  146. package/dist/machines.js.map +1 -0
  147. package/dist/node.d.ts +11 -0
  148. package/dist/node.d.ts.map +1 -0
  149. package/dist/node.js +10 -0
  150. package/dist/node.js.map +1 -0
  151. package/dist/procedures/capability.d.ts +3 -6
  152. package/dist/procedures/capability.d.ts.map +1 -1
  153. package/dist/procedures/capability.js +7 -8
  154. package/dist/procedures/capability.js.map +1 -1
  155. package/dist/procedures/collection/index.d.ts +3 -3
  156. package/dist/procedures/collection/index.d.ts.map +1 -1
  157. package/dist/procedures/collection/index.js +1 -1
  158. package/dist/procedures/collection/index.js.map +1 -1
  159. package/dist/procedures/collection/procedures.d.ts.map +1 -1
  160. package/dist/procedures/collection/procedures.js +49 -1
  161. package/dist/procedures/collection/procedures.js.map +1 -1
  162. package/dist/procedures/collection/schemas.d.ts +33 -0
  163. package/dist/procedures/collection/schemas.d.ts.map +1 -1
  164. package/dist/procedures/collection/schemas.js +68 -0
  165. package/dist/procedures/collection/schemas.js.map +1 -1
  166. package/dist/procedures/collection/storage.d.ts +37 -0
  167. package/dist/procedures/collection/storage.d.ts.map +1 -1
  168. package/dist/procedures/core/array.d.ts +1 -1
  169. package/dist/procedures/core/array.d.ts.map +1 -1
  170. package/dist/procedures/core/array.js +81 -29
  171. package/dist/procedures/core/array.js.map +1 -1
  172. package/dist/procedures/core/index.d.ts.map +1 -1
  173. package/dist/procedures/core/index.js +17 -2
  174. package/dist/procedures/core/index.js.map +1 -1
  175. package/dist/procedures/core/logic.js +1 -1
  176. package/dist/procedures/core/logic.js.map +1 -1
  177. package/dist/procedures/core/math.d.ts.map +1 -1
  178. package/dist/procedures/core/math.js +5 -1
  179. package/dist/procedures/core/math.js.map +1 -1
  180. package/dist/procedures/core/meta.d.ts.map +1 -1
  181. package/dist/procedures/core/meta.js +43 -8
  182. package/dist/procedures/core/meta.js.map +1 -1
  183. package/dist/procedures/core/object.d.ts.map +1 -1
  184. package/dist/procedures/core/object.js +42 -13
  185. package/dist/procedures/core/object.js.map +1 -1
  186. package/dist/procedures/core/operands.d.ts.map +1 -1
  187. package/dist/procedures/core/operands.js +6 -1
  188. package/dist/procedures/core/operands.js.map +1 -1
  189. package/dist/procedures/core/string.d.ts.map +1 -1
  190. package/dist/procedures/core/string.js +4 -2
  191. package/dist/procedures/core/string.js.map +1 -1
  192. package/dist/procedures/core/type.d.ts.map +1 -1
  193. package/dist/procedures/core/type.js +4 -2
  194. package/dist/procedures/core/type.js.map +1 -1
  195. package/dist/procedures/define-procedure.d.ts +19 -7
  196. package/dist/procedures/define-procedure.d.ts.map +1 -1
  197. package/dist/procedures/define-procedure.js +57 -17
  198. package/dist/procedures/define-procedure.js.map +1 -1
  199. package/dist/procedures/discovery.d.ts +4 -6
  200. package/dist/procedures/discovery.d.ts.map +1 -1
  201. package/dist/procedures/discovery.js +4 -6
  202. package/dist/procedures/discovery.js.map +1 -1
  203. package/dist/procedures/effects.d.ts +16 -0
  204. package/dist/procedures/effects.d.ts.map +1 -1
  205. package/dist/procedures/effects.js +33 -0
  206. package/dist/procedures/effects.js.map +1 -1
  207. package/dist/procedures/idempotency.d.ts +73 -0
  208. package/dist/procedures/idempotency.d.ts.map +1 -0
  209. package/dist/procedures/idempotency.js +131 -0
  210. package/dist/procedures/idempotency.js.map +1 -0
  211. package/dist/procedures/index.d.ts +7 -3
  212. package/dist/procedures/index.d.ts.map +1 -1
  213. package/dist/procedures/index.js +6 -2
  214. package/dist/procedures/index.js.map +1 -1
  215. package/dist/procedures/interceptors.d.ts +17 -4
  216. package/dist/procedures/interceptors.d.ts.map +1 -1
  217. package/dist/procedures/interceptors.js +18 -7
  218. package/dist/procedures/interceptors.js.map +1 -1
  219. package/dist/procedures/invoke.d.ts +15 -3
  220. package/dist/procedures/invoke.d.ts.map +1 -1
  221. package/dist/procedures/invoke.js +8 -3
  222. package/dist/procedures/invoke.js.map +1 -1
  223. package/dist/procedures/manifest.d.ts +83 -0
  224. package/dist/procedures/manifest.d.ts.map +1 -0
  225. package/dist/procedures/manifest.js +90 -0
  226. package/dist/procedures/manifest.js.map +1 -0
  227. package/dist/procedures/ref.d.ts +4 -2
  228. package/dist/procedures/ref.d.ts.map +1 -1
  229. package/dist/procedures/ref.js +2 -1
  230. package/dist/procedures/ref.js.map +1 -1
  231. package/dist/procedures/runtime-programs.d.ts +19 -0
  232. package/dist/procedures/runtime-programs.d.ts.map +1 -0
  233. package/dist/procedures/runtime-programs.js +24 -0
  234. package/dist/procedures/runtime-programs.js.map +1 -0
  235. package/dist/procedures/types.d.ts +16 -0
  236. package/dist/procedures/types.d.ts.map +1 -1
  237. package/dist/procedures/types.js.map +1 -1
  238. package/dist/protocol.d.ts +47 -0
  239. package/dist/protocol.d.ts.map +1 -0
  240. package/dist/protocol.js +68 -0
  241. package/dist/protocol.js.map +1 -0
  242. package/dist/server/host-policy.d.ts +4 -3
  243. package/dist/server/host-policy.d.ts.map +1 -1
  244. package/dist/server/host-policy.js +6 -3
  245. package/dist/server/host-policy.js.map +1 -1
  246. package/dist/server/host-procedures.d.ts +31 -0
  247. package/dist/server/host-procedures.d.ts.map +1 -0
  248. package/dist/server/host-procedures.js +75 -0
  249. package/dist/server/host-procedures.js.map +1 -0
  250. package/dist/server/index.d.ts +3 -3
  251. package/dist/server/index.d.ts.map +1 -1
  252. package/dist/server/index.js +1 -1
  253. package/dist/server/index.js.map +1 -1
  254. package/dist/server/procedure-server.d.ts +112 -5
  255. package/dist/server/procedure-server.d.ts.map +1 -1
  256. package/dist/server/procedure-server.js +273 -19
  257. package/dist/server/procedure-server.js.map +1 -1
  258. package/dist/server/request-auth.d.ts +19 -0
  259. package/dist/server/request-auth.d.ts.map +1 -0
  260. package/dist/server/request-auth.js +32 -0
  261. package/dist/server/request-auth.js.map +1 -0
  262. package/dist/server/types.d.ts +32 -0
  263. package/dist/server/types.d.ts.map +1 -1
  264. package/dist/server/types.js.map +1 -1
  265. package/dist/timers.d.ts +60 -0
  266. package/dist/timers.d.ts.map +1 -0
  267. package/dist/timers.js +33 -0
  268. package/dist/timers.js.map +1 -0
  269. package/dist/tracing/traceparent.d.ts +26 -0
  270. package/dist/tracing/traceparent.d.ts.map +1 -0
  271. package/dist/tracing/traceparent.js +53 -0
  272. package/dist/tracing/traceparent.js.map +1 -0
  273. package/package.json +11 -4
  274. package/src/adapters/http/client/transport.ts +31 -3
  275. package/src/adapters/http/client/types.ts +3 -2
  276. package/src/adapters/http/server/transport.ts +61 -26
  277. package/src/adapters/http/shared/headers.ts +7 -6
  278. package/src/adapters/local/client/transport.ts +74 -7
  279. package/src/adapters/local/client/types.ts +18 -0
  280. package/src/adapters/mock/client/transport.ts +9 -6
  281. package/src/adapters/mock/client/types.ts +7 -6
  282. package/src/adapters/port/transport.ts +124 -0
  283. package/src/adapters/websocket/client/index.ts +1 -0
  284. package/src/adapters/websocket/client/node.ts +34 -0
  285. package/src/adapters/websocket/client/transport.ts +251 -81
  286. package/src/adapters/websocket/client/types.ts +81 -21
  287. package/src/adapters/websocket/server/transport.ts +199 -301
  288. package/src/adapters/websocket/server/types.ts +20 -1
  289. package/src/adapters/websocket/shared/rpc.ts +453 -0
  290. package/src/browser.ts +51 -3
  291. package/src/client/call-types.ts +10 -14
  292. package/src/client/client.ts +28 -9
  293. package/src/client/deadline.ts +64 -0
  294. package/src/client/errors/README.md +2 -0
  295. package/src/client/errors/factory.ts +12 -8
  296. package/src/client/errors/http-errors.ts +5 -0
  297. package/src/client/errors/index.ts +1 -1
  298. package/src/client/errors/registry.ts +79 -22
  299. package/src/client/index.ts +21 -3
  300. package/src/client/middleware/cache.ts +120 -23
  301. package/src/client/middleware/circuit-breaker.ts +65 -35
  302. package/src/client/middleware/contexts.ts +17 -10
  303. package/src/client/middleware/items.ts +101 -6
  304. package/src/client/middleware/lru.ts +12 -0
  305. package/src/client/middleware/rate-limit.ts +9 -9
  306. package/src/client/middleware/resilience.ts +81 -0
  307. package/src/client/middleware/retry.ts +146 -34
  308. package/src/client/middleware/timeout.ts +57 -55
  309. package/src/client/middleware/tracing.ts +13 -4
  310. package/src/client/types.ts +9 -1
  311. package/src/clock.ts +21 -0
  312. package/src/duration.ts +56 -0
  313. package/src/index.ts +4 -0
  314. package/src/machines.ts +186 -0
  315. package/src/node.ts +11 -0
  316. package/src/procedures/capability.ts +7 -8
  317. package/src/procedures/collection/index.ts +9 -1
  318. package/src/procedures/collection/procedures.ts +57 -0
  319. package/src/procedures/collection/schemas.ts +110 -0
  320. package/src/procedures/collection/storage.ts +38 -0
  321. package/src/procedures/core/array.ts +79 -31
  322. package/src/procedures/core/index.ts +17 -2
  323. package/src/procedures/core/logic.ts +1 -1
  324. package/src/procedures/core/math.ts +5 -1
  325. package/src/procedures/core/meta.ts +45 -8
  326. package/src/procedures/core/object.ts +48 -14
  327. package/src/procedures/core/operands.ts +7 -0
  328. package/src/procedures/core/string.ts +4 -2
  329. package/src/procedures/core/type.ts +4 -2
  330. package/src/procedures/define-procedure.ts +73 -20
  331. package/src/procedures/discovery.ts +4 -6
  332. package/src/procedures/effects.ts +35 -0
  333. package/src/procedures/idempotency.ts +152 -0
  334. package/src/procedures/index.ts +18 -2
  335. package/src/procedures/interceptors.ts +28 -11
  336. package/src/procedures/invoke.ts +19 -5
  337. package/src/procedures/manifest.ts +158 -0
  338. package/src/procedures/ref.ts +4 -2
  339. package/src/procedures/runtime-programs.ts +27 -0
  340. package/src/procedures/types.ts +16 -0
  341. package/src/protocol.ts +108 -0
  342. package/src/server/host-policy.ts +8 -5
  343. package/src/server/host-procedures.ts +102 -0
  344. package/src/server/index.ts +5 -2
  345. package/src/server/procedure-server.ts +328 -12
  346. package/src/server/request-auth.ts +35 -0
  347. package/src/server/types.ts +37 -0
  348. package/src/timers.ts +82 -0
  349. package/src/tracing/traceparent.ts +61 -0
  350. package/dist/procedures/cli/announce.d.ts +0 -67
  351. package/dist/procedures/cli/announce.d.ts.map +0 -1
  352. package/dist/procedures/cli/announce.js +0 -392
  353. package/dist/procedures/cli/announce.js.map +0 -1
  354. package/dist/procedures/cli/discover.d.ts +0 -66
  355. package/dist/procedures/cli/discover.d.ts.map +0 -1
  356. package/dist/procedures/cli/discover.js +0 -310
  357. package/dist/procedures/cli/discover.js.map +0 -1
  358. package/dist/procedures/cli/index.d.ts +0 -13
  359. package/dist/procedures/cli/index.d.ts.map +0 -1
  360. package/dist/procedures/cli/index.js +0 -203
  361. package/dist/procedures/cli/index.js.map +0 -1
  362. package/dist/procedures/cli/init.d.ts +0 -17
  363. package/dist/procedures/cli/init.d.ts.map +0 -1
  364. package/dist/procedures/cli/init.js +0 -336
  365. package/dist/procedures/cli/init.js.map +0 -1
  366. package/src/procedures/cli/announce.ts +0 -470
  367. package/src/procedures/cli/discover.ts +0 -390
  368. package/src/procedures/cli/index.ts +0 -231
  369. package/src/procedures/cli/init.ts +0 -370
@@ -6,9 +6,20 @@
6
6
  */
7
7
 
8
8
  import type { Transport, Message, ResponseItem } from "../../../client/types.js";
9
- import type { WebSocketTransportOptions, WebSocketMessage, ServerRequestHandler, EventHandler } from "./types.js";
9
+ import type { WebSocketTransportOptions, WebSocketMessage, ServerRequestHandler, EventHandler, WebSocketClass } from "./types.js";
10
10
  import { WebSocketState } from "./types.js";
11
11
  import { withoutInternalKeys } from "../../metadata.js";
12
+ import { IncomingCalls } from "../shared/rpc.js";
13
+ import type { RequestHandler, ServerResponse } from "../../../server/types.js";
14
+ import { methodToPath } from "../../../server/method.js";
15
+ import { optionalMs } from "../../../duration.js";
16
+ import { PROTOCOL_VERSION, protocolVersionOf } from "../../../protocol.js";
17
+ import { TIMER_DEFAULTS } from "../../../timers.js";
18
+ import { realClock, type Cancel, type Clock } from "../../../clock.js";
19
+ import { heartbeat, type Heartbeat } from "@mark1russell7/service/heartbeat";
20
+ import { backoffDelay } from "@mark1russell7/service/backoff";
21
+ import type { MachineTransition } from "../../../machines.js";
22
+ import { WEBSOCKET_CONNECTION_MACHINE } from "../../../machines.js";
12
23
 
13
24
  /**
14
25
  * Pending request waiting for response.
@@ -64,7 +75,7 @@ interface PendingRequest {
64
75
  /** The response items of the request, in order */
65
76
  queue: ItemQueue<ResponseItem<unknown>>;
66
77
  /** The request timeout: it runs until the first frame arrives */
67
- timeout?: ReturnType<typeof setTimeout> | undefined;
78
+ timeout?: Cancel | undefined;
68
79
  }
69
80
 
70
81
  /**
@@ -85,6 +96,23 @@ interface PendingRequest {
85
96
  * const client = new Client({ transport });
86
97
  * ```
87
98
  */
99
+ /** The `readyState` of an open socket. The global `WebSocket` and the `ws` package use the same number. */
100
+ const OPEN = 1;
101
+
102
+ /**
103
+ * The error handler of a socket that the transport left. A `WebSocket` of the `ws` package that
104
+ * closes during its handshake emits an error. Without a handler, that error ends the process.
105
+ */
106
+ const ignoreError = (): void => undefined;
107
+
108
+ /** This function removes the handlers of the transport from a socket, and keeps `ignoreError`. */
109
+ function detach(ws: WebSocket): void {
110
+ ws.onopen = null;
111
+ ws.onmessage = null;
112
+ ws.onclose = null;
113
+ ws.onerror = ignoreError;
114
+ }
115
+
88
116
  function errorItem<TRes>(id: string, code: string, message: string, retryable: boolean): ResponseItem<TRes> {
89
117
  return { id, status: { type: "error", code, message, retryable }, payload: null as TRes, metadata: {} };
90
118
  }
@@ -93,6 +121,59 @@ function abortedItem<TRes>(id: string): ResponseItem<TRes> {
93
121
  return errorItem<TRes>(id, "ABORTED", "Request was aborted", false);
94
122
  }
95
123
 
124
+ /**
125
+ * The handler of the calls of the server without a `serve` option: the older `onServerRequest`
126
+ * function (it gets the path and the input), or else a `NOT_FOUND` answer.
127
+ */
128
+ function legacyHandler(onServerRequest: ServerRequestHandler | undefined): RequestHandler {
129
+ return {
130
+ async handle(request): Promise<ServerResponse> {
131
+ if (!onServerRequest) {
132
+ return {
133
+ id: request.id,
134
+ status: { type: "error", code: "NOT_FOUND", message: "This client serves no procedures", retryable: false },
135
+ metadata: {},
136
+ };
137
+ }
138
+ try {
139
+ const payload = await onServerRequest(methodToPath(request.method), request.payload);
140
+ return { id: request.id, status: { type: "success", code: 200 }, payload, metadata: {} };
141
+ } catch (error) {
142
+ return {
143
+ id: request.id,
144
+ status: { type: "error", code: "HANDLER_ERROR", message: error instanceof Error ? error.message : String(error), retryable: false },
145
+ metadata: {},
146
+ };
147
+ }
148
+ },
149
+ };
150
+ }
151
+
152
+ /**
153
+ * The delay before a reconnect try (from 1): an exponential backoff up to `maxDelay`, with a
154
+ * random part. With `jitter: 1` (full jitter, the default), the delay is a random time from 0 to
155
+ * the backoff, so the clients of a server that restarted do not come back at the same moments
156
+ * (roadmap R3). The backoff is `backoffDelay` of `@mark1russell7/service/backoff` (roadmap R37).
157
+ */
158
+ export function reconnectDelay(
159
+ attempt: number,
160
+ options: { initialDelay: number; backoffMultiplier: number; maxDelay: number; jitter: number },
161
+ random: () => number = Math.random,
162
+ ): number {
163
+ return Math.round(
164
+ backoffDelay(
165
+ {
166
+ base: options.initialDelay,
167
+ factor: options.backoffMultiplier,
168
+ cap: options.maxDelay,
169
+ jitter: { kind: "subtract", fraction: options.jitter },
170
+ },
171
+ attempt,
172
+ random,
173
+ ),
174
+ );
175
+ }
176
+
96
177
  export class WebSocketTransport implements Transport {
97
178
  readonly name = "websocket";
98
179
 
@@ -106,6 +187,7 @@ export class WebSocketTransport implements Transport {
106
187
  initialDelay: number;
107
188
  maxDelay: number;
108
189
  backoffMultiplier: number;
190
+ jitter: number;
109
191
  };
110
192
  connectionTimeout: number;
111
193
  requestTimeout: number;
@@ -114,12 +196,14 @@ export class WebSocketTransport implements Transport {
114
196
  enabled: boolean;
115
197
  interval: number;
116
198
  timeout: number;
199
+ misses: number;
117
200
  };
118
201
  onConnect: (() => void) | undefined;
119
202
  onDisconnect: ((reason?: string) => void) | undefined;
120
203
  onReconnecting: ((attempt: number) => void) | undefined;
121
204
  onError: ((error: Error) => void) | undefined;
122
205
  onServerRequest: ServerRequestHandler | undefined;
206
+ serve: RequestHandler | undefined;
123
207
  onEvent: EventHandler | undefined;
124
208
  };
125
209
  /** The requests in progress, by wire id */
@@ -135,9 +219,21 @@ export class WebSocketTransport implements Transport {
135
219
  /** Set by close(): the connection is being closed on purpose, so it must not reconnect */
136
220
  private closing = false;
137
221
  private reconnectAttempts = 0;
138
- private reconnectTimer: ReturnType<typeof setTimeout> | undefined = undefined;
139
- private heartbeatTimer: ReturnType<typeof setTimeout> | undefined = undefined;
140
- private heartbeatTimeout: ReturnType<typeof setTimeout> | undefined = undefined;
222
+ private reconnectTimer: Cancel | undefined = undefined;
223
+ /** The heartbeat of the connection: `heartbeat` of `@mark1russell7/service/heartbeat` (roadmap R37) */
224
+ private heartbeat: Heartbeat | undefined = undefined;
225
+ /** The clock of each timer of the transport (roadmap R35) */
226
+ private readonly clock: Clock;
227
+ /** The class of the sockets, or undefined for the global `WebSocket` (finding of roadmap R36) */
228
+ private readonly webSocketClass: WebSocketClass | undefined;
229
+ /** The observer of the changes of the state (roadmap R35) */
230
+ private readonly transitionHandler: ((transition: MachineTransition) => void) | undefined;
231
+ /** The protocol version of the server, from its hello, or undefined (an older server) (R25) */
232
+ private serverProtocol: number | undefined;
233
+ /** The calls that the server sends on the current socket, served by `serve` (roadmap R24) */
234
+ private incoming: IncomingCalls | undefined;
235
+ /** The time limit of the handshake of the socket that opens now */
236
+ private handshakeTimer: Cancel | undefined = undefined;
141
237
 
142
238
  constructor(options: WebSocketTransportOptions) {
143
239
  this.options = {
@@ -145,40 +241,54 @@ export class WebSocketTransport implements Transport {
145
241
  reconnect: {
146
242
  enabled: options.reconnect?.enabled ?? true,
147
243
  maxAttempts: options.reconnect?.maxAttempts ?? Infinity,
148
- initialDelay: options.reconnect?.initialDelay ?? 1000,
149
- maxDelay: options.reconnect?.maxDelay ?? 30000,
244
+ initialDelay: optionalMs(options.reconnect?.initialDelay, "reconnect.initialDelay") ?? TIMER_DEFAULTS.webSocketClient.reconnectInitialDelayMs,
245
+ maxDelay: optionalMs(options.reconnect?.maxDelay, "reconnect.maxDelay") ?? TIMER_DEFAULTS.webSocketClient.reconnectMaxDelayMs,
150
246
  backoffMultiplier: options.reconnect?.backoffMultiplier ?? 1.5,
247
+ jitter: Math.min(1, Math.max(0, options.reconnect?.jitter ?? 1)),
151
248
  },
152
- connectionTimeout: options.connectionTimeout ?? 10000,
153
- requestTimeout: options.requestTimeout ?? 30000,
249
+ connectionTimeout: optionalMs(options.connectionTimeout, "connectionTimeout") ?? TIMER_DEFAULTS.webSocketClient.connectionTimeoutMs,
250
+ requestTimeout: optionalMs(options.requestTimeout, "requestTimeout") ?? TIMER_DEFAULTS.webSocketClient.requestTimeoutMs,
154
251
  streamWindow: Math.max(2, options.streamWindow ?? 64),
155
252
  heartbeat: {
156
253
  enabled: options.heartbeat?.enabled ?? true,
157
- interval: options.heartbeat?.interval ?? 30000,
158
- timeout: options.heartbeat?.timeout ?? 5000,
254
+ interval: optionalMs(options.heartbeat?.interval, "heartbeat.interval") ?? TIMER_DEFAULTS.webSocketClient.heartbeatIntervalMs,
255
+ timeout: optionalMs(options.heartbeat?.timeout, "heartbeat.timeout") ?? TIMER_DEFAULTS.webSocketClient.heartbeatTimeoutMs,
256
+ misses: Math.max(1, options.heartbeat?.misses ?? TIMER_DEFAULTS.webSocketClient.heartbeatMisses),
159
257
  },
160
258
  onConnect: options.onConnect,
161
259
  onDisconnect: options.onDisconnect,
162
260
  onReconnecting: options.onReconnecting,
163
261
  onError: options.onError,
164
262
  onServerRequest: options.onServerRequest,
263
+ serve: options.serve,
165
264
  onEvent: options.onEvent,
166
265
  };
266
+ this.clock = options.clock ?? realClock;
267
+ this.webSocketClass = options.webSocket;
268
+ this.transitionHandler = options.onTransition;
167
269
 
168
270
  // Auto-connect
169
271
  this.connect();
170
272
  }
171
273
 
172
- /** Change the state, and tell the callers that wait for a connection. */
173
- private setState(state: WebSocketState): void {
274
+ /**
275
+ * Change the state, and tell the callers that wait for a connection. `on` is the event of
276
+ * `WEBSOCKET_CONNECTION_MACHINE`: a change goes to `onTransition` (roadmap R35).
277
+ */
278
+ private setState(state: WebSocketState, on: string): void {
279
+ const from = this.state;
174
280
  this.state = state;
281
+ if (from !== state) {
282
+ this.transitionHandler?.({ machine: WEBSOCKET_CONNECTION_MACHINE.id, instance: this.name, from, on, to: state, at: this.clock.now() });
283
+ }
175
284
  for (const listener of [...this.stateListeners]) listener();
176
285
  }
177
286
 
178
287
  /**
179
288
  * Connect to WebSocket server.
180
289
  */
181
- private connect(): void {
290
+ /** `on` is the event of the machine: the first connect, or the end of a backoff. */
291
+ private connect(on: "connect" | "reconnect-due" = "connect"): void {
182
292
  if (this.closing) {
183
293
  return;
184
294
  }
@@ -186,17 +296,36 @@ export class WebSocketTransport implements Transport {
186
296
  return;
187
297
  }
188
298
 
189
- this.setState(WebSocketState.CONNECTING);
299
+ this.setState(WebSocketState.CONNECTING, on);
190
300
 
191
301
  try {
192
- const ws = new WebSocket(this.options.url);
302
+ const WebSocketClass = (this.webSocketClass ?? WebSocket) as typeof WebSocket;
303
+ const ws = new WebSocketClass(this.options.url);
193
304
  this.ws = ws;
305
+ // The calls of the server on this socket (roadmap R24)
306
+ this.incoming = this.servedCallsOf(ws);
307
+
308
+ // The handshake has a time limit: an address that never answers does not keep the socket
309
+ // in CONNECTING (roadmap R3)
310
+ this.clearHandshakeTimer();
311
+ this.handshakeTimer = this.clock.after(this.options.connectionTimeout, () => {
312
+ this.handshakeTimer = undefined;
313
+ if (this.ws === ws && this.state === WebSocketState.CONNECTING) {
314
+ console.error(`[${this.name}] Handshake timeout after ${this.options.connectionTimeout}ms`);
315
+ this.dropConnection("Handshake timeout");
316
+ }
317
+ });
194
318
 
195
319
  // Connection opened
196
320
  ws.onopen = () => {
197
321
  if (this.ws !== ws) return;
322
+ this.clearHandshakeTimer();
198
323
  this.reconnectAttempts = 0;
199
- this.setState(WebSocketState.CONNECTED);
324
+ // The protocol version of this client (roadmap R25). An older server answers with an
325
+ // error frame, which the client ignores.
326
+ this.serverProtocol = undefined;
327
+ ws.send(JSON.stringify({ id: "hello", type: "hello", protocol: PROTOCOL_VERSION }));
328
+ this.setState(WebSocketState.CONNECTED, "open");
200
329
  console.error(`[${this.name}] Connected to ${this.options.url}`);
201
330
 
202
331
  if (this.options.onConnect) {
@@ -235,6 +364,10 @@ export class WebSocketTransport implements Transport {
235
364
  if (this.options.onError) {
236
365
  this.options.onError(error);
237
366
  }
367
+ // A failed handshake: the WebSocket of Node 22 gives no close event after this error, and
368
+ // stays in CONNECTING. The transport closes it now, so the reconnect starts at once
369
+ // (roadmap R30: before, each try waited for the time limit of the handshake).
370
+ if (this.state === WebSocketState.CONNECTING) this.dropConnection("Connection failed");
238
371
  };
239
372
  } catch (error) {
240
373
  console.error(`[${this.name}] Failed to create WebSocket:`, error);
@@ -252,12 +385,13 @@ export class WebSocketTransport implements Transport {
252
385
  const ws = this.ws;
253
386
  this.ws = null;
254
387
  if (ws) {
255
- ws.onopen = null;
256
- ws.onmessage = null;
257
- ws.onclose = null;
258
- ws.onerror = null;
388
+ detach(ws);
259
389
  try {
260
- ws.close();
390
+ // A socket of the ws package ends at once. close() waits for the answer of a peer that can
391
+ // be dead (finding of roadmap R36).
392
+ const terminate = (ws as { terminate?: () => void }).terminate;
393
+ if (typeof terminate === "function") terminate.call(ws);
394
+ else ws.close();
261
395
  } catch {
262
396
  // The socket is unusable: nothing more to do
263
397
  }
@@ -269,9 +403,7 @@ export class WebSocketTransport implements Transport {
269
403
  private failPending(reason: string): void {
270
404
  for (const pending of this.pendingRequests.values()) {
271
405
  pending.queue.fail(new Error(reason));
272
- if (pending.timeout) {
273
- clearTimeout(pending.timeout);
274
- }
406
+ pending.timeout?.();
275
407
  }
276
408
  this.pendingRequests.clear();
277
409
  }
@@ -281,11 +413,14 @@ export class WebSocketTransport implements Transport {
281
413
  */
282
414
  private handleClose(reason?: string): void {
283
415
  this.stopHeartbeat();
416
+ this.incoming?.abortAll();
417
+ this.incoming = undefined;
418
+ this.clearHandshakeTimer();
284
419
 
285
420
  // An intentional close() must not reconnect (BUGS-2026-07 H6)
286
421
  const reconnect = this.options.reconnect;
287
422
  const willReconnect = !this.closing && reconnect.enabled && this.reconnectAttempts < reconnect.maxAttempts;
288
- this.setState(willReconnect ? WebSocketState.RECONNECTING : WebSocketState.DISCONNECTED);
423
+ this.setState(willReconnect ? WebSocketState.RECONNECTING : WebSocketState.DISCONNECTED, willReconnect ? "drop" : "give-up");
289
424
 
290
425
  console.error(`[${this.name}] Disconnected${reason ? `: ${reason}` : ""}`);
291
426
 
@@ -305,17 +440,13 @@ export class WebSocketTransport implements Transport {
305
440
  */
306
441
  private scheduleReconnect(): void {
307
442
  if (this.reconnectTimer) {
308
- clearTimeout(this.reconnectTimer);
443
+ this.reconnectTimer();
309
444
  }
310
445
 
311
446
  this.reconnectAttempts++;
312
- this.setState(WebSocketState.RECONNECTING);
447
+ this.setState(WebSocketState.RECONNECTING, "drop");
313
448
 
314
- const reconnect = this.options.reconnect;
315
- const delay = Math.min(
316
- reconnect.initialDelay * Math.pow(reconnect.backoffMultiplier, this.reconnectAttempts - 1),
317
- reconnect.maxDelay
318
- );
449
+ const delay = reconnectDelay(this.reconnectAttempts, this.options.reconnect);
319
450
 
320
451
  console.error(`[${this.name}] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
321
452
 
@@ -323,64 +454,95 @@ export class WebSocketTransport implements Transport {
323
454
  this.options.onReconnecting(this.reconnectAttempts);
324
455
  }
325
456
 
326
- this.reconnectTimer = setTimeout(() => {
457
+ this.reconnectTimer = this.clock.after(delay, () => {
327
458
  this.reconnectTimer = undefined;
328
459
  // connect() returns at once in RECONNECTING only for CONNECTING/CONNECTED
329
- this.connect();
330
- }, delay);
460
+ this.connect("reconnect-due");
461
+ });
331
462
  }
332
463
 
333
464
  /**
334
- * Start heartbeat to keep connection alive.
465
+ * Start the heartbeat of the connection: a ping each interval on the clock, one ping at a time.
466
+ * A ping without a pong at its timeout is a miss. The connection drops after `misses` misses in
467
+ * sequence. A ping whose send throws is a miss too.
335
468
  */
336
469
  private startHeartbeat(): void {
337
470
  this.stopHeartbeat();
338
-
339
- this.heartbeatTimer = setInterval(() => {
340
- // One ping at a time: a new ping must not replace the timeout of an unanswered one
341
- if (this.state === WebSocketState.CONNECTED && this.ws && !this.heartbeatTimeout) {
342
- // Send ping
471
+ this.heartbeat = heartbeat({
472
+ interval: this.options.heartbeat.interval,
473
+ timeout: this.options.heartbeat.timeout,
474
+ misses: this.options.heartbeat.misses,
475
+ clock: this.clock,
476
+ due: () => this.state === WebSocketState.CONNECTED && this.ws !== null,
477
+ ping: () => {
343
478
  const ping: WebSocketMessage = {
344
479
  id: `ping-${Date.now()}`,
345
480
  type: "ping",
346
481
  };
347
- this.ws.send(JSON.stringify(ping));
348
-
349
- // Set timeout for pong
350
- this.heartbeatTimeout = setTimeout(() => {
351
- this.heartbeatTimeout = undefined;
352
- console.warn(`[${this.name}] Heartbeat timeout - dropping connection`);
353
- this.dropConnection("Heartbeat timeout");
354
- }, this.options.heartbeat.timeout);
355
- }
356
- }, this.options.heartbeat.interval);
482
+ this.ws?.send(JSON.stringify(ping));
483
+ },
484
+ onDead: () => {
485
+ console.warn(`[${this.name}] Heartbeat timeout - dropping connection`);
486
+ this.dropConnection("Heartbeat timeout");
487
+ },
488
+ });
357
489
  }
358
490
 
359
- /**
360
- * Stop heartbeat.
361
- */
362
- private stopHeartbeat(): void {
363
- if (this.heartbeatTimer) {
364
- clearInterval(this.heartbeatTimer);
365
- this.heartbeatTimer = undefined;
366
- }
367
- if (this.heartbeatTimeout) {
368
- clearTimeout(this.heartbeatTimeout);
369
- this.heartbeatTimeout = undefined;
491
+ /** Stop the time limit of the handshake. */
492
+ private clearHandshakeTimer(): void {
493
+ if (this.handshakeTimer) {
494
+ this.handshakeTimer();
495
+ this.handshakeTimer = undefined;
370
496
  }
371
497
  }
372
498
 
499
+ /** Stop the heartbeat: its ticks, its timeout and its count of misses. */
500
+ private stopHeartbeat(): void {
501
+ this.heartbeat?.stop();
502
+ this.heartbeat = undefined;
503
+ }
504
+
373
505
  /**
374
506
  * Handle incoming message.
375
507
  */
508
+ /**
509
+ * The calls of the server on a socket, served by the `serve` option (or by `onServerRequest`, or
510
+ * with `NOT_FOUND`), with the same frames as the calls of the client (roadmap R24).
511
+ */
512
+ private servedCallsOf(ws: WebSocket): IncomingCalls {
513
+ const handler = this.options.serve ?? legacyHandler(this.options.onServerRequest);
514
+ return new IncomingCalls({
515
+ handler,
516
+ send: (frame) => ws.send(JSON.stringify(frame)),
517
+ isOpen: () => this.ws === ws && ws.readyState === OPEN,
518
+ // A call of the server that this client opened: the client trusts it, so it has no
519
+ // credentials (a ProcedureServer applies no token to it)
520
+ requestOf: (frame, signal) => ({
521
+ id: frame.id,
522
+ method: frame.method!,
523
+ payload: frame.payload === undefined ? {} : frame.payload,
524
+ metadata: withoutInternalKeys(frame.metadata),
525
+ signal,
526
+ }),
527
+ });
528
+ }
529
+
376
530
  private handleMessage(message: WebSocketMessage): void {
531
+ // The hello of the server, or the error of an older server that does not know it (R25)
532
+ if (message.id === "hello" && (message.type === "hello" || message.type === "error")) {
533
+ if (message.type === "hello") this.serverProtocol = protocolVersionOf(message.protocol);
534
+ else if (message.error?.code === "PROTOCOL_VERSION") this.options.onError?.(new Error(message.error.message));
535
+ return;
536
+ }
537
+ // A call of the server, its cancel or its credit (roadmap R24)
538
+ if ((message.type === "request" || message.type === "cancel" || message.type === "credit") && this.incoming?.receive(message)) {
539
+ return;
540
+ }
541
+
377
542
 
378
543
  // Handle pong (heartbeat response)
379
544
  if (message.type === "pong") {
380
- if (this.heartbeatTimeout) {
381
- clearTimeout(this.heartbeatTimeout);
382
- this.heartbeatTimeout = undefined;
383
- }
545
+ this.heartbeat?.answer();
384
546
  return;
385
547
  }
386
548
 
@@ -421,7 +583,7 @@ export class WebSocketTransport implements Transport {
421
583
 
422
584
  // The first frame arrived: the request timeout ends. A stream can stay open longer.
423
585
  if (pending.timeout) {
424
- clearTimeout(pending.timeout);
586
+ pending.timeout();
425
587
  pending.timeout = undefined;
426
588
  }
427
589
 
@@ -539,7 +701,7 @@ export class WebSocketTransport implements Transport {
539
701
 
540
702
  const queue = new ItemQueue<ResponseItem<unknown>>();
541
703
  const pending: PendingRequest = { messageId: message.id, queue };
542
- pending.timeout = setTimeout(() => {
704
+ pending.timeout = this.clock.after(this.options.requestTimeout, () => {
543
705
  if (this.pendingRequests.get(wireId) !== pending) return;
544
706
  this.pendingRequests.delete(wireId);
545
707
  this.sendCancel(wireId);
@@ -548,13 +710,13 @@ export class WebSocketTransport implements Transport {
548
710
  // so a retry middleware must not repeat it (deep dive TRN-8)
549
711
  queue.push(errorItem(message.id, "TIMEOUT", `Request timeout after ${this.options.requestTimeout}ms`, false));
550
712
  queue.end();
551
- }, this.options.requestTimeout);
713
+ });
552
714
  this.pendingRequests.set(wireId, pending);
553
715
 
554
716
  const onAbort = (): void => {
555
717
  if (this.pendingRequests.get(wireId) !== pending) return;
556
718
  this.pendingRequests.delete(wireId);
557
- if (pending.timeout) clearTimeout(pending.timeout);
719
+ pending.timeout?.();
558
720
  this.sendCancel(wireId);
559
721
  queue.push(abortedItem(message.id));
560
722
  queue.end();
@@ -579,7 +741,7 @@ export class WebSocketTransport implements Transport {
579
741
  }
580
742
  } finally {
581
743
  message.signal?.removeEventListener("abort", onAbort);
582
- if (pending.timeout) clearTimeout(pending.timeout);
744
+ pending.timeout?.();
583
745
  if (this.pendingRequests.get(wireId) === pending) {
584
746
  // The reader stopped before the end: the server stops the stream
585
747
  this.pendingRequests.delete(wireId);
@@ -619,7 +781,7 @@ export class WebSocketTransport implements Transport {
619
781
 
620
782
  return new Promise((resolve, reject) => {
621
783
  const finish = (error?: Error): void => {
622
- clearTimeout(timeout);
784
+ timeout();
623
785
  this.stateListeners.delete(check);
624
786
  signal?.removeEventListener("abort", onAbort);
625
787
  if (error) reject(error);
@@ -631,7 +793,7 @@ export class WebSocketTransport implements Transport {
631
793
  };
632
794
  // The caller aborted: send() then gives the ABORTED item
633
795
  const onAbort = (): void => finish();
634
- const timeout = setTimeout(() => finish(new Error("Connection timeout")), this.options.connectionTimeout);
796
+ const timeout = this.clock.after(this.options.connectionTimeout, () => finish(new Error("Connection timeout")));
635
797
  this.stateListeners.add(check);
636
798
  signal?.addEventListener("abort", onAbort, { once: true });
637
799
  });
@@ -691,11 +853,14 @@ export class WebSocketTransport implements Transport {
691
853
  */
692
854
  async close(): Promise<void> {
693
855
  this.closing = true;
694
- this.setState(WebSocketState.DISCONNECTING);
856
+ this.setState(WebSocketState.DISCONNECTING, "close");
695
857
  this.stopHeartbeat();
858
+ this.clearHandshakeTimer();
859
+ this.incoming?.abortAll();
860
+ this.incoming = undefined;
696
861
 
697
862
  if (this.reconnectTimer) {
698
- clearTimeout(this.reconnectTimer);
863
+ this.reconnectTimer();
699
864
  this.reconnectTimer = undefined;
700
865
  }
701
866
 
@@ -703,20 +868,25 @@ export class WebSocketTransport implements Transport {
703
868
  if (this.ws) {
704
869
  const ws = this.ws;
705
870
  this.ws = null;
706
- ws.onopen = null;
707
- ws.onmessage = null;
708
- ws.onclose = null;
709
- ws.onerror = null;
871
+ detach(ws);
710
872
  ws.close();
711
873
  }
712
874
 
713
875
  this.failPending("WebSocket connection closed");
714
- this.setState(WebSocketState.DISCONNECTED);
876
+ this.setState(WebSocketState.DISCONNECTED, "closed");
715
877
  if (hadSocket && this.options.onDisconnect) {
716
878
  this.options.onDisconnect("closed");
717
879
  }
718
880
  }
719
881
 
882
+ /**
883
+ * The protocol version of the server: from its hello, or undefined before the hello or for a
884
+ * server older than protocol v1 (roadmap R25).
885
+ */
886
+ getServerProtocol(): number | undefined {
887
+ return this.serverProtocol;
888
+ }
889
+
720
890
  /**
721
891
  * Get current connection state.
722
892
  */