@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/README.en.md +442 -0
- package/README.md +86 -14
- package/dist/index.cjs +243 -39
- package/dist/index.d.cts +153 -36
- package/dist/index.d.ts +153 -36
- package/dist/index.js +243 -39
- package/dist/server/index.cjs +2 -0
- package/dist/server/index.d.cts +39 -1
- package/dist/server/index.d.ts +39 -1
- package/dist/server/index.js +2 -0
- package/package.json +3 -2
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
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
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
|
-
/**
|
|
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
|
|
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
|
-
/**
|
|
513
|
-
|
|
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:
|
|
645
|
-
*
|
|
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
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
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
|
-
/**
|
|
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
|
|
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
|
-
/**
|
|
513
|
-
|
|
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:
|
|
645
|
-
*
|
|
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. */
|