@procedures/client 0.2.3 → 0.3.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 +65 -0
- package/README.md +61 -38
- 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 +53 -20
- 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 +63 -4
- 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/transport.d.ts +47 -7
- package/dist/adapters/websocket/client/transport.d.ts.map +1 -1
- package/dist/adapters/websocket/client/transport.js +213 -67
- package/dist/adapters/websocket/client/transport.js.map +1 -1
- package/dist/adapters/websocket/client/types.d.ts +70 -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/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 -6
- 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 +46 -37
- 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/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/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.map +1 -1
- package/dist/procedures/core/array.js +26 -15
- 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 +8 -0
- package/dist/procedures/core/index.js.map +1 -1
- package/dist/procedures/core/meta.d.ts.map +1 -1
- package/dist/procedures/core/meta.js +2 -1
- 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/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/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/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 +40 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +58 -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 +110 -4
- package/dist/server/procedure-server.d.ts.map +1 -1
- package/dist/server/procedure-server.js +269 -17
- 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 +7 -1
- 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 +60 -24
- package/src/adapters/http/shared/headers.ts +7 -6
- package/src/adapters/local/client/transport.ts +70 -5
- 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/transport.ts +232 -73
- package/src/adapters/websocket/client/types.ts +80 -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/factory.ts +12 -8
- 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 -7
- package/src/client/middleware/resilience.ts +81 -0
- package/src/client/middleware/retry.ts +146 -34
- package/src/client/middleware/timeout.ts +55 -46
- 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/machines.ts +186 -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 +27 -16
- package/src/procedures/core/index.ts +8 -0
- package/src/procedures/core/meta.ts +2 -1
- 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/define-procedure.ts +73 -20
- 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/runtime-programs.ts +27 -0
- package/src/procedures/types.ts +16 -0
- package/src/protocol.ts +97 -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 +324 -10
- 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
|
@@ -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?:
|
|
78
|
+
timeout?: Cancel | undefined;
|
|
68
79
|
}
|
|
69
80
|
|
|
70
81
|
/**
|
|
@@ -93,6 +104,59 @@ function abortedItem<TRes>(id: string): ResponseItem<TRes> {
|
|
|
93
104
|
return errorItem<TRes>(id, "ABORTED", "Request was aborted", false);
|
|
94
105
|
}
|
|
95
106
|
|
|
107
|
+
/**
|
|
108
|
+
* The handler of the calls of the server without a `serve` option: the older `onServerRequest`
|
|
109
|
+
* function (it gets the path and the input), or else a `NOT_FOUND` answer.
|
|
110
|
+
*/
|
|
111
|
+
function legacyHandler(onServerRequest: ServerRequestHandler | undefined): RequestHandler {
|
|
112
|
+
return {
|
|
113
|
+
async handle(request): Promise<ServerResponse> {
|
|
114
|
+
if (!onServerRequest) {
|
|
115
|
+
return {
|
|
116
|
+
id: request.id,
|
|
117
|
+
status: { type: "error", code: "NOT_FOUND", message: "This client serves no procedures", retryable: false },
|
|
118
|
+
metadata: {},
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
const payload = await onServerRequest(methodToPath(request.method), request.payload);
|
|
123
|
+
return { id: request.id, status: { type: "success", code: 200 }, payload, metadata: {} };
|
|
124
|
+
} catch (error) {
|
|
125
|
+
return {
|
|
126
|
+
id: request.id,
|
|
127
|
+
status: { type: "error", code: "HANDLER_ERROR", message: error instanceof Error ? error.message : String(error), retryable: false },
|
|
128
|
+
metadata: {},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* The delay before a reconnect try (from 1): an exponential backoff up to `maxDelay`, with a
|
|
137
|
+
* random part. With `jitter: 1` (full jitter, the default), the delay is a random time from 0 to
|
|
138
|
+
* the backoff, so the clients of a server that restarted do not come back at the same moments
|
|
139
|
+
* (roadmap R3). The backoff is `backoffDelay` of `@mark1russell7/service/backoff` (roadmap R37).
|
|
140
|
+
*/
|
|
141
|
+
export function reconnectDelay(
|
|
142
|
+
attempt: number,
|
|
143
|
+
options: { initialDelay: number; backoffMultiplier: number; maxDelay: number; jitter: number },
|
|
144
|
+
random: () => number = Math.random,
|
|
145
|
+
): number {
|
|
146
|
+
return Math.round(
|
|
147
|
+
backoffDelay(
|
|
148
|
+
{
|
|
149
|
+
base: options.initialDelay,
|
|
150
|
+
factor: options.backoffMultiplier,
|
|
151
|
+
cap: options.maxDelay,
|
|
152
|
+
jitter: { kind: "subtract", fraction: options.jitter },
|
|
153
|
+
},
|
|
154
|
+
attempt,
|
|
155
|
+
random,
|
|
156
|
+
),
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
96
160
|
export class WebSocketTransport implements Transport {
|
|
97
161
|
readonly name = "websocket";
|
|
98
162
|
|
|
@@ -106,6 +170,7 @@ export class WebSocketTransport implements Transport {
|
|
|
106
170
|
initialDelay: number;
|
|
107
171
|
maxDelay: number;
|
|
108
172
|
backoffMultiplier: number;
|
|
173
|
+
jitter: number;
|
|
109
174
|
};
|
|
110
175
|
connectionTimeout: number;
|
|
111
176
|
requestTimeout: number;
|
|
@@ -114,12 +179,14 @@ export class WebSocketTransport implements Transport {
|
|
|
114
179
|
enabled: boolean;
|
|
115
180
|
interval: number;
|
|
116
181
|
timeout: number;
|
|
182
|
+
misses: number;
|
|
117
183
|
};
|
|
118
184
|
onConnect: (() => void) | undefined;
|
|
119
185
|
onDisconnect: ((reason?: string) => void) | undefined;
|
|
120
186
|
onReconnecting: ((attempt: number) => void) | undefined;
|
|
121
187
|
onError: ((error: Error) => void) | undefined;
|
|
122
188
|
onServerRequest: ServerRequestHandler | undefined;
|
|
189
|
+
serve: RequestHandler | undefined;
|
|
123
190
|
onEvent: EventHandler | undefined;
|
|
124
191
|
};
|
|
125
192
|
/** The requests in progress, by wire id */
|
|
@@ -135,9 +202,21 @@ export class WebSocketTransport implements Transport {
|
|
|
135
202
|
/** Set by close(): the connection is being closed on purpose, so it must not reconnect */
|
|
136
203
|
private closing = false;
|
|
137
204
|
private reconnectAttempts = 0;
|
|
138
|
-
private reconnectTimer:
|
|
139
|
-
|
|
140
|
-
private
|
|
205
|
+
private reconnectTimer: Cancel | undefined = undefined;
|
|
206
|
+
/** The heartbeat of the connection: `heartbeat` of `@mark1russell7/service/heartbeat` (roadmap R37) */
|
|
207
|
+
private heartbeat: Heartbeat | undefined = undefined;
|
|
208
|
+
/** The clock of each timer of the transport (roadmap R35) */
|
|
209
|
+
private readonly clock: Clock;
|
|
210
|
+
/** The class of the sockets, or undefined for the global `WebSocket` (finding of roadmap R36) */
|
|
211
|
+
private readonly webSocketClass: WebSocketClass | undefined;
|
|
212
|
+
/** The observer of the changes of the state (roadmap R35) */
|
|
213
|
+
private readonly transitionHandler: ((transition: MachineTransition) => void) | undefined;
|
|
214
|
+
/** The protocol version of the server, from its hello, or undefined (an older server) (R25) */
|
|
215
|
+
private serverProtocol: number | undefined;
|
|
216
|
+
/** The calls that the server sends on the current socket, served by `serve` (roadmap R24) */
|
|
217
|
+
private incoming: IncomingCalls | undefined;
|
|
218
|
+
/** The time limit of the handshake of the socket that opens now */
|
|
219
|
+
private handshakeTimer: Cancel | undefined = undefined;
|
|
141
220
|
|
|
142
221
|
constructor(options: WebSocketTransportOptions) {
|
|
143
222
|
this.options = {
|
|
@@ -145,40 +224,54 @@ export class WebSocketTransport implements Transport {
|
|
|
145
224
|
reconnect: {
|
|
146
225
|
enabled: options.reconnect?.enabled ?? true,
|
|
147
226
|
maxAttempts: options.reconnect?.maxAttempts ?? Infinity,
|
|
148
|
-
initialDelay: options.reconnect?.initialDelay ??
|
|
149
|
-
maxDelay: options.reconnect?.maxDelay ??
|
|
227
|
+
initialDelay: optionalMs(options.reconnect?.initialDelay, "reconnect.initialDelay") ?? TIMER_DEFAULTS.webSocketClient.reconnectInitialDelayMs,
|
|
228
|
+
maxDelay: optionalMs(options.reconnect?.maxDelay, "reconnect.maxDelay") ?? TIMER_DEFAULTS.webSocketClient.reconnectMaxDelayMs,
|
|
150
229
|
backoffMultiplier: options.reconnect?.backoffMultiplier ?? 1.5,
|
|
230
|
+
jitter: Math.min(1, Math.max(0, options.reconnect?.jitter ?? 1)),
|
|
151
231
|
},
|
|
152
|
-
connectionTimeout: options.connectionTimeout ??
|
|
153
|
-
requestTimeout: options.requestTimeout ??
|
|
232
|
+
connectionTimeout: optionalMs(options.connectionTimeout, "connectionTimeout") ?? TIMER_DEFAULTS.webSocketClient.connectionTimeoutMs,
|
|
233
|
+
requestTimeout: optionalMs(options.requestTimeout, "requestTimeout") ?? TIMER_DEFAULTS.webSocketClient.requestTimeoutMs,
|
|
154
234
|
streamWindow: Math.max(2, options.streamWindow ?? 64),
|
|
155
235
|
heartbeat: {
|
|
156
236
|
enabled: options.heartbeat?.enabled ?? true,
|
|
157
|
-
interval: options.heartbeat?.interval ??
|
|
158
|
-
timeout: options.heartbeat?.timeout ??
|
|
237
|
+
interval: optionalMs(options.heartbeat?.interval, "heartbeat.interval") ?? TIMER_DEFAULTS.webSocketClient.heartbeatIntervalMs,
|
|
238
|
+
timeout: optionalMs(options.heartbeat?.timeout, "heartbeat.timeout") ?? TIMER_DEFAULTS.webSocketClient.heartbeatTimeoutMs,
|
|
239
|
+
misses: Math.max(1, options.heartbeat?.misses ?? TIMER_DEFAULTS.webSocketClient.heartbeatMisses),
|
|
159
240
|
},
|
|
160
241
|
onConnect: options.onConnect,
|
|
161
242
|
onDisconnect: options.onDisconnect,
|
|
162
243
|
onReconnecting: options.onReconnecting,
|
|
163
244
|
onError: options.onError,
|
|
164
245
|
onServerRequest: options.onServerRequest,
|
|
246
|
+
serve: options.serve,
|
|
165
247
|
onEvent: options.onEvent,
|
|
166
248
|
};
|
|
249
|
+
this.clock = options.clock ?? realClock;
|
|
250
|
+
this.webSocketClass = options.webSocket;
|
|
251
|
+
this.transitionHandler = options.onTransition;
|
|
167
252
|
|
|
168
253
|
// Auto-connect
|
|
169
254
|
this.connect();
|
|
170
255
|
}
|
|
171
256
|
|
|
172
|
-
/**
|
|
173
|
-
|
|
257
|
+
/**
|
|
258
|
+
* Change the state, and tell the callers that wait for a connection. `on` is the event of
|
|
259
|
+
* `WEBSOCKET_CONNECTION_MACHINE`: a change goes to `onTransition` (roadmap R35).
|
|
260
|
+
*/
|
|
261
|
+
private setState(state: WebSocketState, on: string): void {
|
|
262
|
+
const from = this.state;
|
|
174
263
|
this.state = state;
|
|
264
|
+
if (from !== state) {
|
|
265
|
+
this.transitionHandler?.({ machine: WEBSOCKET_CONNECTION_MACHINE.id, instance: this.name, from, on, to: state, at: this.clock.now() });
|
|
266
|
+
}
|
|
175
267
|
for (const listener of [...this.stateListeners]) listener();
|
|
176
268
|
}
|
|
177
269
|
|
|
178
270
|
/**
|
|
179
271
|
* Connect to WebSocket server.
|
|
180
272
|
*/
|
|
181
|
-
|
|
273
|
+
/** `on` is the event of the machine: the first connect, or the end of a backoff. */
|
|
274
|
+
private connect(on: "connect" | "reconnect-due" = "connect"): void {
|
|
182
275
|
if (this.closing) {
|
|
183
276
|
return;
|
|
184
277
|
}
|
|
@@ -186,17 +279,36 @@ export class WebSocketTransport implements Transport {
|
|
|
186
279
|
return;
|
|
187
280
|
}
|
|
188
281
|
|
|
189
|
-
this.setState(WebSocketState.CONNECTING);
|
|
282
|
+
this.setState(WebSocketState.CONNECTING, on);
|
|
190
283
|
|
|
191
284
|
try {
|
|
192
|
-
const
|
|
285
|
+
const WebSocketClass = (this.webSocketClass ?? WebSocket) as typeof WebSocket;
|
|
286
|
+
const ws = new WebSocketClass(this.options.url);
|
|
193
287
|
this.ws = ws;
|
|
288
|
+
// The calls of the server on this socket (roadmap R24)
|
|
289
|
+
this.incoming = this.servedCallsOf(ws);
|
|
290
|
+
|
|
291
|
+
// The handshake has a time limit: an address that never answers does not keep the socket
|
|
292
|
+
// in CONNECTING (roadmap R3)
|
|
293
|
+
this.clearHandshakeTimer();
|
|
294
|
+
this.handshakeTimer = this.clock.after(this.options.connectionTimeout, () => {
|
|
295
|
+
this.handshakeTimer = undefined;
|
|
296
|
+
if (this.ws === ws && this.state === WebSocketState.CONNECTING) {
|
|
297
|
+
console.error(`[${this.name}] Handshake timeout after ${this.options.connectionTimeout}ms`);
|
|
298
|
+
this.dropConnection("Handshake timeout");
|
|
299
|
+
}
|
|
300
|
+
});
|
|
194
301
|
|
|
195
302
|
// Connection opened
|
|
196
303
|
ws.onopen = () => {
|
|
197
304
|
if (this.ws !== ws) return;
|
|
305
|
+
this.clearHandshakeTimer();
|
|
198
306
|
this.reconnectAttempts = 0;
|
|
199
|
-
this
|
|
307
|
+
// The protocol version of this client (roadmap R25). An older server answers with an
|
|
308
|
+
// error frame, which the client ignores.
|
|
309
|
+
this.serverProtocol = undefined;
|
|
310
|
+
ws.send(JSON.stringify({ id: "hello", type: "hello", protocol: PROTOCOL_VERSION }));
|
|
311
|
+
this.setState(WebSocketState.CONNECTED, "open");
|
|
200
312
|
console.error(`[${this.name}] Connected to ${this.options.url}`);
|
|
201
313
|
|
|
202
314
|
if (this.options.onConnect) {
|
|
@@ -235,6 +347,10 @@ export class WebSocketTransport implements Transport {
|
|
|
235
347
|
if (this.options.onError) {
|
|
236
348
|
this.options.onError(error);
|
|
237
349
|
}
|
|
350
|
+
// A failed handshake: the WebSocket of Node 22 gives no close event after this error, and
|
|
351
|
+
// stays in CONNECTING. The transport closes it now, so the reconnect starts at once
|
|
352
|
+
// (roadmap R30: before, each try waited for the time limit of the handshake).
|
|
353
|
+
if (this.state === WebSocketState.CONNECTING) this.dropConnection("Connection failed");
|
|
238
354
|
};
|
|
239
355
|
} catch (error) {
|
|
240
356
|
console.error(`[${this.name}] Failed to create WebSocket:`, error);
|
|
@@ -257,7 +373,11 @@ export class WebSocketTransport implements Transport {
|
|
|
257
373
|
ws.onclose = null;
|
|
258
374
|
ws.onerror = null;
|
|
259
375
|
try {
|
|
260
|
-
ws.close()
|
|
376
|
+
// A socket of the ws package ends at once. close() waits for the answer of a peer that can
|
|
377
|
+
// be dead (finding of roadmap R36).
|
|
378
|
+
const terminate = (ws as { terminate?: () => void }).terminate;
|
|
379
|
+
if (typeof terminate === "function") terminate.call(ws);
|
|
380
|
+
else ws.close();
|
|
261
381
|
} catch {
|
|
262
382
|
// The socket is unusable: nothing more to do
|
|
263
383
|
}
|
|
@@ -269,9 +389,7 @@ export class WebSocketTransport implements Transport {
|
|
|
269
389
|
private failPending(reason: string): void {
|
|
270
390
|
for (const pending of this.pendingRequests.values()) {
|
|
271
391
|
pending.queue.fail(new Error(reason));
|
|
272
|
-
|
|
273
|
-
clearTimeout(pending.timeout);
|
|
274
|
-
}
|
|
392
|
+
pending.timeout?.();
|
|
275
393
|
}
|
|
276
394
|
this.pendingRequests.clear();
|
|
277
395
|
}
|
|
@@ -281,11 +399,14 @@ export class WebSocketTransport implements Transport {
|
|
|
281
399
|
*/
|
|
282
400
|
private handleClose(reason?: string): void {
|
|
283
401
|
this.stopHeartbeat();
|
|
402
|
+
this.incoming?.abortAll();
|
|
403
|
+
this.incoming = undefined;
|
|
404
|
+
this.clearHandshakeTimer();
|
|
284
405
|
|
|
285
406
|
// An intentional close() must not reconnect (BUGS-2026-07 H6)
|
|
286
407
|
const reconnect = this.options.reconnect;
|
|
287
408
|
const willReconnect = !this.closing && reconnect.enabled && this.reconnectAttempts < reconnect.maxAttempts;
|
|
288
|
-
this.setState(willReconnect ? WebSocketState.RECONNECTING : WebSocketState.DISCONNECTED);
|
|
409
|
+
this.setState(willReconnect ? WebSocketState.RECONNECTING : WebSocketState.DISCONNECTED, willReconnect ? "drop" : "give-up");
|
|
289
410
|
|
|
290
411
|
console.error(`[${this.name}] Disconnected${reason ? `: ${reason}` : ""}`);
|
|
291
412
|
|
|
@@ -305,17 +426,13 @@ export class WebSocketTransport implements Transport {
|
|
|
305
426
|
*/
|
|
306
427
|
private scheduleReconnect(): void {
|
|
307
428
|
if (this.reconnectTimer) {
|
|
308
|
-
|
|
429
|
+
this.reconnectTimer();
|
|
309
430
|
}
|
|
310
431
|
|
|
311
432
|
this.reconnectAttempts++;
|
|
312
|
-
this.setState(WebSocketState.RECONNECTING);
|
|
433
|
+
this.setState(WebSocketState.RECONNECTING, "drop");
|
|
313
434
|
|
|
314
|
-
const
|
|
315
|
-
const delay = Math.min(
|
|
316
|
-
reconnect.initialDelay * Math.pow(reconnect.backoffMultiplier, this.reconnectAttempts - 1),
|
|
317
|
-
reconnect.maxDelay
|
|
318
|
-
);
|
|
435
|
+
const delay = reconnectDelay(this.reconnectAttempts, this.options.reconnect);
|
|
319
436
|
|
|
320
437
|
console.error(`[${this.name}] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
|
|
321
438
|
|
|
@@ -323,64 +440,95 @@ export class WebSocketTransport implements Transport {
|
|
|
323
440
|
this.options.onReconnecting(this.reconnectAttempts);
|
|
324
441
|
}
|
|
325
442
|
|
|
326
|
-
this.reconnectTimer =
|
|
443
|
+
this.reconnectTimer = this.clock.after(delay, () => {
|
|
327
444
|
this.reconnectTimer = undefined;
|
|
328
445
|
// connect() returns at once in RECONNECTING only for CONNECTING/CONNECTED
|
|
329
|
-
this.connect();
|
|
330
|
-
}
|
|
446
|
+
this.connect("reconnect-due");
|
|
447
|
+
});
|
|
331
448
|
}
|
|
332
449
|
|
|
333
450
|
/**
|
|
334
|
-
* Start heartbeat
|
|
451
|
+
* Start the heartbeat of the connection: a ping each interval on the clock, one ping at a time.
|
|
452
|
+
* A ping without a pong at its timeout is a miss. The connection drops after `misses` misses in
|
|
453
|
+
* sequence. A ping whose send throws is a miss too.
|
|
335
454
|
*/
|
|
336
455
|
private startHeartbeat(): void {
|
|
337
456
|
this.stopHeartbeat();
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
457
|
+
this.heartbeat = heartbeat({
|
|
458
|
+
interval: this.options.heartbeat.interval,
|
|
459
|
+
timeout: this.options.heartbeat.timeout,
|
|
460
|
+
misses: this.options.heartbeat.misses,
|
|
461
|
+
clock: this.clock,
|
|
462
|
+
due: () => this.state === WebSocketState.CONNECTED && this.ws !== null,
|
|
463
|
+
ping: () => {
|
|
343
464
|
const ping: WebSocketMessage = {
|
|
344
465
|
id: `ping-${Date.now()}`,
|
|
345
466
|
type: "ping",
|
|
346
467
|
};
|
|
347
|
-
this.ws
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
this.
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
}, this.options.heartbeat.timeout);
|
|
355
|
-
}
|
|
356
|
-
}, this.options.heartbeat.interval);
|
|
468
|
+
this.ws?.send(JSON.stringify(ping));
|
|
469
|
+
},
|
|
470
|
+
onDead: () => {
|
|
471
|
+
console.warn(`[${this.name}] Heartbeat timeout - dropping connection`);
|
|
472
|
+
this.dropConnection("Heartbeat timeout");
|
|
473
|
+
},
|
|
474
|
+
});
|
|
357
475
|
}
|
|
358
476
|
|
|
359
|
-
/**
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
clearInterval(this.heartbeatTimer);
|
|
365
|
-
this.heartbeatTimer = undefined;
|
|
366
|
-
}
|
|
367
|
-
if (this.heartbeatTimeout) {
|
|
368
|
-
clearTimeout(this.heartbeatTimeout);
|
|
369
|
-
this.heartbeatTimeout = undefined;
|
|
477
|
+
/** Stop the time limit of the handshake. */
|
|
478
|
+
private clearHandshakeTimer(): void {
|
|
479
|
+
if (this.handshakeTimer) {
|
|
480
|
+
this.handshakeTimer();
|
|
481
|
+
this.handshakeTimer = undefined;
|
|
370
482
|
}
|
|
371
483
|
}
|
|
372
484
|
|
|
485
|
+
/** Stop the heartbeat: its ticks, its timeout and its count of misses. */
|
|
486
|
+
private stopHeartbeat(): void {
|
|
487
|
+
this.heartbeat?.stop();
|
|
488
|
+
this.heartbeat = undefined;
|
|
489
|
+
}
|
|
490
|
+
|
|
373
491
|
/**
|
|
374
492
|
* Handle incoming message.
|
|
375
493
|
*/
|
|
494
|
+
/**
|
|
495
|
+
* The calls of the server on a socket, served by the `serve` option (or by `onServerRequest`, or
|
|
496
|
+
* with `NOT_FOUND`), with the same frames as the calls of the client (roadmap R24).
|
|
497
|
+
*/
|
|
498
|
+
private servedCallsOf(ws: WebSocket): IncomingCalls {
|
|
499
|
+
const handler = this.options.serve ?? legacyHandler(this.options.onServerRequest);
|
|
500
|
+
return new IncomingCalls({
|
|
501
|
+
handler,
|
|
502
|
+
send: (frame) => ws.send(JSON.stringify(frame)),
|
|
503
|
+
isOpen: () => this.ws === ws && ws.readyState === WebSocket.OPEN,
|
|
504
|
+
// A call of the server that this client opened: the client trusts it, so it has no
|
|
505
|
+
// credentials (a ProcedureServer applies no token to it)
|
|
506
|
+
requestOf: (frame, signal) => ({
|
|
507
|
+
id: frame.id,
|
|
508
|
+
method: frame.method!,
|
|
509
|
+
payload: frame.payload === undefined ? {} : frame.payload,
|
|
510
|
+
metadata: withoutInternalKeys(frame.metadata),
|
|
511
|
+
signal,
|
|
512
|
+
}),
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
|
|
376
516
|
private handleMessage(message: WebSocketMessage): void {
|
|
517
|
+
// The hello of the server, or the error of an older server that does not know it (R25)
|
|
518
|
+
if (message.id === "hello" && (message.type === "hello" || message.type === "error")) {
|
|
519
|
+
if (message.type === "hello") this.serverProtocol = protocolVersionOf(message.protocol);
|
|
520
|
+
else if (message.error?.code === "PROTOCOL_VERSION") this.options.onError?.(new Error(message.error.message));
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
// A call of the server, its cancel or its credit (roadmap R24)
|
|
524
|
+
if ((message.type === "request" || message.type === "cancel" || message.type === "credit") && this.incoming?.receive(message)) {
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
|
|
377
528
|
|
|
378
529
|
// Handle pong (heartbeat response)
|
|
379
530
|
if (message.type === "pong") {
|
|
380
|
-
|
|
381
|
-
clearTimeout(this.heartbeatTimeout);
|
|
382
|
-
this.heartbeatTimeout = undefined;
|
|
383
|
-
}
|
|
531
|
+
this.heartbeat?.answer();
|
|
384
532
|
return;
|
|
385
533
|
}
|
|
386
534
|
|
|
@@ -421,7 +569,7 @@ export class WebSocketTransport implements Transport {
|
|
|
421
569
|
|
|
422
570
|
// The first frame arrived: the request timeout ends. A stream can stay open longer.
|
|
423
571
|
if (pending.timeout) {
|
|
424
|
-
|
|
572
|
+
pending.timeout();
|
|
425
573
|
pending.timeout = undefined;
|
|
426
574
|
}
|
|
427
575
|
|
|
@@ -539,7 +687,7 @@ export class WebSocketTransport implements Transport {
|
|
|
539
687
|
|
|
540
688
|
const queue = new ItemQueue<ResponseItem<unknown>>();
|
|
541
689
|
const pending: PendingRequest = { messageId: message.id, queue };
|
|
542
|
-
pending.timeout =
|
|
690
|
+
pending.timeout = this.clock.after(this.options.requestTimeout, () => {
|
|
543
691
|
if (this.pendingRequests.get(wireId) !== pending) return;
|
|
544
692
|
this.pendingRequests.delete(wireId);
|
|
545
693
|
this.sendCancel(wireId);
|
|
@@ -548,13 +696,13 @@ export class WebSocketTransport implements Transport {
|
|
|
548
696
|
// so a retry middleware must not repeat it (deep dive TRN-8)
|
|
549
697
|
queue.push(errorItem(message.id, "TIMEOUT", `Request timeout after ${this.options.requestTimeout}ms`, false));
|
|
550
698
|
queue.end();
|
|
551
|
-
}
|
|
699
|
+
});
|
|
552
700
|
this.pendingRequests.set(wireId, pending);
|
|
553
701
|
|
|
554
702
|
const onAbort = (): void => {
|
|
555
703
|
if (this.pendingRequests.get(wireId) !== pending) return;
|
|
556
704
|
this.pendingRequests.delete(wireId);
|
|
557
|
-
|
|
705
|
+
pending.timeout?.();
|
|
558
706
|
this.sendCancel(wireId);
|
|
559
707
|
queue.push(abortedItem(message.id));
|
|
560
708
|
queue.end();
|
|
@@ -579,7 +727,7 @@ export class WebSocketTransport implements Transport {
|
|
|
579
727
|
}
|
|
580
728
|
} finally {
|
|
581
729
|
message.signal?.removeEventListener("abort", onAbort);
|
|
582
|
-
|
|
730
|
+
pending.timeout?.();
|
|
583
731
|
if (this.pendingRequests.get(wireId) === pending) {
|
|
584
732
|
// The reader stopped before the end: the server stops the stream
|
|
585
733
|
this.pendingRequests.delete(wireId);
|
|
@@ -619,7 +767,7 @@ export class WebSocketTransport implements Transport {
|
|
|
619
767
|
|
|
620
768
|
return new Promise((resolve, reject) => {
|
|
621
769
|
const finish = (error?: Error): void => {
|
|
622
|
-
|
|
770
|
+
timeout();
|
|
623
771
|
this.stateListeners.delete(check);
|
|
624
772
|
signal?.removeEventListener("abort", onAbort);
|
|
625
773
|
if (error) reject(error);
|
|
@@ -631,7 +779,7 @@ export class WebSocketTransport implements Transport {
|
|
|
631
779
|
};
|
|
632
780
|
// The caller aborted: send() then gives the ABORTED item
|
|
633
781
|
const onAbort = (): void => finish();
|
|
634
|
-
const timeout =
|
|
782
|
+
const timeout = this.clock.after(this.options.connectionTimeout, () => finish(new Error("Connection timeout")));
|
|
635
783
|
this.stateListeners.add(check);
|
|
636
784
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
637
785
|
});
|
|
@@ -691,11 +839,14 @@ export class WebSocketTransport implements Transport {
|
|
|
691
839
|
*/
|
|
692
840
|
async close(): Promise<void> {
|
|
693
841
|
this.closing = true;
|
|
694
|
-
this.setState(WebSocketState.DISCONNECTING);
|
|
842
|
+
this.setState(WebSocketState.DISCONNECTING, "close");
|
|
695
843
|
this.stopHeartbeat();
|
|
844
|
+
this.clearHandshakeTimer();
|
|
845
|
+
this.incoming?.abortAll();
|
|
846
|
+
this.incoming = undefined;
|
|
696
847
|
|
|
697
848
|
if (this.reconnectTimer) {
|
|
698
|
-
|
|
849
|
+
this.reconnectTimer();
|
|
699
850
|
this.reconnectTimer = undefined;
|
|
700
851
|
}
|
|
701
852
|
|
|
@@ -711,12 +862,20 @@ export class WebSocketTransport implements Transport {
|
|
|
711
862
|
}
|
|
712
863
|
|
|
713
864
|
this.failPending("WebSocket connection closed");
|
|
714
|
-
this.setState(WebSocketState.DISCONNECTED);
|
|
865
|
+
this.setState(WebSocketState.DISCONNECTED, "closed");
|
|
715
866
|
if (hadSocket && this.options.onDisconnect) {
|
|
716
867
|
this.options.onDisconnect("closed");
|
|
717
868
|
}
|
|
718
869
|
}
|
|
719
870
|
|
|
871
|
+
/**
|
|
872
|
+
* The protocol version of the server: from its hello, or undefined before the hello or for a
|
|
873
|
+
* server older than protocol v1 (roadmap R25).
|
|
874
|
+
*/
|
|
875
|
+
getServerProtocol(): number | undefined {
|
|
876
|
+
return this.serverProtocol;
|
|
877
|
+
}
|
|
878
|
+
|
|
720
879
|
/**
|
|
721
880
|
* Get current connection state.
|
|
722
881
|
*/
|