@inline-chat/realtime-sdk 0.0.2 → 0.0.4

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/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { InlineSdkClient } from "./sdk/inline-sdk-client.js";
2
- export type { InlineSdkClientOptions, InlineSdkSendMessageMedia, InlineSdkSendMessageParams, InlineSdkState, InlineSdkStateStore, InlineSdkUploadFileParams, InlineSdkUploadFileResult, InlineSdkUploadFileType, InlineInboundEvent, RpcInputForMethod, RpcResultForMethod, } from "./sdk/types.js";
2
+ export type { InlineSdkClientOptions, InlineSdkGetMessagesParams, InlineSdkSendMessageMedia, InlineSdkSendMessageParams, InlineSdkState, InlineSdkStateStore, InlineSdkUploadFileParams, InlineSdkUploadFileResult, InlineSdkUploadFileType, InlineInboundEvent, RpcInputForMethod, RpcResultForMethod, } from "./sdk/types.js";
3
3
  export type { InlineSdkLogger } from "./sdk/logger.js";
4
4
  export { JsonFileStateStore } from "./state/json-file-state-store.js";
5
5
  export { serializeStateV1, deserializeStateV1 } from "./state/serde.js";
@@ -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 rpcContinuations;
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 failRpcContinuation;
58
- private getAndRemoveRpcContinuation;
59
- private cancelAllRpcContinuations;
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
- rpcContinuations = new Map();
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 continuation = { resolve, reject };
65
- this.rpcContinuations.set(message.id, continuation);
66
- void this.transport.send(message).catch((error) => {
67
- this.failRpcContinuation(message.id, error instanceof Error ? error : new Error("send-failed"));
68
- });
69
- const timeoutMs = options?.timeoutMs ?? 15_000;
70
- if (timeoutMs > 0) {
71
- continuation.timeout = setTimeout(() => {
72
- this.failRpcContinuation(message.id, new ProtocolClientError("timeout"));
73
- }, timeoutMs);
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.cancelAllRpcContinuations(new ProtocolClientError("stopped"));
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 continuation = this.getAndRemoveRpcContinuation(msgId);
243
- continuation?.resolve(rpcResult);
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 continuation = this.getAndRemoveRpcContinuation(msgId);
248
- continuation?.reject(error);
260
+ const pending = this.getAndRemovePendingRpcRequest(msgId);
261
+ pending?.reject(error);
249
262
  }
250
- failRpcContinuation(msgId, error) {
251
- const continuation = this.getAndRemoveRpcContinuation(msgId);
252
- continuation?.reject(error);
263
+ failPendingRpcRequest(msgId, error) {
264
+ const pending = this.getAndRemovePendingRpcRequest(msgId);
265
+ pending?.reject(error);
253
266
  }
254
- getAndRemoveRpcContinuation(msgId) {
255
- const continuation = this.rpcContinuations.get(msgId);
256
- if (!continuation)
267
+ getAndRemovePendingRpcRequest(msgId) {
268
+ const pending = this.pendingRpcRequests.get(msgId);
269
+ if (!pending)
257
270
  return null;
258
- if (continuation.timeout)
259
- clearTimeout(continuation.timeout);
260
- this.rpcContinuations.delete(msgId);
261
- return continuation;
262
- }
263
- cancelAllRpcContinuations(error) {
264
- for (const continuation of this.rpcContinuations.values()) {
265
- continuation.reject(error);
266
- if (continuation.timeout)
267
- clearTimeout(continuation.timeout);
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
- this.rpcContinuations.clear();
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);
@@ -1,6 +1,6 @@
1
- import { Method, type Peer, type RpcCall, type RpcResult } from "@inline-chat/protocol/core";
1
+ import { Method, type Message, type Peer, type RpcCall, type RpcResult } from "@inline-chat/protocol/core";
2
2
  import { type InlineIdLike } from "../ids.js";
3
- import type { InlineSdkClientOptions, InlineInboundEvent, InlineSdkSendMessageParams, InlineSdkState, InlineSdkUploadFileParams, InlineSdkUploadFileResult, MappedMethod, RpcInputForMethod, RpcResultForMethod } from "./types.js";
3
+ import type { InlineSdkClientOptions, InlineInboundEvent, InlineSdkGetMessagesParams, InlineSdkSendMessageParams, InlineSdkState, InlineSdkUploadFileParams, InlineSdkUploadFileResult, MappedMethod, RpcInputForMethod, RpcResultForMethod } from "./types.js";
4
4
  export declare class InlineSdkClient {
5
5
  private readonly options;
6
6
  private readonly log;
@@ -33,6 +33,9 @@ export declare class InlineSdkClient {
33
33
  peer?: Peer;
34
34
  title: string;
35
35
  }>;
36
+ getMessages(params: InlineSdkGetMessagesParams): Promise<{
37
+ messages: Message[];
38
+ }>;
36
39
  sendMessage(params: InlineSdkSendMessageParams): Promise<{
37
40
  messageId: bigint | null;
38
41
  }>;
@@ -42,13 +45,13 @@ export declare class InlineSdkClient {
42
45
  typing: boolean;
43
46
  }): Promise<void>;
44
47
  invokeRaw(method: Method, input?: RpcCall["input"], options?: {
45
- timeoutMs?: number;
48
+ timeoutMs?: number | null;
46
49
  }): Promise<RpcResult["result"]>;
47
50
  invokeUncheckedRaw(method: Method, input?: RpcCall["input"], options?: {
48
- timeoutMs?: number;
51
+ timeoutMs?: number | null;
49
52
  }): Promise<RpcResult["result"]>;
50
53
  invoke<M extends MappedMethod>(method: M, input: RpcInputForMethod<M>, options?: {
51
- timeoutMs?: number;
54
+ timeoutMs?: number | null;
52
55
  }): Promise<RpcResultForMethod<M>>;
53
56
  private assertMethodInputMatch;
54
57
  private assertMethodResultMatch;
@@ -1,4 +1,4 @@
1
- import { GetChatInput, GetMeInput, GetUpdatesInput, GetUpdatesResult_ResultType, GetUpdatesStateInput, InputPeer, MessageSendMode, Method, UpdateBucket, UpdateComposeAction_ComposeAction, } from "@inline-chat/protocol/core";
1
+ import { GetChatInput, GetMessagesInput, GetMeInput, GetUpdatesInput, GetUpdatesResult_ResultType, GetUpdatesStateInput, InputPeer, MessageSendMode, Method, UpdateBucket, UpdateComposeAction_ComposeAction, } from "@inline-chat/protocol/core";
2
2
  import { asInlineId } from "../ids.js";
3
3
  import { AsyncChannel } from "../utils/async-channel.js";
4
4
  import { ProtocolClient } from "../realtime/protocol-client.js";
@@ -53,6 +53,7 @@ export class InlineSdkClient {
53
53
  clientVersion: getSdkVersion(),
54
54
  }),
55
55
  logger: options.logger,
56
+ defaultRpcTimeoutMs: options.rpcTimeoutMs,
56
57
  });
57
58
  void this.startListeners();
58
59
  }
@@ -146,6 +147,18 @@ export class InlineSdkClient {
146
147
  throw new Error("getChat: missing chat");
147
148
  return { chatId: chat.id, peer: chat.peerId, title: chat.title };
148
149
  }
150
+ async getMessages(params) {
151
+ const peerId = this.inputPeerFromTarget(params, "getMessages");
152
+ const messageIds = params.messageIds.map((messageId, index) => asInlineId(messageId, `messageIds[${index}]`));
153
+ const result = await this.invoke(Method.GET_MESSAGES, {
154
+ oneofKind: "getMessages",
155
+ getMessages: GetMessagesInput.create({
156
+ peerId,
157
+ messageIds,
158
+ }),
159
+ });
160
+ return { messages: result.getMessages.messages };
161
+ }
149
162
  async sendMessage(params) {
150
163
  if (params.entities != null && params.parseMarkdown != null) {
151
164
  throw new Error("sendMessage: provide either `entities` or `parseMarkdown`, not both");
@@ -6,6 +6,7 @@ import type { Transport } from "../realtime/transport.js";
6
6
  export type InlineSdkClientOptions = {
7
7
  baseUrl?: string;
8
8
  token: string;
9
+ rpcTimeoutMs?: number | null;
9
10
  logger?: InlineSdkLogger;
10
11
  state?: InlineSdkStateStore;
11
12
  transport?: Transport;
@@ -40,6 +41,15 @@ export type InlineSdkSendMessageParams = {
40
41
  sendMode?: "silent";
41
42
  entities?: MessageEntities;
42
43
  };
44
+ export type InlineSdkGetMessagesParams = {
45
+ chatId: InlineIdLike;
46
+ userId?: never;
47
+ messageIds: InlineIdLike[];
48
+ } | {
49
+ userId: InlineIdLike;
50
+ chatId?: never;
51
+ messageIds: InlineIdLike[];
52
+ };
43
53
  export type InlineSdkBinaryInput = Blob | Uint8Array | ArrayBuffer | SharedArrayBuffer;
44
54
  export type InlineSdkUploadFileType = "photo" | "video" | "document";
45
55
  export type InlineSdkUploadFileParams = {
@@ -151,6 +161,9 @@ export declare const rpcInputKindByMethod: {
151
161
  readonly 33: "listBots";
152
162
  readonly 34: "revealBotToken";
153
163
  readonly 35: "moveThread";
164
+ readonly 36: "rotateBotToken";
165
+ readonly 37: "updateBotProfile";
166
+ readonly 38: "getMessages";
154
167
  };
155
168
  export declare const rpcResultKindByMethod: {
156
169
  readonly 0: undefined;
@@ -189,6 +202,9 @@ export declare const rpcResultKindByMethod: {
189
202
  readonly 33: "listBots";
190
203
  readonly 34: "revealBotToken";
191
204
  readonly 35: "moveThread";
205
+ readonly 36: "rotateBotToken";
206
+ readonly 37: "updateBotProfile";
207
+ readonly 38: "getMessages";
192
208
  };
193
209
  type RpcInputKindByMethod = typeof rpcInputKindByMethod;
194
210
  type RpcResultKindByMethod = typeof rpcResultKindByMethod;
package/dist/sdk/types.js CHANGED
@@ -35,6 +35,9 @@ export const rpcInputKindByMethod = {
35
35
  33: "listBots",
36
36
  34: "revealBotToken",
37
37
  35: "moveThread",
38
+ 36: "rotateBotToken",
39
+ 37: "updateBotProfile",
40
+ 38: "getMessages",
38
41
  };
39
42
  export const rpcResultKindByMethod = {
40
43
  0: undefined, // UNSPECIFIED
@@ -73,4 +76,7 @@ export const rpcResultKindByMethod = {
73
76
  33: "listBots",
74
77
  34: "revealBotToken",
75
78
  35: "moveThread",
79
+ 36: "rotateBotToken",
80
+ 37: "updateBotProfile",
81
+ 38: "getMessages",
76
82
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inline-chat/realtime-sdk",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "files": [
@@ -20,7 +20,7 @@
20
20
  "test": "vitest run --coverage"
21
21
  },
22
22
  "dependencies": {
23
- "@inline-chat/protocol": "^0.0.1",
23
+ "@inline-chat/protocol": "^0.0.2",
24
24
  "ws": "^8.18.3"
25
25
  },
26
26
  "devDependencies": {