@kispi/chat 0.2.0 → 0.2.2

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.cts CHANGED
@@ -14,6 +14,28 @@ declare class Emitter<Events extends Record<string, unknown>> {
14
14
  clear(): void;
15
15
  }
16
16
 
17
+ /**
18
+ * Every failure this SDK raises, with the server's own code on it.
19
+ *
20
+ * The code matters more than the message. A consumer branches on
21
+ * `rate_limited` versus `moderation_denied` versus `unauthorized`, and a
22
+ * bare `Error` carrying prose forces them to match on strings the server
23
+ * is free to reword.
24
+ */
25
+ declare class ChatError extends Error {
26
+ readonly code: string;
27
+ /** Present on rate limits, in milliseconds. */
28
+ readonly retryAfterMs?: number;
29
+ /** The consumer's own code, when a before_publish hook denied this. */
30
+ readonly appCode?: string;
31
+ /** The HTTP status, when this came from a REST call. */
32
+ status?: number;
33
+ constructor(code: string, message: string, extra?: {
34
+ retryAfterMs?: number;
35
+ appCode?: string;
36
+ });
37
+ }
38
+
17
39
  /**
18
40
  * The wire shapes this SDK reads and writes.
19
41
  *
@@ -120,6 +142,16 @@ type Message = {
120
142
  emoji: string;
121
143
  count: number;
122
144
  }[];
145
+ /**
146
+ * The emoji this client's user has on the message.
147
+ *
148
+ * Filled by history reads and kept in step by the SDK afterwards: a
149
+ * `reaction.*` event whose `userId` is `chat.user.id`, and the ack of
150
+ * this client's own `react()`/`unreact()`, update it. Absent on a row
151
+ * the server rendered without a viewer (a live `message.created` has
152
+ * none yet, which is the same as empty).
153
+ */
154
+ myReactions?: string[];
123
155
  thread?: {
124
156
  count: number;
125
157
  lastSeq?: number;
@@ -166,11 +198,11 @@ type RoomEvents = {
166
198
  * socket's read loop where there is no caller to throw to -- and
167
199
  * swallowing it is what leaves a hole nothing ever fills.
168
200
  *
169
- * The retry to offer is **`room.subscribe()`**. A room that emitted
170
- * this is left not-subscribed on purpose, precisely so that call does
171
- * something: it re-subscribes and reloads. Marking it subscribed and
172
- * telling the consumer to retry would be advice that returns
173
- * immediately having done nothing, which is worse than no advice.
201
+ * The room repairs itself: it re-subscribes with backoff (1 s doubling,
202
+ * up to five tries while connected), and every reconnect tries again.
203
+ * `room.subscribe()` is still the retry to offer a person pressing a
204
+ * button -- a room that emitted this is left not-subscribed on purpose,
205
+ * precisely so that call does something.
174
206
  */
175
207
  error: Error;
176
208
  /**
@@ -252,10 +284,22 @@ declare class Room extends Emitter<RoomEvents> {
252
284
  */
253
285
  private wanted;
254
286
  private subscribing;
287
+ /** Self-repair: the armed retry, and how many have run since the last success. */
288
+ private repairTimer;
289
+ private repairs;
255
290
  constructor(chat: ChatClient, rest: Rest, address: {
256
291
  id?: string;
257
292
  key?: string;
258
293
  });
294
+ /**
295
+ * Whether older history exists above the first message held -- what
296
+ * `loadOlder()` would report as `hasMore`, known before calling it.
297
+ *
298
+ * Derived from the first row's seq against the room's retention floor
299
+ * (the subscribe ack's `minSeq`), and false once a `loadOlder()` came
300
+ * back short. False while nothing is loaded. Re-read it on `messages`.
301
+ */
302
+ get hasOlder(): boolean;
259
303
  /** Everything this client knows about the room, in seq order. A new array whenever it changes. */
260
304
  get messages(): Message[];
261
305
  /**
@@ -323,8 +367,14 @@ declare class Room extends Emitter<RoomEvents> {
323
367
  thread: string;
324
368
  };
325
369
  }): Promise<Message[]>;
326
- /** Adds a reaction. Idempotent, like the frame. */
370
+ /**
371
+ * Adds a reaction. Idempotent, like the frame.
372
+ *
373
+ * The ack updates the row's `myReactions` (not its count -- see below),
374
+ * so the "I reacted" state is right even if the broadcast is missed.
375
+ */
327
376
  react(messageId: string, emoji: string): Promise<void>;
377
+ /** Removes a reaction. Idempotent; the ack clears it from `myReactions`. */
328
378
  unreact(messageId: string, emoji: string): Promise<void>;
329
379
  /**
330
380
  * Moves this user's read cursor.
@@ -387,7 +437,17 @@ declare class Room extends Emitter<RoomEvents> {
387
437
  * transient 500 takes a room out of the live feed for the life of the
388
438
  * page.
389
439
  */
390
- resume(): Promise<void>;
440
+ resume(restart?: boolean): Promise<void>;
441
+ /**
442
+ * Reports a failure no caller was waiting for, and repairs.
443
+ *
444
+ * 백그라운드 실패(재접속 뒤 다시 구독, 구멍 메우기)에는 거절을 받을 호출자가
445
+ * 없다. 이벤트만 내고 두면 방은 다음 재접속이나 탭 복귀까지 구멍을 안고 —
446
+ * 구독 프레임부터 실패했다면 라이브 피드 밖에서 — 머문다. 그래서 스스로 다시
447
+ * 구독한다. 횟수를 묶는 것은 고칠 수 없는 실패(권한, 없어진 방)가 서버를
448
+ * 계속 두드리지 않게 하려는 것이고, 그 뒤는 재접속과 `subscribe()`의 몫이다.
449
+ */
450
+ failed(err: unknown): void;
391
451
  /**
392
452
  * Sends `subscribe` and recovers from the one error it has an answer
393
453
  * for.
@@ -474,7 +534,9 @@ type ChatClientOptions = {
474
534
  * Called again on every connect, and again when a REST call is
475
535
  * answered 401 (the call is then retried once), so an expired token is
476
536
  * replaced rather than reused. A throw here is retried with backoff
477
- * like a dropped socket; only `close()` stops that.
537
+ * like a dropped socket -- **unless it throws a `ChatError`**, which
538
+ * means "do not retry" (a ban, a session that ended): the client goes
539
+ * to `closed` and emits it as `error`.
478
540
  *
479
541
  * **Never sign these in the browser** -- signing needs the `sk_`, and
480
542
  * a `sk_` in a browser is the whole app.
@@ -497,6 +559,15 @@ type ChatClientEvents = {
497
559
  * SDK learns about it.
498
560
  */
499
561
  frame: Frame;
562
+ /**
563
+ * 클라이언트가 스스로 `closed`로 멈췄고, 이게 그 이유다.
564
+ *
565
+ * 멈추는 길은 셋이다: `close()`, 서버의 인증 거절, `token()`이 던진
566
+ * `ChatError`. 첫 connect라면 거절로도 알 수 있지만 재접속 중이나 REST의
567
+ * 토큰 갱신 중에는 받을 호출자가 없어서, 이것 없이는 `state`가 `closed`로
568
+ * 바뀌는 것만 보이고 왜인지는 사라진다. `close()`로 닫을 때는 내지 않는다.
569
+ */
570
+ error: ChatError;
500
571
  };
501
572
  declare class ChatClient extends Emitter<ChatClientEvents> {
502
573
  state: ConnectionState;
@@ -509,8 +580,11 @@ declare class ChatClient extends Emitter<ChatClientEvents> {
509
580
  private heartbeat;
510
581
  private retryTimer;
511
582
  private attempt;
512
- /** Set by close(), and the only thing that stops the reconnect loop. */
513
- private closedByCaller;
583
+ /**
584
+ * The reconnect loop is off: set by close(), and by a failure that
585
+ * retrying cannot fix (see `stop`). Cleared by connect().
586
+ */
587
+ private stopped;
514
588
  private nextFrameId;
515
589
  private opening;
516
590
  private readonly rest;
@@ -637,14 +711,59 @@ declare class ChatClient extends Emitter<ChatClientEvents> {
637
711
  registerRoomId(roomId: string, room: Room): void;
638
712
  /** Opens the connection and resolves when `hello` arrives. */
639
713
  connect(): Promise<void>;
714
+ /**
715
+ * Re-authenticates: calls `token()` again and replaces the socket, keeping
716
+ * every room handle and every subscription.
717
+ *
718
+ * For when the identity the server holds has to change mid-session --
719
+ * a nickname change, a new avatar, fresh claims. The server reads the
720
+ * token once, at `auth`, so `sender.name` on the next message is the old
721
+ * one until the connection is re-authenticated. `close()` + `connect()`
722
+ * does that too, but passes through `closed` (a consumer's "you are
723
+ * offline" screen) and tears down the page listeners.
724
+ *
725
+ * States: `open` → `reconnecting` → `open`, never `closed`. Rooms
726
+ * resubscribe from what they hold and catch up the gap, as after any
727
+ * drop; `messages` is kept. Frames awaiting an ack on the old socket are
728
+ * rejected with `closed`. `chat.user` is the new identity once this
729
+ * resolves. If `token()` fails the client keeps retrying in the
730
+ * background like any reconnect (and this rejects); a `ChatError` from
731
+ * `token()` stops it at `closed`, as on connect.
732
+ *
733
+ * On a client that is not connected (never connected, or `close()`d) it
734
+ * is `connect()`.
735
+ */
736
+ reconnect(): Promise<void>;
737
+ /** Sockets `reconnect()` replaced: their late events are not this client's any more. */
738
+ private readonly retired;
640
739
  /**
641
740
  * Closes for good.
642
741
  *
643
742
  * The distinction from a dropped socket is the whole point of the state
644
- * machine: this is the only path that reaches `closed`, and a consumer
645
- * showing "disconnected, retrying" versus "disconnected" needs it.
743
+ * machine: `closed` means nothing is coming back, and a consumer showing
744
+ * "disconnected, retrying" versus "disconnected" needs it. The other
745
+ * ways to reach it are refusals retrying cannot fix -- see `stop`.
646
746
  */
647
747
  close(): Promise<void>;
748
+ /**
749
+ * Stops for a reason retrying cannot fix, and says which.
750
+ *
751
+ * `close()` without the teardown of the page listeners -- a consumer
752
+ * that logs back in calls `connect()` on the same client -- and with an
753
+ * `error` event, because on a reconnect there is no caller to reject.
754
+ */
755
+ private stop;
756
+ /**
757
+ * Resolves once the connection is open. Used by `Room.subscribe`.
758
+ *
759
+ * 연결 전에 부른 `subscribe()`를 `closed`로 거절하면 소비자는 "언제 다시
760
+ * 부를지"를 스스로 알아내야 하고, 첫 connect가 실패했다면 그 때는 영영 오지
761
+ * 않는다. 붙을 때까지 기다리면 그 신호가 필요 없다. 멈춘 클라이언트(close,
762
+ * 인증 거절)는 붙을 일이 없으니 거절한다.
763
+ */
764
+ whenOpen(): Promise<void>;
765
+ private openWaiters;
766
+ private settleOpenWaiters;
648
767
  /** Sends a frame and resolves with its ack, or rejects with its error. */
649
768
  send(type: string, data?: unknown, timeoutMs?: number): Promise<unknown>;
650
769
  /**
@@ -700,28 +819,6 @@ declare class ChatClient extends Emitter<ChatClientEvents> {
700
819
  }
701
820
  declare function createChatClient(options: ChatClientOptions): ChatClient;
702
821
 
703
- /**
704
- * Every failure this SDK raises, with the server's own code on it.
705
- *
706
- * The code matters more than the message. A consumer branches on
707
- * `rate_limited` versus `moderation_denied` versus `unauthorized`, and a
708
- * bare `Error` carrying prose forces them to match on strings the server
709
- * is free to reword.
710
- */
711
- declare class ChatError extends Error {
712
- readonly code: string;
713
- /** Present on rate limits, in milliseconds. */
714
- readonly retryAfterMs?: number;
715
- /** The consumer's own code, when a before_publish hook denied this. */
716
- readonly appCode?: string;
717
- /** The HTTP status, when this came from a REST call. */
718
- status?: number;
719
- constructor(code: string, message: string, extra?: {
720
- retryAfterMs?: number;
721
- appCode?: string;
722
- });
723
- }
724
-
725
822
  /**
726
823
  * The message list, in `seq` order, with its holes filled.
727
824
  *
@@ -793,9 +890,27 @@ declare class Timeline {
793
890
  private shared;
794
891
  /** The `loadOlder` in flight, so a scroll handler firing twice asks once. */
795
892
  private older;
893
+ /**
894
+ * The lowest seq the room still keeps (`minSeq` from the subscribe ack;
895
+ * 1 until told). Below it there is nothing to read, whatever seq says.
896
+ */
897
+ private floor;
898
+ /** A `loadOlder` came back short: the top of what the server keeps was reached. */
899
+ private exhausted;
796
900
  constructor(options: TimelineOptions);
797
901
  /** A new array whenever the list changes; never mutated once returned. */
798
902
  get messages(): Message[];
903
+ /**
904
+ * Whether `loadOlder()` would find anything, answered without asking.
905
+ *
906
+ * `loadOlder()`의 `hasMore`와 같은 판정이다: 맨 위 행이 방의 보존 하한
907
+ * (`minSeq`, seq는 1부터 구멍 없이 붙는다)보다 위에 있고, 앞선 `loadOlder`가
908
+ * 덜 찬 페이지로 끝을 알린 적이 없으면 더 있다. 첫 `loadOlder()` 전에 "이전
909
+ * 메시지 더 보기"를 그릴지 정할 수 있게 한다. 목록이 비었으면 false다.
910
+ */
911
+ get hasOlder(): boolean;
912
+ /** Records the room's retention floor (`minSeq`). */
913
+ setFloor(minSeq: number): void;
799
914
  /** The highest seq this timeline holds, hole or no hole. */
800
915
  get highestSeq(): number;
801
916
  /**
@@ -870,9 +985,11 @@ declare class Timeline {
870
985
  *
871
986
  * The server sends the new `count` with the event, so this is a
872
987
  * replacement rather than an increment: two clients reacting at once
873
- * cannot drift the way `+1`/`-1` would.
988
+ * cannot drift the way `+1`/`-1` would. `count` undefined leaves the
989
+ * aggregate alone (the react ack path); `mine` undefined leaves
990
+ * `myReactions` alone (somebody else's event).
874
991
  */
875
- reaction(messageId: string, emoji: string, count: number): void;
992
+ reaction(messageId: string, emoji: string, count: number | undefined, mine?: boolean): void;
876
993
  /** Applies a `thread.updated` to the root message's aggregate. */
877
994
  thread(rootId: string, count: number, lastSeq?: number): void;
878
995
  /** Applies a `message.deleted`, remembering it if the row is not here. */
package/dist/index.d.ts CHANGED
@@ -14,6 +14,28 @@ declare class Emitter<Events extends Record<string, unknown>> {
14
14
  clear(): void;
15
15
  }
16
16
 
17
+ /**
18
+ * Every failure this SDK raises, with the server's own code on it.
19
+ *
20
+ * The code matters more than the message. A consumer branches on
21
+ * `rate_limited` versus `moderation_denied` versus `unauthorized`, and a
22
+ * bare `Error` carrying prose forces them to match on strings the server
23
+ * is free to reword.
24
+ */
25
+ declare class ChatError extends Error {
26
+ readonly code: string;
27
+ /** Present on rate limits, in milliseconds. */
28
+ readonly retryAfterMs?: number;
29
+ /** The consumer's own code, when a before_publish hook denied this. */
30
+ readonly appCode?: string;
31
+ /** The HTTP status, when this came from a REST call. */
32
+ status?: number;
33
+ constructor(code: string, message: string, extra?: {
34
+ retryAfterMs?: number;
35
+ appCode?: string;
36
+ });
37
+ }
38
+
17
39
  /**
18
40
  * The wire shapes this SDK reads and writes.
19
41
  *
@@ -120,6 +142,16 @@ type Message = {
120
142
  emoji: string;
121
143
  count: number;
122
144
  }[];
145
+ /**
146
+ * The emoji this client's user has on the message.
147
+ *
148
+ * Filled by history reads and kept in step by the SDK afterwards: a
149
+ * `reaction.*` event whose `userId` is `chat.user.id`, and the ack of
150
+ * this client's own `react()`/`unreact()`, update it. Absent on a row
151
+ * the server rendered without a viewer (a live `message.created` has
152
+ * none yet, which is the same as empty).
153
+ */
154
+ myReactions?: string[];
123
155
  thread?: {
124
156
  count: number;
125
157
  lastSeq?: number;
@@ -166,11 +198,11 @@ type RoomEvents = {
166
198
  * socket's read loop where there is no caller to throw to -- and
167
199
  * swallowing it is what leaves a hole nothing ever fills.
168
200
  *
169
- * The retry to offer is **`room.subscribe()`**. A room that emitted
170
- * this is left not-subscribed on purpose, precisely so that call does
171
- * something: it re-subscribes and reloads. Marking it subscribed and
172
- * telling the consumer to retry would be advice that returns
173
- * immediately having done nothing, which is worse than no advice.
201
+ * The room repairs itself: it re-subscribes with backoff (1 s doubling,
202
+ * up to five tries while connected), and every reconnect tries again.
203
+ * `room.subscribe()` is still the retry to offer a person pressing a
204
+ * button -- a room that emitted this is left not-subscribed on purpose,
205
+ * precisely so that call does something.
174
206
  */
175
207
  error: Error;
176
208
  /**
@@ -252,10 +284,22 @@ declare class Room extends Emitter<RoomEvents> {
252
284
  */
253
285
  private wanted;
254
286
  private subscribing;
287
+ /** Self-repair: the armed retry, and how many have run since the last success. */
288
+ private repairTimer;
289
+ private repairs;
255
290
  constructor(chat: ChatClient, rest: Rest, address: {
256
291
  id?: string;
257
292
  key?: string;
258
293
  });
294
+ /**
295
+ * Whether older history exists above the first message held -- what
296
+ * `loadOlder()` would report as `hasMore`, known before calling it.
297
+ *
298
+ * Derived from the first row's seq against the room's retention floor
299
+ * (the subscribe ack's `minSeq`), and false once a `loadOlder()` came
300
+ * back short. False while nothing is loaded. Re-read it on `messages`.
301
+ */
302
+ get hasOlder(): boolean;
259
303
  /** Everything this client knows about the room, in seq order. A new array whenever it changes. */
260
304
  get messages(): Message[];
261
305
  /**
@@ -323,8 +367,14 @@ declare class Room extends Emitter<RoomEvents> {
323
367
  thread: string;
324
368
  };
325
369
  }): Promise<Message[]>;
326
- /** Adds a reaction. Idempotent, like the frame. */
370
+ /**
371
+ * Adds a reaction. Idempotent, like the frame.
372
+ *
373
+ * The ack updates the row's `myReactions` (not its count -- see below),
374
+ * so the "I reacted" state is right even if the broadcast is missed.
375
+ */
327
376
  react(messageId: string, emoji: string): Promise<void>;
377
+ /** Removes a reaction. Idempotent; the ack clears it from `myReactions`. */
328
378
  unreact(messageId: string, emoji: string): Promise<void>;
329
379
  /**
330
380
  * Moves this user's read cursor.
@@ -387,7 +437,17 @@ declare class Room extends Emitter<RoomEvents> {
387
437
  * transient 500 takes a room out of the live feed for the life of the
388
438
  * page.
389
439
  */
390
- resume(): Promise<void>;
440
+ resume(restart?: boolean): Promise<void>;
441
+ /**
442
+ * Reports a failure no caller was waiting for, and repairs.
443
+ *
444
+ * 백그라운드 실패(재접속 뒤 다시 구독, 구멍 메우기)에는 거절을 받을 호출자가
445
+ * 없다. 이벤트만 내고 두면 방은 다음 재접속이나 탭 복귀까지 구멍을 안고 —
446
+ * 구독 프레임부터 실패했다면 라이브 피드 밖에서 — 머문다. 그래서 스스로 다시
447
+ * 구독한다. 횟수를 묶는 것은 고칠 수 없는 실패(권한, 없어진 방)가 서버를
448
+ * 계속 두드리지 않게 하려는 것이고, 그 뒤는 재접속과 `subscribe()`의 몫이다.
449
+ */
450
+ failed(err: unknown): void;
391
451
  /**
392
452
  * Sends `subscribe` and recovers from the one error it has an answer
393
453
  * for.
@@ -474,7 +534,9 @@ type ChatClientOptions = {
474
534
  * Called again on every connect, and again when a REST call is
475
535
  * answered 401 (the call is then retried once), so an expired token is
476
536
  * replaced rather than reused. A throw here is retried with backoff
477
- * like a dropped socket; only `close()` stops that.
537
+ * like a dropped socket -- **unless it throws a `ChatError`**, which
538
+ * means "do not retry" (a ban, a session that ended): the client goes
539
+ * to `closed` and emits it as `error`.
478
540
  *
479
541
  * **Never sign these in the browser** -- signing needs the `sk_`, and
480
542
  * a `sk_` in a browser is the whole app.
@@ -497,6 +559,15 @@ type ChatClientEvents = {
497
559
  * SDK learns about it.
498
560
  */
499
561
  frame: Frame;
562
+ /**
563
+ * 클라이언트가 스스로 `closed`로 멈췄고, 이게 그 이유다.
564
+ *
565
+ * 멈추는 길은 셋이다: `close()`, 서버의 인증 거절, `token()`이 던진
566
+ * `ChatError`. 첫 connect라면 거절로도 알 수 있지만 재접속 중이나 REST의
567
+ * 토큰 갱신 중에는 받을 호출자가 없어서, 이것 없이는 `state`가 `closed`로
568
+ * 바뀌는 것만 보이고 왜인지는 사라진다. `close()`로 닫을 때는 내지 않는다.
569
+ */
570
+ error: ChatError;
500
571
  };
501
572
  declare class ChatClient extends Emitter<ChatClientEvents> {
502
573
  state: ConnectionState;
@@ -509,8 +580,11 @@ declare class ChatClient extends Emitter<ChatClientEvents> {
509
580
  private heartbeat;
510
581
  private retryTimer;
511
582
  private attempt;
512
- /** Set by close(), and the only thing that stops the reconnect loop. */
513
- private closedByCaller;
583
+ /**
584
+ * The reconnect loop is off: set by close(), and by a failure that
585
+ * retrying cannot fix (see `stop`). Cleared by connect().
586
+ */
587
+ private stopped;
514
588
  private nextFrameId;
515
589
  private opening;
516
590
  private readonly rest;
@@ -637,14 +711,59 @@ declare class ChatClient extends Emitter<ChatClientEvents> {
637
711
  registerRoomId(roomId: string, room: Room): void;
638
712
  /** Opens the connection and resolves when `hello` arrives. */
639
713
  connect(): Promise<void>;
714
+ /**
715
+ * Re-authenticates: calls `token()` again and replaces the socket, keeping
716
+ * every room handle and every subscription.
717
+ *
718
+ * For when the identity the server holds has to change mid-session --
719
+ * a nickname change, a new avatar, fresh claims. The server reads the
720
+ * token once, at `auth`, so `sender.name` on the next message is the old
721
+ * one until the connection is re-authenticated. `close()` + `connect()`
722
+ * does that too, but passes through `closed` (a consumer's "you are
723
+ * offline" screen) and tears down the page listeners.
724
+ *
725
+ * States: `open` → `reconnecting` → `open`, never `closed`. Rooms
726
+ * resubscribe from what they hold and catch up the gap, as after any
727
+ * drop; `messages` is kept. Frames awaiting an ack on the old socket are
728
+ * rejected with `closed`. `chat.user` is the new identity once this
729
+ * resolves. If `token()` fails the client keeps retrying in the
730
+ * background like any reconnect (and this rejects); a `ChatError` from
731
+ * `token()` stops it at `closed`, as on connect.
732
+ *
733
+ * On a client that is not connected (never connected, or `close()`d) it
734
+ * is `connect()`.
735
+ */
736
+ reconnect(): Promise<void>;
737
+ /** Sockets `reconnect()` replaced: their late events are not this client's any more. */
738
+ private readonly retired;
640
739
  /**
641
740
  * Closes for good.
642
741
  *
643
742
  * The distinction from a dropped socket is the whole point of the state
644
- * machine: this is the only path that reaches `closed`, and a consumer
645
- * showing "disconnected, retrying" versus "disconnected" needs it.
743
+ * machine: `closed` means nothing is coming back, and a consumer showing
744
+ * "disconnected, retrying" versus "disconnected" needs it. The other
745
+ * ways to reach it are refusals retrying cannot fix -- see `stop`.
646
746
  */
647
747
  close(): Promise<void>;
748
+ /**
749
+ * Stops for a reason retrying cannot fix, and says which.
750
+ *
751
+ * `close()` without the teardown of the page listeners -- a consumer
752
+ * that logs back in calls `connect()` on the same client -- and with an
753
+ * `error` event, because on a reconnect there is no caller to reject.
754
+ */
755
+ private stop;
756
+ /**
757
+ * Resolves once the connection is open. Used by `Room.subscribe`.
758
+ *
759
+ * 연결 전에 부른 `subscribe()`를 `closed`로 거절하면 소비자는 "언제 다시
760
+ * 부를지"를 스스로 알아내야 하고, 첫 connect가 실패했다면 그 때는 영영 오지
761
+ * 않는다. 붙을 때까지 기다리면 그 신호가 필요 없다. 멈춘 클라이언트(close,
762
+ * 인증 거절)는 붙을 일이 없으니 거절한다.
763
+ */
764
+ whenOpen(): Promise<void>;
765
+ private openWaiters;
766
+ private settleOpenWaiters;
648
767
  /** Sends a frame and resolves with its ack, or rejects with its error. */
649
768
  send(type: string, data?: unknown, timeoutMs?: number): Promise<unknown>;
650
769
  /**
@@ -700,28 +819,6 @@ declare class ChatClient extends Emitter<ChatClientEvents> {
700
819
  }
701
820
  declare function createChatClient(options: ChatClientOptions): ChatClient;
702
821
 
703
- /**
704
- * Every failure this SDK raises, with the server's own code on it.
705
- *
706
- * The code matters more than the message. A consumer branches on
707
- * `rate_limited` versus `moderation_denied` versus `unauthorized`, and a
708
- * bare `Error` carrying prose forces them to match on strings the server
709
- * is free to reword.
710
- */
711
- declare class ChatError extends Error {
712
- readonly code: string;
713
- /** Present on rate limits, in milliseconds. */
714
- readonly retryAfterMs?: number;
715
- /** The consumer's own code, when a before_publish hook denied this. */
716
- readonly appCode?: string;
717
- /** The HTTP status, when this came from a REST call. */
718
- status?: number;
719
- constructor(code: string, message: string, extra?: {
720
- retryAfterMs?: number;
721
- appCode?: string;
722
- });
723
- }
724
-
725
822
  /**
726
823
  * The message list, in `seq` order, with its holes filled.
727
824
  *
@@ -793,9 +890,27 @@ declare class Timeline {
793
890
  private shared;
794
891
  /** The `loadOlder` in flight, so a scroll handler firing twice asks once. */
795
892
  private older;
893
+ /**
894
+ * The lowest seq the room still keeps (`minSeq` from the subscribe ack;
895
+ * 1 until told). Below it there is nothing to read, whatever seq says.
896
+ */
897
+ private floor;
898
+ /** A `loadOlder` came back short: the top of what the server keeps was reached. */
899
+ private exhausted;
796
900
  constructor(options: TimelineOptions);
797
901
  /** A new array whenever the list changes; never mutated once returned. */
798
902
  get messages(): Message[];
903
+ /**
904
+ * Whether `loadOlder()` would find anything, answered without asking.
905
+ *
906
+ * `loadOlder()`의 `hasMore`와 같은 판정이다: 맨 위 행이 방의 보존 하한
907
+ * (`minSeq`, seq는 1부터 구멍 없이 붙는다)보다 위에 있고, 앞선 `loadOlder`가
908
+ * 덜 찬 페이지로 끝을 알린 적이 없으면 더 있다. 첫 `loadOlder()` 전에 "이전
909
+ * 메시지 더 보기"를 그릴지 정할 수 있게 한다. 목록이 비었으면 false다.
910
+ */
911
+ get hasOlder(): boolean;
912
+ /** Records the room's retention floor (`minSeq`). */
913
+ setFloor(minSeq: number): void;
799
914
  /** The highest seq this timeline holds, hole or no hole. */
800
915
  get highestSeq(): number;
801
916
  /**
@@ -870,9 +985,11 @@ declare class Timeline {
870
985
  *
871
986
  * The server sends the new `count` with the event, so this is a
872
987
  * replacement rather than an increment: two clients reacting at once
873
- * cannot drift the way `+1`/`-1` would.
988
+ * cannot drift the way `+1`/`-1` would. `count` undefined leaves the
989
+ * aggregate alone (the react ack path); `mine` undefined leaves
990
+ * `myReactions` alone (somebody else's event).
874
991
  */
875
- reaction(messageId: string, emoji: string, count: number): void;
992
+ reaction(messageId: string, emoji: string, count: number | undefined, mine?: boolean): void;
876
993
  /** Applies a `thread.updated` to the root message's aggregate. */
877
994
  thread(rootId: string, count: number, lastSeq?: number): void;
878
995
  /** Applies a `message.deleted`, remembering it if the row is not here. */