@inline-chat/realtime-sdk 0.0.2 → 0.0.3
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.
|
@@ -9,6 +9,7 @@ export type ProtocolClientOptions = {
|
|
|
9
9
|
transport: Transport;
|
|
10
10
|
getConnectionInit: () => ConnectionInit | null;
|
|
11
11
|
logger?: InlineSdkLogger;
|
|
12
|
+
defaultRpcTimeoutMs?: number | null;
|
|
12
13
|
};
|
|
13
14
|
export declare class ProtocolClient {
|
|
14
15
|
readonly events: AsyncChannel<ClientEvent>;
|
|
@@ -17,7 +18,8 @@ export declare class ProtocolClient {
|
|
|
17
18
|
state: ClientState;
|
|
18
19
|
private readonly log;
|
|
19
20
|
private readonly getConnectionInit;
|
|
20
|
-
private
|
|
21
|
+
private readonly defaultRpcTimeoutMs;
|
|
22
|
+
private pendingRpcRequests;
|
|
21
23
|
private seq;
|
|
22
24
|
private lastTimestamp;
|
|
23
25
|
private sequence;
|
|
@@ -35,7 +37,7 @@ export declare class ProtocolClient {
|
|
|
35
37
|
}): Promise<void>;
|
|
36
38
|
sendRpc(method: Method, input?: RpcCall["input"]): Promise<bigint>;
|
|
37
39
|
callRpc(method: Method, input?: RpcCall["input"], options?: {
|
|
38
|
-
timeoutMs?: number;
|
|
40
|
+
timeoutMs?: number | null;
|
|
39
41
|
}): Promise<RpcResult["result"]>;
|
|
40
42
|
private startListeners;
|
|
41
43
|
private handleTransportMessage;
|
|
@@ -53,10 +55,14 @@ export declare class ProtocolClient {
|
|
|
53
55
|
private generateId;
|
|
54
56
|
private currentTimestamp;
|
|
55
57
|
private completeRpcResult;
|
|
58
|
+
private ensureOpenForRpc;
|
|
56
59
|
private completeRpcError;
|
|
57
|
-
private
|
|
58
|
-
private
|
|
59
|
-
private
|
|
60
|
+
private failPendingRpcRequest;
|
|
61
|
+
private getAndRemovePendingRpcRequest;
|
|
62
|
+
private cancelAllPendingRpcRequests;
|
|
63
|
+
private resolveRpcTimeoutMs;
|
|
64
|
+
private resendPendingRpcRequests;
|
|
65
|
+
private trySendPendingRpcRequest;
|
|
60
66
|
}
|
|
61
67
|
export declare class ProtocolClientError extends Error {
|
|
62
68
|
constructor(code: "not-authorized" | "not-connected" | "rpc-error" | "stopped" | "timeout", details?: {
|
|
@@ -2,6 +2,7 @@ import { ClientMessage, ServerProtocolMessage } from "@inline-chat/protocol/core
|
|
|
2
2
|
import { AsyncChannel } from "../utils/async-channel.js";
|
|
3
3
|
import { PingPongService } from "./ping-pong.js";
|
|
4
4
|
const emptyRpcInput = { oneofKind: undefined };
|
|
5
|
+
const defaultRpcTimeoutMs = 30_000;
|
|
5
6
|
export class ProtocolClient {
|
|
6
7
|
events = new AsyncChannel();
|
|
7
8
|
transport;
|
|
@@ -9,7 +10,8 @@ export class ProtocolClient {
|
|
|
9
10
|
state = "connecting";
|
|
10
11
|
log;
|
|
11
12
|
getConnectionInit;
|
|
12
|
-
|
|
13
|
+
defaultRpcTimeoutMs;
|
|
14
|
+
pendingRpcRequests = new Map();
|
|
13
15
|
seq = 0;
|
|
14
16
|
lastTimestamp = 0;
|
|
15
17
|
sequence = 0;
|
|
@@ -22,6 +24,7 @@ export class ProtocolClient {
|
|
|
22
24
|
this.transport = options.transport;
|
|
23
25
|
this.log = options.logger ?? {};
|
|
24
26
|
this.getConnectionInit = options.getConnectionInit;
|
|
27
|
+
this.defaultRpcTimeoutMs = normalizeRpcTimeoutMs(options.defaultRpcTimeoutMs, defaultRpcTimeoutMs);
|
|
25
28
|
this.pingPong = new PingPongService({ logger: this.log });
|
|
26
29
|
this.pingPong.configure(this);
|
|
27
30
|
this.startListeners();
|
|
@@ -48,6 +51,7 @@ export class ProtocolClient {
|
|
|
48
51
|
await this.transport.reconnect({ skipDelay: options?.skipDelay });
|
|
49
52
|
}
|
|
50
53
|
async sendRpc(method, input = emptyRpcInput) {
|
|
54
|
+
this.ensureOpenForRpc();
|
|
51
55
|
const message = this.wrapMessage({
|
|
52
56
|
oneofKind: "rpcCall",
|
|
53
57
|
rpcCall: { method, input },
|
|
@@ -61,17 +65,20 @@ export class ProtocolClient {
|
|
|
61
65
|
rpcCall: { method, input },
|
|
62
66
|
});
|
|
63
67
|
return await new Promise((resolve, reject) => {
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
68
|
+
const pending = {
|
|
69
|
+
message,
|
|
70
|
+
resolve,
|
|
71
|
+
reject,
|
|
72
|
+
timeoutMs: this.resolveRpcTimeoutMs(options?.timeoutMs),
|
|
73
|
+
sending: false,
|
|
74
|
+
};
|
|
75
|
+
this.pendingRpcRequests.set(message.id, pending);
|
|
76
|
+
if (pending.timeoutMs !== null) {
|
|
77
|
+
pending.timeout = setTimeout(() => {
|
|
78
|
+
this.failPendingRpcRequest(message.id, new ProtocolClientError("timeout"));
|
|
79
|
+
}, pending.timeoutMs);
|
|
74
80
|
}
|
|
81
|
+
this.trySendPendingRpcRequest(message.id);
|
|
75
82
|
});
|
|
76
83
|
}
|
|
77
84
|
async startListeners() {
|
|
@@ -168,6 +175,7 @@ export class ProtocolClient {
|
|
|
168
175
|
}
|
|
169
176
|
this.connectionAttemptNo = 0;
|
|
170
177
|
this.pingPong.start();
|
|
178
|
+
this.resendPendingRpcRequests();
|
|
171
179
|
}
|
|
172
180
|
async connecting() {
|
|
173
181
|
this.state = "connecting";
|
|
@@ -176,7 +184,7 @@ export class ProtocolClient {
|
|
|
176
184
|
async reset() {
|
|
177
185
|
this.pingPong.stop();
|
|
178
186
|
this.stopAuthenticationTimeout();
|
|
179
|
-
this.
|
|
187
|
+
this.cancelAllPendingRpcRequests(new ProtocolClientError("stopped"));
|
|
180
188
|
this.state = "connecting";
|
|
181
189
|
}
|
|
182
190
|
startAuthenticationTimeout() {
|
|
@@ -195,8 +203,8 @@ export class ProtocolClient {
|
|
|
195
203
|
}
|
|
196
204
|
handleClientFailure() {
|
|
197
205
|
this.pingPong.stop();
|
|
198
|
-
this.cancelAllRpcContinuations(new ProtocolClientError("not-connected"));
|
|
199
206
|
this.stopAuthenticationTimeout();
|
|
207
|
+
this.state = "connecting";
|
|
200
208
|
if (this.reconnectionTimer) {
|
|
201
209
|
clearTimeout(this.reconnectionTimer);
|
|
202
210
|
}
|
|
@@ -239,36 +247,80 @@ export class ProtocolClient {
|
|
|
239
247
|
return Math.floor(Date.now() / 1000) - this.epochSeconds;
|
|
240
248
|
}
|
|
241
249
|
completeRpcResult(msgId, rpcResult) {
|
|
242
|
-
const
|
|
243
|
-
|
|
250
|
+
const pending = this.getAndRemovePendingRpcRequest(msgId);
|
|
251
|
+
pending?.resolve(rpcResult);
|
|
252
|
+
}
|
|
253
|
+
ensureOpenForRpc() {
|
|
254
|
+
if (this.state !== "open") {
|
|
255
|
+
throw new ProtocolClientError("not-connected");
|
|
256
|
+
}
|
|
244
257
|
}
|
|
245
258
|
completeRpcError(msgId, rpcError) {
|
|
246
259
|
const error = new ProtocolClientError("rpc-error", { code: rpcError.code, message: rpcError.message });
|
|
247
|
-
const
|
|
248
|
-
|
|
260
|
+
const pending = this.getAndRemovePendingRpcRequest(msgId);
|
|
261
|
+
pending?.reject(error);
|
|
249
262
|
}
|
|
250
|
-
|
|
251
|
-
const
|
|
252
|
-
|
|
263
|
+
failPendingRpcRequest(msgId, error) {
|
|
264
|
+
const pending = this.getAndRemovePendingRpcRequest(msgId);
|
|
265
|
+
pending?.reject(error);
|
|
253
266
|
}
|
|
254
|
-
|
|
255
|
-
const
|
|
256
|
-
if (!
|
|
267
|
+
getAndRemovePendingRpcRequest(msgId) {
|
|
268
|
+
const pending = this.pendingRpcRequests.get(msgId);
|
|
269
|
+
if (!pending)
|
|
257
270
|
return null;
|
|
258
|
-
if (
|
|
259
|
-
clearTimeout(
|
|
260
|
-
this.
|
|
261
|
-
return
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
for (const
|
|
265
|
-
|
|
266
|
-
if (
|
|
267
|
-
clearTimeout(
|
|
271
|
+
if (pending.timeout)
|
|
272
|
+
clearTimeout(pending.timeout);
|
|
273
|
+
this.pendingRpcRequests.delete(msgId);
|
|
274
|
+
return pending;
|
|
275
|
+
}
|
|
276
|
+
cancelAllPendingRpcRequests(error) {
|
|
277
|
+
for (const pending of this.pendingRpcRequests.values()) {
|
|
278
|
+
pending.reject(error);
|
|
279
|
+
if (pending.timeout)
|
|
280
|
+
clearTimeout(pending.timeout);
|
|
281
|
+
}
|
|
282
|
+
this.pendingRpcRequests.clear();
|
|
283
|
+
}
|
|
284
|
+
resolveRpcTimeoutMs(timeoutMs) {
|
|
285
|
+
return normalizeRpcTimeoutMs(timeoutMs, this.defaultRpcTimeoutMs);
|
|
286
|
+
}
|
|
287
|
+
resendPendingRpcRequests() {
|
|
288
|
+
for (const msgId of this.pendingRpcRequests.keys()) {
|
|
289
|
+
this.trySendPendingRpcRequest(msgId);
|
|
268
290
|
}
|
|
269
|
-
|
|
291
|
+
}
|
|
292
|
+
trySendPendingRpcRequest(msgId) {
|
|
293
|
+
const pending = this.pendingRpcRequests.get(msgId);
|
|
294
|
+
if (!pending)
|
|
295
|
+
return;
|
|
296
|
+
if (this.state !== "open")
|
|
297
|
+
return;
|
|
298
|
+
if (pending.sending)
|
|
299
|
+
return;
|
|
300
|
+
pending.sending = true;
|
|
301
|
+
void this.transport
|
|
302
|
+
.send(pending.message)
|
|
303
|
+
.catch((error) => {
|
|
304
|
+
this.log.warn?.("Failed to send RPC request; waiting for reconnect", error);
|
|
305
|
+
this.handleClientFailure();
|
|
306
|
+
})
|
|
307
|
+
.finally(() => {
|
|
308
|
+
pending.sending = false;
|
|
309
|
+
});
|
|
270
310
|
}
|
|
271
311
|
}
|
|
312
|
+
const normalizeRpcTimeoutMs = (timeoutMs, fallback) => {
|
|
313
|
+
const resolved = timeoutMs === undefined ? fallback : timeoutMs;
|
|
314
|
+
if (resolved == null)
|
|
315
|
+
return null;
|
|
316
|
+
if (resolved === Number.POSITIVE_INFINITY)
|
|
317
|
+
return null;
|
|
318
|
+
if (!Number.isFinite(resolved))
|
|
319
|
+
return null;
|
|
320
|
+
if (resolved <= 0)
|
|
321
|
+
return null;
|
|
322
|
+
return Math.floor(resolved);
|
|
323
|
+
};
|
|
272
324
|
export class ProtocolClientError extends Error {
|
|
273
325
|
constructor(code, details) {
|
|
274
326
|
super(details?.message ?? code);
|
|
@@ -42,13 +42,13 @@ export declare class InlineSdkClient {
|
|
|
42
42
|
typing: boolean;
|
|
43
43
|
}): Promise<void>;
|
|
44
44
|
invokeRaw(method: Method, input?: RpcCall["input"], options?: {
|
|
45
|
-
timeoutMs?: number;
|
|
45
|
+
timeoutMs?: number | null;
|
|
46
46
|
}): Promise<RpcResult["result"]>;
|
|
47
47
|
invokeUncheckedRaw(method: Method, input?: RpcCall["input"], options?: {
|
|
48
|
-
timeoutMs?: number;
|
|
48
|
+
timeoutMs?: number | null;
|
|
49
49
|
}): Promise<RpcResult["result"]>;
|
|
50
50
|
invoke<M extends MappedMethod>(method: M, input: RpcInputForMethod<M>, options?: {
|
|
51
|
-
timeoutMs?: number;
|
|
51
|
+
timeoutMs?: number | null;
|
|
52
52
|
}): Promise<RpcResultForMethod<M>>;
|
|
53
53
|
private assertMethodInputMatch;
|
|
54
54
|
private assertMethodResultMatch;
|
package/dist/sdk/types.d.ts
CHANGED