@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.
- package/CHANGELOG.md +94 -0
- package/README.md +77 -44
- package/dist/.tsbuildinfo +1 -1
- package/dist/adapters/http/client/transport.d.ts.map +1 -1
- package/dist/adapters/http/client/transport.js +35 -3
- package/dist/adapters/http/client/transport.js.map +1 -1
- package/dist/adapters/http/client/types.d.ts +3 -2
- package/dist/adapters/http/client/types.d.ts.map +1 -1
- package/dist/adapters/http/server/transport.d.ts +2 -2
- package/dist/adapters/http/server/transport.d.ts.map +1 -1
- package/dist/adapters/http/server/transport.js +54 -22
- package/dist/adapters/http/server/transport.js.map +1 -1
- package/dist/adapters/http/shared/headers.d.ts +1 -1
- package/dist/adapters/http/shared/headers.d.ts.map +1 -1
- package/dist/adapters/http/shared/headers.js +7 -6
- package/dist/adapters/http/shared/headers.js.map +1 -1
- package/dist/adapters/local/client/transport.d.ts +3 -0
- package/dist/adapters/local/client/transport.d.ts.map +1 -1
- package/dist/adapters/local/client/transport.js +67 -6
- package/dist/adapters/local/client/transport.js.map +1 -1
- package/dist/adapters/local/client/types.d.ts +15 -0
- package/dist/adapters/local/client/types.d.ts.map +1 -1
- package/dist/adapters/local/client/types.js.map +1 -1
- package/dist/adapters/mock/client/transport.d.ts +3 -2
- package/dist/adapters/mock/client/transport.d.ts.map +1 -1
- package/dist/adapters/mock/client/transport.js +8 -5
- package/dist/adapters/mock/client/transport.js.map +1 -1
- package/dist/adapters/mock/client/types.d.ts +7 -6
- package/dist/adapters/mock/client/types.d.ts.map +1 -1
- package/dist/adapters/port/transport.d.ts +62 -0
- package/dist/adapters/port/transport.d.ts.map +1 -0
- package/dist/adapters/port/transport.js +94 -0
- package/dist/adapters/port/transport.js.map +1 -0
- package/dist/adapters/websocket/client/index.d.ts +1 -1
- package/dist/adapters/websocket/client/index.d.ts.map +1 -1
- package/dist/adapters/websocket/client/index.js.map +1 -1
- package/dist/adapters/websocket/client/node.d.ts +30 -0
- package/dist/adapters/websocket/client/node.d.ts.map +1 -0
- package/dist/adapters/websocket/client/node.js +32 -0
- package/dist/adapters/websocket/client/node.js.map +1 -0
- package/dist/adapters/websocket/client/transport.d.ts +47 -7
- package/dist/adapters/websocket/client/transport.d.ts.map +1 -1
- package/dist/adapters/websocket/client/transport.js +229 -75
- package/dist/adapters/websocket/client/transport.js.map +1 -1
- package/dist/adapters/websocket/client/types.d.ts +71 -21
- package/dist/adapters/websocket/client/types.d.ts.map +1 -1
- package/dist/adapters/websocket/client/types.js.map +1 -1
- package/dist/adapters/websocket/server/transport.d.ts +65 -24
- package/dist/adapters/websocket/server/transport.d.ts.map +1 -1
- package/dist/adapters/websocket/server/transport.js +192 -263
- package/dist/adapters/websocket/server/transport.js.map +1 -1
- package/dist/adapters/websocket/server/types.d.ts +20 -1
- package/dist/adapters/websocket/server/types.d.ts.map +1 -1
- package/dist/adapters/websocket/shared/rpc.d.ts +129 -0
- package/dist/adapters/websocket/shared/rpc.d.ts.map +1 -0
- package/dist/adapters/websocket/shared/rpc.js +425 -0
- package/dist/adapters/websocket/shared/rpc.js.map +1 -0
- package/dist/browser.d.ts +29 -7
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +26 -3
- package/dist/browser.js.map +1 -1
- package/dist/client/call-types.d.ts +10 -10
- package/dist/client/call-types.d.ts.map +1 -1
- package/dist/client/call-types.js +1 -6
- package/dist/client/call-types.js.map +1 -1
- package/dist/client/client.d.ts +6 -1
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/client.js +23 -9
- package/dist/client/client.js.map +1 -1
- package/dist/client/deadline.d.ts +38 -0
- package/dist/client/deadline.d.ts.map +1 -0
- package/dist/client/deadline.js +60 -0
- package/dist/client/deadline.js.map +1 -0
- package/dist/client/errors/factory.d.ts.map +1 -1
- package/dist/client/errors/factory.js +11 -7
- package/dist/client/errors/factory.js.map +1 -1
- package/dist/client/errors/http-errors.d.ts.map +1 -1
- package/dist/client/errors/http-errors.js +6 -0
- package/dist/client/errors/http-errors.js.map +1 -1
- package/dist/client/errors/index.d.ts +1 -1
- package/dist/client/errors/index.js +1 -1
- package/dist/client/errors/registry.d.ts +2 -1
- package/dist/client/errors/registry.d.ts.map +1 -1
- package/dist/client/errors/registry.js +72 -22
- package/dist/client/errors/registry.js.map +1 -1
- package/dist/client/index.d.ts +9 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +8 -2
- package/dist/client/index.js.map +1 -1
- package/dist/client/middleware/cache.d.ts +19 -5
- package/dist/client/middleware/cache.d.ts.map +1 -1
- package/dist/client/middleware/cache.js +106 -18
- package/dist/client/middleware/cache.js.map +1 -1
- package/dist/client/middleware/circuit-breaker.d.ts +28 -10
- package/dist/client/middleware/circuit-breaker.d.ts.map +1 -1
- package/dist/client/middleware/circuit-breaker.js +35 -24
- package/dist/client/middleware/circuit-breaker.js.map +1 -1
- package/dist/client/middleware/contexts.d.ts +16 -10
- package/dist/client/middleware/contexts.d.ts.map +1 -1
- package/dist/client/middleware/contexts.js.map +1 -1
- package/dist/client/middleware/items.d.ts +31 -2
- package/dist/client/middleware/items.d.ts.map +1 -1
- package/dist/client/middleware/items.js +86 -6
- package/dist/client/middleware/items.js.map +1 -1
- package/dist/client/middleware/lru.d.ts +2 -0
- package/dist/client/middleware/lru.d.ts.map +1 -1
- package/dist/client/middleware/lru.js +11 -0
- package/dist/client/middleware/lru.js.map +1 -1
- package/dist/client/middleware/rate-limit.d.ts +7 -6
- package/dist/client/middleware/rate-limit.d.ts.map +1 -1
- package/dist/client/middleware/rate-limit.js +9 -7
- package/dist/client/middleware/rate-limit.js.map +1 -1
- package/dist/client/middleware/resilience.d.ts +61 -0
- package/dist/client/middleware/resilience.d.ts.map +1 -0
- package/dist/client/middleware/resilience.js +59 -0
- package/dist/client/middleware/resilience.js.map +1 -0
- package/dist/client/middleware/retry.d.ts +45 -11
- package/dist/client/middleware/retry.d.ts.map +1 -1
- package/dist/client/middleware/retry.js +102 -22
- package/dist/client/middleware/retry.js.map +1 -1
- package/dist/client/middleware/timeout.d.ts +15 -12
- package/dist/client/middleware/timeout.d.ts.map +1 -1
- package/dist/client/middleware/timeout.js +48 -46
- package/dist/client/middleware/timeout.js.map +1 -1
- package/dist/client/middleware/tracing.d.ts.map +1 -1
- package/dist/client/middleware/tracing.js +11 -3
- package/dist/client/middleware/tracing.js.map +1 -1
- package/dist/client/types.d.ts +9 -1
- package/dist/client/types.d.ts.map +1 -1
- package/dist/client/types.js.map +1 -1
- package/dist/clock.d.ts +19 -0
- package/dist/clock.d.ts.map +1 -0
- package/dist/clock.js +18 -0
- package/dist/clock.js.map +1 -0
- package/dist/duration.d.ts +37 -0
- package/dist/duration.d.ts.map +1 -0
- package/dist/duration.js +44 -0
- package/dist/duration.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/machines.d.ts +90 -0
- package/dist/machines.d.ts.map +1 -0
- package/dist/machines.js +127 -0
- package/dist/machines.js.map +1 -0
- package/dist/node.d.ts +11 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +10 -0
- package/dist/node.js.map +1 -0
- package/dist/procedures/capability.d.ts +3 -6
- package/dist/procedures/capability.d.ts.map +1 -1
- package/dist/procedures/capability.js +7 -8
- package/dist/procedures/capability.js.map +1 -1
- package/dist/procedures/collection/index.d.ts +3 -3
- package/dist/procedures/collection/index.d.ts.map +1 -1
- package/dist/procedures/collection/index.js +1 -1
- package/dist/procedures/collection/index.js.map +1 -1
- package/dist/procedures/collection/procedures.d.ts.map +1 -1
- package/dist/procedures/collection/procedures.js +49 -1
- package/dist/procedures/collection/procedures.js.map +1 -1
- package/dist/procedures/collection/schemas.d.ts +33 -0
- package/dist/procedures/collection/schemas.d.ts.map +1 -1
- package/dist/procedures/collection/schemas.js +68 -0
- package/dist/procedures/collection/schemas.js.map +1 -1
- package/dist/procedures/collection/storage.d.ts +37 -0
- package/dist/procedures/collection/storage.d.ts.map +1 -1
- package/dist/procedures/core/array.d.ts +1 -1
- package/dist/procedures/core/array.d.ts.map +1 -1
- package/dist/procedures/core/array.js +81 -29
- package/dist/procedures/core/array.js.map +1 -1
- package/dist/procedures/core/index.d.ts.map +1 -1
- package/dist/procedures/core/index.js +17 -2
- package/dist/procedures/core/index.js.map +1 -1
- package/dist/procedures/core/logic.js +1 -1
- package/dist/procedures/core/logic.js.map +1 -1
- package/dist/procedures/core/math.d.ts.map +1 -1
- package/dist/procedures/core/math.js +5 -1
- package/dist/procedures/core/math.js.map +1 -1
- package/dist/procedures/core/meta.d.ts.map +1 -1
- package/dist/procedures/core/meta.js +43 -8
- package/dist/procedures/core/meta.js.map +1 -1
- package/dist/procedures/core/object.d.ts.map +1 -1
- package/dist/procedures/core/object.js +42 -13
- package/dist/procedures/core/object.js.map +1 -1
- package/dist/procedures/core/operands.d.ts.map +1 -1
- package/dist/procedures/core/operands.js +6 -1
- package/dist/procedures/core/operands.js.map +1 -1
- package/dist/procedures/core/string.d.ts.map +1 -1
- package/dist/procedures/core/string.js +4 -2
- package/dist/procedures/core/string.js.map +1 -1
- package/dist/procedures/core/type.d.ts.map +1 -1
- package/dist/procedures/core/type.js +4 -2
- package/dist/procedures/core/type.js.map +1 -1
- package/dist/procedures/define-procedure.d.ts +19 -7
- package/dist/procedures/define-procedure.d.ts.map +1 -1
- package/dist/procedures/define-procedure.js +57 -17
- package/dist/procedures/define-procedure.js.map +1 -1
- package/dist/procedures/discovery.d.ts +4 -6
- package/dist/procedures/discovery.d.ts.map +1 -1
- package/dist/procedures/discovery.js +4 -6
- package/dist/procedures/discovery.js.map +1 -1
- package/dist/procedures/effects.d.ts +16 -0
- package/dist/procedures/effects.d.ts.map +1 -1
- package/dist/procedures/effects.js +33 -0
- package/dist/procedures/effects.js.map +1 -1
- package/dist/procedures/idempotency.d.ts +73 -0
- package/dist/procedures/idempotency.d.ts.map +1 -0
- package/dist/procedures/idempotency.js +131 -0
- package/dist/procedures/idempotency.js.map +1 -0
- package/dist/procedures/index.d.ts +7 -3
- package/dist/procedures/index.d.ts.map +1 -1
- package/dist/procedures/index.js +6 -2
- package/dist/procedures/index.js.map +1 -1
- package/dist/procedures/interceptors.d.ts +17 -4
- package/dist/procedures/interceptors.d.ts.map +1 -1
- package/dist/procedures/interceptors.js +18 -7
- package/dist/procedures/interceptors.js.map +1 -1
- package/dist/procedures/invoke.d.ts +15 -3
- package/dist/procedures/invoke.d.ts.map +1 -1
- package/dist/procedures/invoke.js +8 -3
- package/dist/procedures/invoke.js.map +1 -1
- package/dist/procedures/manifest.d.ts +83 -0
- package/dist/procedures/manifest.d.ts.map +1 -0
- package/dist/procedures/manifest.js +90 -0
- package/dist/procedures/manifest.js.map +1 -0
- package/dist/procedures/ref.d.ts +4 -2
- package/dist/procedures/ref.d.ts.map +1 -1
- package/dist/procedures/ref.js +2 -1
- package/dist/procedures/ref.js.map +1 -1
- package/dist/procedures/runtime-programs.d.ts +19 -0
- package/dist/procedures/runtime-programs.d.ts.map +1 -0
- package/dist/procedures/runtime-programs.js +24 -0
- package/dist/procedures/runtime-programs.js.map +1 -0
- package/dist/procedures/types.d.ts +16 -0
- package/dist/procedures/types.d.ts.map +1 -1
- package/dist/procedures/types.js.map +1 -1
- package/dist/protocol.d.ts +47 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +68 -0
- package/dist/protocol.js.map +1 -0
- package/dist/server/host-policy.d.ts +4 -3
- package/dist/server/host-policy.d.ts.map +1 -1
- package/dist/server/host-policy.js +6 -3
- package/dist/server/host-policy.js.map +1 -1
- package/dist/server/host-procedures.d.ts +31 -0
- package/dist/server/host-procedures.d.ts.map +1 -0
- package/dist/server/host-procedures.js +75 -0
- package/dist/server/host-procedures.js.map +1 -0
- package/dist/server/index.d.ts +3 -3
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/procedure-server.d.ts +112 -5
- package/dist/server/procedure-server.d.ts.map +1 -1
- package/dist/server/procedure-server.js +273 -19
- package/dist/server/procedure-server.js.map +1 -1
- package/dist/server/request-auth.d.ts +19 -0
- package/dist/server/request-auth.d.ts.map +1 -0
- package/dist/server/request-auth.js +32 -0
- package/dist/server/request-auth.js.map +1 -0
- package/dist/server/types.d.ts +32 -0
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/types.js.map +1 -1
- package/dist/timers.d.ts +60 -0
- package/dist/timers.d.ts.map +1 -0
- package/dist/timers.js +33 -0
- package/dist/timers.js.map +1 -0
- package/dist/tracing/traceparent.d.ts +26 -0
- package/dist/tracing/traceparent.d.ts.map +1 -0
- package/dist/tracing/traceparent.js +53 -0
- package/dist/tracing/traceparent.js.map +1 -0
- package/package.json +11 -4
- package/src/adapters/http/client/transport.ts +31 -3
- package/src/adapters/http/client/types.ts +3 -2
- package/src/adapters/http/server/transport.ts +61 -26
- package/src/adapters/http/shared/headers.ts +7 -6
- package/src/adapters/local/client/transport.ts +74 -7
- package/src/adapters/local/client/types.ts +18 -0
- package/src/adapters/mock/client/transport.ts +9 -6
- package/src/adapters/mock/client/types.ts +7 -6
- package/src/adapters/port/transport.ts +124 -0
- package/src/adapters/websocket/client/index.ts +1 -0
- package/src/adapters/websocket/client/node.ts +34 -0
- package/src/adapters/websocket/client/transport.ts +251 -81
- package/src/adapters/websocket/client/types.ts +81 -21
- package/src/adapters/websocket/server/transport.ts +199 -301
- package/src/adapters/websocket/server/types.ts +20 -1
- package/src/adapters/websocket/shared/rpc.ts +453 -0
- package/src/browser.ts +51 -3
- package/src/client/call-types.ts +10 -14
- package/src/client/client.ts +28 -9
- package/src/client/deadline.ts +64 -0
- package/src/client/errors/README.md +2 -0
- package/src/client/errors/factory.ts +12 -8
- package/src/client/errors/http-errors.ts +5 -0
- package/src/client/errors/index.ts +1 -1
- package/src/client/errors/registry.ts +79 -22
- package/src/client/index.ts +21 -3
- package/src/client/middleware/cache.ts +120 -23
- package/src/client/middleware/circuit-breaker.ts +65 -35
- package/src/client/middleware/contexts.ts +17 -10
- package/src/client/middleware/items.ts +101 -6
- package/src/client/middleware/lru.ts +12 -0
- package/src/client/middleware/rate-limit.ts +9 -9
- package/src/client/middleware/resilience.ts +81 -0
- package/src/client/middleware/retry.ts +146 -34
- package/src/client/middleware/timeout.ts +57 -55
- package/src/client/middleware/tracing.ts +13 -4
- package/src/client/types.ts +9 -1
- package/src/clock.ts +21 -0
- package/src/duration.ts +56 -0
- package/src/index.ts +4 -0
- package/src/machines.ts +186 -0
- package/src/node.ts +11 -0
- package/src/procedures/capability.ts +7 -8
- package/src/procedures/collection/index.ts +9 -1
- package/src/procedures/collection/procedures.ts +57 -0
- package/src/procedures/collection/schemas.ts +110 -0
- package/src/procedures/collection/storage.ts +38 -0
- package/src/procedures/core/array.ts +79 -31
- package/src/procedures/core/index.ts +17 -2
- package/src/procedures/core/logic.ts +1 -1
- package/src/procedures/core/math.ts +5 -1
- package/src/procedures/core/meta.ts +45 -8
- package/src/procedures/core/object.ts +48 -14
- package/src/procedures/core/operands.ts +7 -0
- package/src/procedures/core/string.ts +4 -2
- package/src/procedures/core/type.ts +4 -2
- package/src/procedures/define-procedure.ts +73 -20
- package/src/procedures/discovery.ts +4 -6
- package/src/procedures/effects.ts +35 -0
- package/src/procedures/idempotency.ts +152 -0
- package/src/procedures/index.ts +18 -2
- package/src/procedures/interceptors.ts +28 -11
- package/src/procedures/invoke.ts +19 -5
- package/src/procedures/manifest.ts +158 -0
- package/src/procedures/ref.ts +4 -2
- package/src/procedures/runtime-programs.ts +27 -0
- package/src/procedures/types.ts +16 -0
- package/src/protocol.ts +108 -0
- package/src/server/host-policy.ts +8 -5
- package/src/server/host-procedures.ts +102 -0
- package/src/server/index.ts +5 -2
- package/src/server/procedure-server.ts +328 -12
- package/src/server/request-auth.ts +35 -0
- package/src/server/types.ts +37 -0
- package/src/timers.ts +82 -0
- package/src/tracing/traceparent.ts +61 -0
- package/dist/procedures/cli/announce.d.ts +0 -67
- package/dist/procedures/cli/announce.d.ts.map +0 -1
- package/dist/procedures/cli/announce.js +0 -392
- package/dist/procedures/cli/announce.js.map +0 -1
- package/dist/procedures/cli/discover.d.ts +0 -66
- package/dist/procedures/cli/discover.d.ts.map +0 -1
- package/dist/procedures/cli/discover.js +0 -310
- package/dist/procedures/cli/discover.js.map +0 -1
- package/dist/procedures/cli/index.d.ts +0 -13
- package/dist/procedures/cli/index.d.ts.map +0 -1
- package/dist/procedures/cli/index.js +0 -203
- package/dist/procedures/cli/index.js.map +0 -1
- package/dist/procedures/cli/init.d.ts +0 -17
- package/dist/procedures/cli/init.d.ts.map +0 -1
- package/dist/procedures/cli/init.js +0 -336
- package/dist/procedures/cli/init.js.map +0 -1
- package/src/procedures/cli/announce.ts +0 -470
- package/src/procedures/cli/discover.ts +0 -390
- package/src/procedures/cli/index.ts +0 -231
- package/src/procedures/cli/init.ts +0 -370
|
@@ -7,6 +7,18 @@
|
|
|
7
7
|
import type { Transport, Message, ResponseItem } from "../../../client/types.js";
|
|
8
8
|
import type { WebSocketTransportOptions } from "./types.js";
|
|
9
9
|
import { WebSocketState } from "./types.js";
|
|
10
|
+
/**
|
|
11
|
+
* The delay before a reconnect try (from 1): an exponential backoff up to `maxDelay`, with a
|
|
12
|
+
* random part. With `jitter: 1` (full jitter, the default), the delay is a random time from 0 to
|
|
13
|
+
* the backoff, so the clients of a server that restarted do not come back at the same moments
|
|
14
|
+
* (roadmap R3). The backoff is `backoffDelay` of `@mark1russell7/service/backoff` (roadmap R37).
|
|
15
|
+
*/
|
|
16
|
+
export declare function reconnectDelay(attempt: number, options: {
|
|
17
|
+
initialDelay: number;
|
|
18
|
+
backoffMultiplier: number;
|
|
19
|
+
maxDelay: number;
|
|
20
|
+
jitter: number;
|
|
21
|
+
}, random?: () => number): number;
|
|
10
22
|
export declare class WebSocketTransport implements Transport {
|
|
11
23
|
readonly name = "websocket";
|
|
12
24
|
private ws;
|
|
@@ -26,14 +38,30 @@ export declare class WebSocketTransport implements Transport {
|
|
|
26
38
|
private closing;
|
|
27
39
|
private reconnectAttempts;
|
|
28
40
|
private reconnectTimer;
|
|
29
|
-
|
|
30
|
-
private
|
|
41
|
+
/** The heartbeat of the connection: `heartbeat` of `@mark1russell7/service/heartbeat` (roadmap R37) */
|
|
42
|
+
private heartbeat;
|
|
43
|
+
/** The clock of each timer of the transport (roadmap R35) */
|
|
44
|
+
private readonly clock;
|
|
45
|
+
/** The class of the sockets, or undefined for the global `WebSocket` (finding of roadmap R36) */
|
|
46
|
+
private readonly webSocketClass;
|
|
47
|
+
/** The observer of the changes of the state (roadmap R35) */
|
|
48
|
+
private readonly transitionHandler;
|
|
49
|
+
/** The protocol version of the server, from its hello, or undefined (an older server) (R25) */
|
|
50
|
+
private serverProtocol;
|
|
51
|
+
/** The calls that the server sends on the current socket, served by `serve` (roadmap R24) */
|
|
52
|
+
private incoming;
|
|
53
|
+
/** The time limit of the handshake of the socket that opens now */
|
|
54
|
+
private handshakeTimer;
|
|
31
55
|
constructor(options: WebSocketTransportOptions);
|
|
32
|
-
/**
|
|
56
|
+
/**
|
|
57
|
+
* Change the state, and tell the callers that wait for a connection. `on` is the event of
|
|
58
|
+
* `WEBSOCKET_CONNECTION_MACHINE`: a change goes to `onTransition` (roadmap R35).
|
|
59
|
+
*/
|
|
33
60
|
private setState;
|
|
34
61
|
/**
|
|
35
62
|
* Connect to WebSocket server.
|
|
36
63
|
*/
|
|
64
|
+
/** `on` is the event of the machine: the first connect, or the end of a backoff. */
|
|
37
65
|
private connect;
|
|
38
66
|
/**
|
|
39
67
|
* Leave the current socket without waiting for its close handshake, and handle the close
|
|
@@ -52,16 +80,23 @@ export declare class WebSocketTransport implements Transport {
|
|
|
52
80
|
*/
|
|
53
81
|
private scheduleReconnect;
|
|
54
82
|
/**
|
|
55
|
-
* Start heartbeat
|
|
83
|
+
* Start the heartbeat of the connection: a ping each interval on the clock, one ping at a time.
|
|
84
|
+
* A ping without a pong at its timeout is a miss. The connection drops after `misses` misses in
|
|
85
|
+
* sequence. A ping whose send throws is a miss too.
|
|
56
86
|
*/
|
|
57
87
|
private startHeartbeat;
|
|
58
|
-
/**
|
|
59
|
-
|
|
60
|
-
|
|
88
|
+
/** Stop the time limit of the handshake. */
|
|
89
|
+
private clearHandshakeTimer;
|
|
90
|
+
/** Stop the heartbeat: its ticks, its timeout and its count of misses. */
|
|
61
91
|
private stopHeartbeat;
|
|
62
92
|
/**
|
|
63
93
|
* Handle incoming message.
|
|
64
94
|
*/
|
|
95
|
+
/**
|
|
96
|
+
* The calls of the server on a socket, served by the `serve` option (or by `onServerRequest`, or
|
|
97
|
+
* with `NOT_FOUND`), with the same frames as the calls of the client (roadmap R24).
|
|
98
|
+
*/
|
|
99
|
+
private servedCallsOf;
|
|
65
100
|
private handleMessage;
|
|
66
101
|
/**
|
|
67
102
|
* Send a request and yield its response items as they arrive: one item for a request/response
|
|
@@ -94,6 +129,11 @@ export declare class WebSocketTransport implements Transport {
|
|
|
94
129
|
* Close WebSocket connection. The requests in progress fail.
|
|
95
130
|
*/
|
|
96
131
|
close(): Promise<void>;
|
|
132
|
+
/**
|
|
133
|
+
* The protocol version of the server: from its hello, or undefined before the hello or for a
|
|
134
|
+
* server older than protocol v1 (roadmap R25).
|
|
135
|
+
*/
|
|
136
|
+
getServerProtocol(): number | undefined;
|
|
97
137
|
/**
|
|
98
138
|
* Get current connection state.
|
|
99
139
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../../../src/adapters/websocket/client/transport.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACjF,OAAO,KAAK,EAAE,yBAAyB,
|
|
1
|
+
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../../../src/adapters/websocket/client/transport.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACjF,OAAO,KAAK,EAAE,yBAAyB,EAAwE,MAAM,YAAY,CAAC;AAClI,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AA8I5C;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAC9F,MAAM,GAAE,MAAM,MAAoB,GACjC,MAAM,CAaR;AAED,qBAAa,kBAAmB,YAAW,SAAS;IAClD,QAAQ,CAAC,IAAI,eAAe;IAE5B,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,KAAK,CAA+C;IAC5D,OAAO,CAAC,OAAO,CA0Bb;IACF,2CAA2C;IAC3C,OAAO,CAAC,eAAe,CAA0C;IACjE;;;;OAIG;IACH,OAAO,CAAC,OAAO,CAAK;IACpB,2FAA2F;IAC3F,OAAO,CAAC,cAAc,CAAyB;IAC/C,0FAA0F;IAC1F,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,cAAc,CAAiC;IACvD,uGAAuG;IACvG,OAAO,CAAC,SAAS,CAAoC;IACrD,6DAA6D;IAC7D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAC9B,iGAAiG;IACjG,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA6B;IAC5D,6DAA6D;IAC7D,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAwD;IAC1F,+FAA+F;IAC/F,OAAO,CAAC,cAAc,CAAqB;IAC3C,6FAA6F;IAC7F,OAAO,CAAC,QAAQ,CAA4B;IAC5C,mEAAmE;IACnE,OAAO,CAAC,cAAc,CAAiC;IAEvD,YAAY,OAAO,EAAE,yBAAyB,EAkC7C;IAED;;;OAGG;IACH,OAAO,CAAC,QAAQ;IAShB;;OAEG;IACH,oFAAoF;IACpF,OAAO,CAAC,OAAO;IAwFf;;;;OAIG;IACH,OAAO,CAAC,cAAc;IAkBtB,yEAAyE;IACzE,OAAO,CAAC,WAAW;IAQnB;;OAEG;IACH,OAAO,CAAC,WAAW;IAwBnB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAuBzB;;;;OAIG;IACH,OAAO,CAAC,cAAc;IAsBtB,4CAA4C;IAC5C,OAAO,CAAC,mBAAmB;IAO3B,0EAA0E;IAC1E,OAAO,CAAC,aAAa;IAKrB;;OAEG;IACH;;;OAGG;IACH,OAAO,CAAC,aAAa;IAkBrB,OAAO,CAAC,aAAa;IAsIrB;;;;;;;;;;;OAWG;IACI,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,aAAa,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CA2EjF;IAED,qFAAqF;IACrF,OAAO,CAAC,UAAU;IAOlB,wEAAwE;IACxE,OAAO,CAAC,UAAU;IAOlB;;;;OAIG;YACW,iBAAiB;IA4B/B;;OAEG;YACW,mBAAmB;IA8CjC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CA0B3B;IAED;;;OAGG;IACH,iBAAiB,IAAI,MAAM,GAAG,SAAS,CAEtC;IAED;;OAEG;IACH,QAAQ,IAAI,cAAc,CAEzB;IAED;;OAEG;IACH,WAAW,IAAI,OAAO,CAErB;CACF"}
|
|
@@ -6,6 +6,15 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { WebSocketState } from "./types.js";
|
|
8
8
|
import { withoutInternalKeys } from "../../metadata.js";
|
|
9
|
+
import { IncomingCalls } from "../shared/rpc.js";
|
|
10
|
+
import { methodToPath } from "../../../server/method.js";
|
|
11
|
+
import { optionalMs } from "../../../duration.js";
|
|
12
|
+
import { PROTOCOL_VERSION, protocolVersionOf } from "../../../protocol.js";
|
|
13
|
+
import { TIMER_DEFAULTS } from "../../../timers.js";
|
|
14
|
+
import { realClock } from "../../../clock.js";
|
|
15
|
+
import { heartbeat } from "@mark1russell7/service/heartbeat";
|
|
16
|
+
import { backoffDelay } from "@mark1russell7/service/backoff";
|
|
17
|
+
import { WEBSOCKET_CONNECTION_MACHINE } from "../../../machines.js";
|
|
9
18
|
/**
|
|
10
19
|
* Pending request waiting for response.
|
|
11
20
|
*/
|
|
@@ -68,12 +77,68 @@ class ItemQueue {
|
|
|
68
77
|
* const client = new Client({ transport });
|
|
69
78
|
* ```
|
|
70
79
|
*/
|
|
80
|
+
/** The `readyState` of an open socket. The global `WebSocket` and the `ws` package use the same number. */
|
|
81
|
+
const OPEN = 1;
|
|
82
|
+
/**
|
|
83
|
+
* The error handler of a socket that the transport left. A `WebSocket` of the `ws` package that
|
|
84
|
+
* closes during its handshake emits an error. Without a handler, that error ends the process.
|
|
85
|
+
*/
|
|
86
|
+
const ignoreError = () => undefined;
|
|
87
|
+
/** This function removes the handlers of the transport from a socket, and keeps `ignoreError`. */
|
|
88
|
+
function detach(ws) {
|
|
89
|
+
ws.onopen = null;
|
|
90
|
+
ws.onmessage = null;
|
|
91
|
+
ws.onclose = null;
|
|
92
|
+
ws.onerror = ignoreError;
|
|
93
|
+
}
|
|
71
94
|
function errorItem(id, code, message, retryable) {
|
|
72
95
|
return { id, status: { type: "error", code, message, retryable }, payload: null, metadata: {} };
|
|
73
96
|
}
|
|
74
97
|
function abortedItem(id) {
|
|
75
98
|
return errorItem(id, "ABORTED", "Request was aborted", false);
|
|
76
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* The handler of the calls of the server without a `serve` option: the older `onServerRequest`
|
|
102
|
+
* function (it gets the path and the input), or else a `NOT_FOUND` answer.
|
|
103
|
+
*/
|
|
104
|
+
function legacyHandler(onServerRequest) {
|
|
105
|
+
return {
|
|
106
|
+
async handle(request) {
|
|
107
|
+
if (!onServerRequest) {
|
|
108
|
+
return {
|
|
109
|
+
id: request.id,
|
|
110
|
+
status: { type: "error", code: "NOT_FOUND", message: "This client serves no procedures", retryable: false },
|
|
111
|
+
metadata: {},
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
const payload = await onServerRequest(methodToPath(request.method), request.payload);
|
|
116
|
+
return { id: request.id, status: { type: "success", code: 200 }, payload, metadata: {} };
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
return {
|
|
120
|
+
id: request.id,
|
|
121
|
+
status: { type: "error", code: "HANDLER_ERROR", message: error instanceof Error ? error.message : String(error), retryable: false },
|
|
122
|
+
metadata: {},
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* The delay before a reconnect try (from 1): an exponential backoff up to `maxDelay`, with a
|
|
130
|
+
* random part. With `jitter: 1` (full jitter, the default), the delay is a random time from 0 to
|
|
131
|
+
* the backoff, so the clients of a server that restarted do not come back at the same moments
|
|
132
|
+
* (roadmap R3). The backoff is `backoffDelay` of `@mark1russell7/service/backoff` (roadmap R37).
|
|
133
|
+
*/
|
|
134
|
+
export function reconnectDelay(attempt, options, random = Math.random) {
|
|
135
|
+
return Math.round(backoffDelay({
|
|
136
|
+
base: options.initialDelay,
|
|
137
|
+
factor: options.backoffMultiplier,
|
|
138
|
+
cap: options.maxDelay,
|
|
139
|
+
jitter: { kind: "subtract", fraction: options.jitter },
|
|
140
|
+
}, attempt, random));
|
|
141
|
+
}
|
|
77
142
|
export class WebSocketTransport {
|
|
78
143
|
name = "websocket";
|
|
79
144
|
ws = null;
|
|
@@ -93,62 +158,106 @@ export class WebSocketTransport {
|
|
|
93
158
|
closing = false;
|
|
94
159
|
reconnectAttempts = 0;
|
|
95
160
|
reconnectTimer = undefined;
|
|
96
|
-
|
|
97
|
-
|
|
161
|
+
/** The heartbeat of the connection: `heartbeat` of `@mark1russell7/service/heartbeat` (roadmap R37) */
|
|
162
|
+
heartbeat = undefined;
|
|
163
|
+
/** The clock of each timer of the transport (roadmap R35) */
|
|
164
|
+
clock;
|
|
165
|
+
/** The class of the sockets, or undefined for the global `WebSocket` (finding of roadmap R36) */
|
|
166
|
+
webSocketClass;
|
|
167
|
+
/** The observer of the changes of the state (roadmap R35) */
|
|
168
|
+
transitionHandler;
|
|
169
|
+
/** The protocol version of the server, from its hello, or undefined (an older server) (R25) */
|
|
170
|
+
serverProtocol;
|
|
171
|
+
/** The calls that the server sends on the current socket, served by `serve` (roadmap R24) */
|
|
172
|
+
incoming;
|
|
173
|
+
/** The time limit of the handshake of the socket that opens now */
|
|
174
|
+
handshakeTimer = undefined;
|
|
98
175
|
constructor(options) {
|
|
99
176
|
this.options = {
|
|
100
177
|
url: options.url,
|
|
101
178
|
reconnect: {
|
|
102
179
|
enabled: options.reconnect?.enabled ?? true,
|
|
103
180
|
maxAttempts: options.reconnect?.maxAttempts ?? Infinity,
|
|
104
|
-
initialDelay: options.reconnect?.initialDelay ??
|
|
105
|
-
maxDelay: options.reconnect?.maxDelay ??
|
|
181
|
+
initialDelay: optionalMs(options.reconnect?.initialDelay, "reconnect.initialDelay") ?? TIMER_DEFAULTS.webSocketClient.reconnectInitialDelayMs,
|
|
182
|
+
maxDelay: optionalMs(options.reconnect?.maxDelay, "reconnect.maxDelay") ?? TIMER_DEFAULTS.webSocketClient.reconnectMaxDelayMs,
|
|
106
183
|
backoffMultiplier: options.reconnect?.backoffMultiplier ?? 1.5,
|
|
184
|
+
jitter: Math.min(1, Math.max(0, options.reconnect?.jitter ?? 1)),
|
|
107
185
|
},
|
|
108
|
-
connectionTimeout: options.connectionTimeout ??
|
|
109
|
-
requestTimeout: options.requestTimeout ??
|
|
186
|
+
connectionTimeout: optionalMs(options.connectionTimeout, "connectionTimeout") ?? TIMER_DEFAULTS.webSocketClient.connectionTimeoutMs,
|
|
187
|
+
requestTimeout: optionalMs(options.requestTimeout, "requestTimeout") ?? TIMER_DEFAULTS.webSocketClient.requestTimeoutMs,
|
|
110
188
|
streamWindow: Math.max(2, options.streamWindow ?? 64),
|
|
111
189
|
heartbeat: {
|
|
112
190
|
enabled: options.heartbeat?.enabled ?? true,
|
|
113
|
-
interval: options.heartbeat?.interval ??
|
|
114
|
-
timeout: options.heartbeat?.timeout ??
|
|
191
|
+
interval: optionalMs(options.heartbeat?.interval, "heartbeat.interval") ?? TIMER_DEFAULTS.webSocketClient.heartbeatIntervalMs,
|
|
192
|
+
timeout: optionalMs(options.heartbeat?.timeout, "heartbeat.timeout") ?? TIMER_DEFAULTS.webSocketClient.heartbeatTimeoutMs,
|
|
193
|
+
misses: Math.max(1, options.heartbeat?.misses ?? TIMER_DEFAULTS.webSocketClient.heartbeatMisses),
|
|
115
194
|
},
|
|
116
195
|
onConnect: options.onConnect,
|
|
117
196
|
onDisconnect: options.onDisconnect,
|
|
118
197
|
onReconnecting: options.onReconnecting,
|
|
119
198
|
onError: options.onError,
|
|
120
199
|
onServerRequest: options.onServerRequest,
|
|
200
|
+
serve: options.serve,
|
|
121
201
|
onEvent: options.onEvent,
|
|
122
202
|
};
|
|
203
|
+
this.clock = options.clock ?? realClock;
|
|
204
|
+
this.webSocketClass = options.webSocket;
|
|
205
|
+
this.transitionHandler = options.onTransition;
|
|
123
206
|
// Auto-connect
|
|
124
207
|
this.connect();
|
|
125
208
|
}
|
|
126
|
-
/**
|
|
127
|
-
|
|
209
|
+
/**
|
|
210
|
+
* Change the state, and tell the callers that wait for a connection. `on` is the event of
|
|
211
|
+
* `WEBSOCKET_CONNECTION_MACHINE`: a change goes to `onTransition` (roadmap R35).
|
|
212
|
+
*/
|
|
213
|
+
setState(state, on) {
|
|
214
|
+
const from = this.state;
|
|
128
215
|
this.state = state;
|
|
216
|
+
if (from !== state) {
|
|
217
|
+
this.transitionHandler?.({ machine: WEBSOCKET_CONNECTION_MACHINE.id, instance: this.name, from, on, to: state, at: this.clock.now() });
|
|
218
|
+
}
|
|
129
219
|
for (const listener of [...this.stateListeners])
|
|
130
220
|
listener();
|
|
131
221
|
}
|
|
132
222
|
/**
|
|
133
223
|
* Connect to WebSocket server.
|
|
134
224
|
*/
|
|
135
|
-
connect
|
|
225
|
+
/** `on` is the event of the machine: the first connect, or the end of a backoff. */
|
|
226
|
+
connect(on = "connect") {
|
|
136
227
|
if (this.closing) {
|
|
137
228
|
return;
|
|
138
229
|
}
|
|
139
230
|
if (this.state === WebSocketState.CONNECTING || this.state === WebSocketState.CONNECTED) {
|
|
140
231
|
return;
|
|
141
232
|
}
|
|
142
|
-
this.setState(WebSocketState.CONNECTING);
|
|
233
|
+
this.setState(WebSocketState.CONNECTING, on);
|
|
143
234
|
try {
|
|
144
|
-
const
|
|
235
|
+
const WebSocketClass = (this.webSocketClass ?? WebSocket);
|
|
236
|
+
const ws = new WebSocketClass(this.options.url);
|
|
145
237
|
this.ws = ws;
|
|
238
|
+
// The calls of the server on this socket (roadmap R24)
|
|
239
|
+
this.incoming = this.servedCallsOf(ws);
|
|
240
|
+
// The handshake has a time limit: an address that never answers does not keep the socket
|
|
241
|
+
// in CONNECTING (roadmap R3)
|
|
242
|
+
this.clearHandshakeTimer();
|
|
243
|
+
this.handshakeTimer = this.clock.after(this.options.connectionTimeout, () => {
|
|
244
|
+
this.handshakeTimer = undefined;
|
|
245
|
+
if (this.ws === ws && this.state === WebSocketState.CONNECTING) {
|
|
246
|
+
console.error(`[${this.name}] Handshake timeout after ${this.options.connectionTimeout}ms`);
|
|
247
|
+
this.dropConnection("Handshake timeout");
|
|
248
|
+
}
|
|
249
|
+
});
|
|
146
250
|
// Connection opened
|
|
147
251
|
ws.onopen = () => {
|
|
148
252
|
if (this.ws !== ws)
|
|
149
253
|
return;
|
|
254
|
+
this.clearHandshakeTimer();
|
|
150
255
|
this.reconnectAttempts = 0;
|
|
151
|
-
this
|
|
256
|
+
// The protocol version of this client (roadmap R25). An older server answers with an
|
|
257
|
+
// error frame, which the client ignores.
|
|
258
|
+
this.serverProtocol = undefined;
|
|
259
|
+
ws.send(JSON.stringify({ id: "hello", type: "hello", protocol: PROTOCOL_VERSION }));
|
|
260
|
+
this.setState(WebSocketState.CONNECTED, "open");
|
|
152
261
|
console.error(`[${this.name}] Connected to ${this.options.url}`);
|
|
153
262
|
if (this.options.onConnect) {
|
|
154
263
|
this.options.onConnect();
|
|
@@ -186,6 +295,11 @@ export class WebSocketTransport {
|
|
|
186
295
|
if (this.options.onError) {
|
|
187
296
|
this.options.onError(error);
|
|
188
297
|
}
|
|
298
|
+
// A failed handshake: the WebSocket of Node 22 gives no close event after this error, and
|
|
299
|
+
// stays in CONNECTING. The transport closes it now, so the reconnect starts at once
|
|
300
|
+
// (roadmap R30: before, each try waited for the time limit of the handshake).
|
|
301
|
+
if (this.state === WebSocketState.CONNECTING)
|
|
302
|
+
this.dropConnection("Connection failed");
|
|
189
303
|
};
|
|
190
304
|
}
|
|
191
305
|
catch (error) {
|
|
@@ -203,12 +317,15 @@ export class WebSocketTransport {
|
|
|
203
317
|
const ws = this.ws;
|
|
204
318
|
this.ws = null;
|
|
205
319
|
if (ws) {
|
|
206
|
-
ws
|
|
207
|
-
ws.onmessage = null;
|
|
208
|
-
ws.onclose = null;
|
|
209
|
-
ws.onerror = null;
|
|
320
|
+
detach(ws);
|
|
210
321
|
try {
|
|
211
|
-
ws.close()
|
|
322
|
+
// A socket of the ws package ends at once. close() waits for the answer of a peer that can
|
|
323
|
+
// be dead (finding of roadmap R36).
|
|
324
|
+
const terminate = ws.terminate;
|
|
325
|
+
if (typeof terminate === "function")
|
|
326
|
+
terminate.call(ws);
|
|
327
|
+
else
|
|
328
|
+
ws.close();
|
|
212
329
|
}
|
|
213
330
|
catch {
|
|
214
331
|
// The socket is unusable: nothing more to do
|
|
@@ -220,9 +337,7 @@ export class WebSocketTransport {
|
|
|
220
337
|
failPending(reason) {
|
|
221
338
|
for (const pending of this.pendingRequests.values()) {
|
|
222
339
|
pending.queue.fail(new Error(reason));
|
|
223
|
-
|
|
224
|
-
clearTimeout(pending.timeout);
|
|
225
|
-
}
|
|
340
|
+
pending.timeout?.();
|
|
226
341
|
}
|
|
227
342
|
this.pendingRequests.clear();
|
|
228
343
|
}
|
|
@@ -231,10 +346,13 @@ export class WebSocketTransport {
|
|
|
231
346
|
*/
|
|
232
347
|
handleClose(reason) {
|
|
233
348
|
this.stopHeartbeat();
|
|
349
|
+
this.incoming?.abortAll();
|
|
350
|
+
this.incoming = undefined;
|
|
351
|
+
this.clearHandshakeTimer();
|
|
234
352
|
// An intentional close() must not reconnect (BUGS-2026-07 H6)
|
|
235
353
|
const reconnect = this.options.reconnect;
|
|
236
354
|
const willReconnect = !this.closing && reconnect.enabled && this.reconnectAttempts < reconnect.maxAttempts;
|
|
237
|
-
this.setState(willReconnect ? WebSocketState.RECONNECTING : WebSocketState.DISCONNECTED);
|
|
355
|
+
this.setState(willReconnect ? WebSocketState.RECONNECTING : WebSocketState.DISCONNECTED, willReconnect ? "drop" : "give-up");
|
|
238
356
|
console.error(`[${this.name}] Disconnected${reason ? `: ${reason}` : ""}`);
|
|
239
357
|
if (this.options.onDisconnect) {
|
|
240
358
|
this.options.onDisconnect(reason);
|
|
@@ -249,68 +367,99 @@ export class WebSocketTransport {
|
|
|
249
367
|
*/
|
|
250
368
|
scheduleReconnect() {
|
|
251
369
|
if (this.reconnectTimer) {
|
|
252
|
-
|
|
370
|
+
this.reconnectTimer();
|
|
253
371
|
}
|
|
254
372
|
this.reconnectAttempts++;
|
|
255
|
-
this.setState(WebSocketState.RECONNECTING);
|
|
256
|
-
const
|
|
257
|
-
const delay = Math.min(reconnect.initialDelay * Math.pow(reconnect.backoffMultiplier, this.reconnectAttempts - 1), reconnect.maxDelay);
|
|
373
|
+
this.setState(WebSocketState.RECONNECTING, "drop");
|
|
374
|
+
const delay = reconnectDelay(this.reconnectAttempts, this.options.reconnect);
|
|
258
375
|
console.error(`[${this.name}] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
|
|
259
376
|
if (this.options.onReconnecting) {
|
|
260
377
|
this.options.onReconnecting(this.reconnectAttempts);
|
|
261
378
|
}
|
|
262
|
-
this.reconnectTimer =
|
|
379
|
+
this.reconnectTimer = this.clock.after(delay, () => {
|
|
263
380
|
this.reconnectTimer = undefined;
|
|
264
381
|
// connect() returns at once in RECONNECTING only for CONNECTING/CONNECTED
|
|
265
|
-
this.connect();
|
|
266
|
-
}
|
|
382
|
+
this.connect("reconnect-due");
|
|
383
|
+
});
|
|
267
384
|
}
|
|
268
385
|
/**
|
|
269
|
-
* Start heartbeat
|
|
386
|
+
* Start the heartbeat of the connection: a ping each interval on the clock, one ping at a time.
|
|
387
|
+
* A ping without a pong at its timeout is a miss. The connection drops after `misses` misses in
|
|
388
|
+
* sequence. A ping whose send throws is a miss too.
|
|
270
389
|
*/
|
|
271
390
|
startHeartbeat() {
|
|
272
391
|
this.stopHeartbeat();
|
|
273
|
-
this.
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
392
|
+
this.heartbeat = heartbeat({
|
|
393
|
+
interval: this.options.heartbeat.interval,
|
|
394
|
+
timeout: this.options.heartbeat.timeout,
|
|
395
|
+
misses: this.options.heartbeat.misses,
|
|
396
|
+
clock: this.clock,
|
|
397
|
+
due: () => this.state === WebSocketState.CONNECTED && this.ws !== null,
|
|
398
|
+
ping: () => {
|
|
277
399
|
const ping = {
|
|
278
400
|
id: `ping-${Date.now()}`,
|
|
279
401
|
type: "ping",
|
|
280
402
|
};
|
|
281
|
-
this.ws
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
}
|
|
289
|
-
}, this.options.heartbeat.interval);
|
|
403
|
+
this.ws?.send(JSON.stringify(ping));
|
|
404
|
+
},
|
|
405
|
+
onDead: () => {
|
|
406
|
+
console.warn(`[${this.name}] Heartbeat timeout - dropping connection`);
|
|
407
|
+
this.dropConnection("Heartbeat timeout");
|
|
408
|
+
},
|
|
409
|
+
});
|
|
290
410
|
}
|
|
291
|
-
/**
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
clearInterval(this.heartbeatTimer);
|
|
297
|
-
this.heartbeatTimer = undefined;
|
|
298
|
-
}
|
|
299
|
-
if (this.heartbeatTimeout) {
|
|
300
|
-
clearTimeout(this.heartbeatTimeout);
|
|
301
|
-
this.heartbeatTimeout = undefined;
|
|
411
|
+
/** Stop the time limit of the handshake. */
|
|
412
|
+
clearHandshakeTimer() {
|
|
413
|
+
if (this.handshakeTimer) {
|
|
414
|
+
this.handshakeTimer();
|
|
415
|
+
this.handshakeTimer = undefined;
|
|
302
416
|
}
|
|
303
417
|
}
|
|
418
|
+
/** Stop the heartbeat: its ticks, its timeout and its count of misses. */
|
|
419
|
+
stopHeartbeat() {
|
|
420
|
+
this.heartbeat?.stop();
|
|
421
|
+
this.heartbeat = undefined;
|
|
422
|
+
}
|
|
304
423
|
/**
|
|
305
424
|
* Handle incoming message.
|
|
306
425
|
*/
|
|
426
|
+
/**
|
|
427
|
+
* The calls of the server on a socket, served by the `serve` option (or by `onServerRequest`, or
|
|
428
|
+
* with `NOT_FOUND`), with the same frames as the calls of the client (roadmap R24).
|
|
429
|
+
*/
|
|
430
|
+
servedCallsOf(ws) {
|
|
431
|
+
const handler = this.options.serve ?? legacyHandler(this.options.onServerRequest);
|
|
432
|
+
return new IncomingCalls({
|
|
433
|
+
handler,
|
|
434
|
+
send: (frame) => ws.send(JSON.stringify(frame)),
|
|
435
|
+
isOpen: () => this.ws === ws && ws.readyState === OPEN,
|
|
436
|
+
// A call of the server that this client opened: the client trusts it, so it has no
|
|
437
|
+
// credentials (a ProcedureServer applies no token to it)
|
|
438
|
+
requestOf: (frame, signal) => ({
|
|
439
|
+
id: frame.id,
|
|
440
|
+
method: frame.method,
|
|
441
|
+
payload: frame.payload === undefined ? {} : frame.payload,
|
|
442
|
+
metadata: withoutInternalKeys(frame.metadata),
|
|
443
|
+
signal,
|
|
444
|
+
}),
|
|
445
|
+
});
|
|
446
|
+
}
|
|
307
447
|
handleMessage(message) {
|
|
448
|
+
// The hello of the server, or the error of an older server that does not know it (R25)
|
|
449
|
+
if (message.id === "hello" && (message.type === "hello" || message.type === "error")) {
|
|
450
|
+
if (message.type === "hello")
|
|
451
|
+
this.serverProtocol = protocolVersionOf(message.protocol);
|
|
452
|
+
else if (message.error?.code === "PROTOCOL_VERSION")
|
|
453
|
+
this.options.onError?.(new Error(message.error.message));
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
// A call of the server, its cancel or its credit (roadmap R24)
|
|
457
|
+
if ((message.type === "request" || message.type === "cancel" || message.type === "credit") && this.incoming?.receive(message)) {
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
308
460
|
// Handle pong (heartbeat response)
|
|
309
461
|
if (message.type === "pong") {
|
|
310
|
-
|
|
311
|
-
clearTimeout(this.heartbeatTimeout);
|
|
312
|
-
this.heartbeatTimeout = undefined;
|
|
313
|
-
}
|
|
462
|
+
this.heartbeat?.answer();
|
|
314
463
|
return;
|
|
315
464
|
}
|
|
316
465
|
// Handle ping (server heartbeat)
|
|
@@ -347,7 +496,7 @@ export class WebSocketTransport {
|
|
|
347
496
|
}
|
|
348
497
|
// The first frame arrived: the request timeout ends. A stream can stay open longer.
|
|
349
498
|
if (pending.timeout) {
|
|
350
|
-
|
|
499
|
+
pending.timeout();
|
|
351
500
|
pending.timeout = undefined;
|
|
352
501
|
}
|
|
353
502
|
// A stream frame: one item, or the end of the stream (BUGS-2026-07 H5: before, the
|
|
@@ -457,7 +606,7 @@ export class WebSocketTransport {
|
|
|
457
606
|
};
|
|
458
607
|
const queue = new ItemQueue();
|
|
459
608
|
const pending = { messageId: message.id, queue };
|
|
460
|
-
pending.timeout =
|
|
609
|
+
pending.timeout = this.clock.after(this.options.requestTimeout, () => {
|
|
461
610
|
if (this.pendingRequests.get(wireId) !== pending)
|
|
462
611
|
return;
|
|
463
612
|
this.pendingRequests.delete(wireId);
|
|
@@ -467,14 +616,13 @@ export class WebSocketTransport {
|
|
|
467
616
|
// so a retry middleware must not repeat it (deep dive TRN-8)
|
|
468
617
|
queue.push(errorItem(message.id, "TIMEOUT", `Request timeout after ${this.options.requestTimeout}ms`, false));
|
|
469
618
|
queue.end();
|
|
470
|
-
}
|
|
619
|
+
});
|
|
471
620
|
this.pendingRequests.set(wireId, pending);
|
|
472
621
|
const onAbort = () => {
|
|
473
622
|
if (this.pendingRequests.get(wireId) !== pending)
|
|
474
623
|
return;
|
|
475
624
|
this.pendingRequests.delete(wireId);
|
|
476
|
-
|
|
477
|
-
clearTimeout(pending.timeout);
|
|
625
|
+
pending.timeout?.();
|
|
478
626
|
this.sendCancel(wireId);
|
|
479
627
|
queue.push(abortedItem(message.id));
|
|
480
628
|
queue.end();
|
|
@@ -500,8 +648,7 @@ export class WebSocketTransport {
|
|
|
500
648
|
}
|
|
501
649
|
finally {
|
|
502
650
|
message.signal?.removeEventListener("abort", onAbort);
|
|
503
|
-
|
|
504
|
-
clearTimeout(pending.timeout);
|
|
651
|
+
pending.timeout?.();
|
|
505
652
|
if (this.pendingRequests.get(wireId) === pending) {
|
|
506
653
|
// The reader stopped before the end: the server stops the stream
|
|
507
654
|
this.pendingRequests.delete(wireId);
|
|
@@ -537,7 +684,7 @@ export class WebSocketTransport {
|
|
|
537
684
|
}
|
|
538
685
|
return new Promise((resolve, reject) => {
|
|
539
686
|
const finish = (error) => {
|
|
540
|
-
|
|
687
|
+
timeout();
|
|
541
688
|
this.stateListeners.delete(check);
|
|
542
689
|
signal?.removeEventListener("abort", onAbort);
|
|
543
690
|
if (error)
|
|
@@ -553,7 +700,7 @@ export class WebSocketTransport {
|
|
|
553
700
|
};
|
|
554
701
|
// The caller aborted: send() then gives the ABORTED item
|
|
555
702
|
const onAbort = () => finish();
|
|
556
|
-
const timeout =
|
|
703
|
+
const timeout = this.clock.after(this.options.connectionTimeout, () => finish(new Error("Connection timeout")));
|
|
557
704
|
this.stateListeners.add(check);
|
|
558
705
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
559
706
|
});
|
|
@@ -609,28 +756,35 @@ export class WebSocketTransport {
|
|
|
609
756
|
*/
|
|
610
757
|
async close() {
|
|
611
758
|
this.closing = true;
|
|
612
|
-
this.setState(WebSocketState.DISCONNECTING);
|
|
759
|
+
this.setState(WebSocketState.DISCONNECTING, "close");
|
|
613
760
|
this.stopHeartbeat();
|
|
761
|
+
this.clearHandshakeTimer();
|
|
762
|
+
this.incoming?.abortAll();
|
|
763
|
+
this.incoming = undefined;
|
|
614
764
|
if (this.reconnectTimer) {
|
|
615
|
-
|
|
765
|
+
this.reconnectTimer();
|
|
616
766
|
this.reconnectTimer = undefined;
|
|
617
767
|
}
|
|
618
768
|
const hadSocket = this.ws !== null;
|
|
619
769
|
if (this.ws) {
|
|
620
770
|
const ws = this.ws;
|
|
621
771
|
this.ws = null;
|
|
622
|
-
ws
|
|
623
|
-
ws.onmessage = null;
|
|
624
|
-
ws.onclose = null;
|
|
625
|
-
ws.onerror = null;
|
|
772
|
+
detach(ws);
|
|
626
773
|
ws.close();
|
|
627
774
|
}
|
|
628
775
|
this.failPending("WebSocket connection closed");
|
|
629
|
-
this.setState(WebSocketState.DISCONNECTED);
|
|
776
|
+
this.setState(WebSocketState.DISCONNECTED, "closed");
|
|
630
777
|
if (hadSocket && this.options.onDisconnect) {
|
|
631
778
|
this.options.onDisconnect("closed");
|
|
632
779
|
}
|
|
633
780
|
}
|
|
781
|
+
/**
|
|
782
|
+
* The protocol version of the server: from its hello, or undefined before the hello or for a
|
|
783
|
+
* server older than protocol v1 (roadmap R25).
|
|
784
|
+
*/
|
|
785
|
+
getServerProtocol() {
|
|
786
|
+
return this.serverProtocol;
|
|
787
|
+
}
|
|
634
788
|
/**
|
|
635
789
|
* Get current connection state.
|
|
636
790
|
*/
|