@noverachat/sdk-web 0.3.0 → 0.4.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/README.ko.md +1 -1
- package/README.md +1 -1
- package/dist/index.cjs +672 -13
- package/dist/index.d.cts +385 -5
- package/dist/index.d.ts +385 -5
- package/dist/index.js +665 -12
- package/package.json +1 -1
- package/src/cache.ts +65 -0
- package/src/chat-message.ts +214 -0
- package/src/client.ts +49 -2
- package/src/features/message-collection.ts +504 -0
- package/src/features/room.ts +118 -5
- package/src/index.ts +19 -0
- package/src/internal/compare-id.ts +17 -0
- package/src/types.ts +12 -2
package/dist/index.d.ts
CHANGED
|
@@ -304,6 +304,25 @@ interface DeviceOut {
|
|
|
304
304
|
lastActiveAt: string | null;
|
|
305
305
|
createdAt: string;
|
|
306
306
|
}
|
|
307
|
+
interface RoomOut {
|
|
308
|
+
appId: string;
|
|
309
|
+
roomId: string;
|
|
310
|
+
roomType: RoomType;
|
|
311
|
+
customType: string | null;
|
|
312
|
+
name: string | null;
|
|
313
|
+
coverImageUrl: string | null;
|
|
314
|
+
isDistinct: boolean;
|
|
315
|
+
isPublic: boolean;
|
|
316
|
+
isFrozen: boolean;
|
|
317
|
+
joinPolicy: JoinPolicy;
|
|
318
|
+
timerSeconds: number | null;
|
|
319
|
+
memberCount: number;
|
|
320
|
+
lastMessageId?: string | null;
|
|
321
|
+
lastMessageAt: string | null;
|
|
322
|
+
lastMessagePreview?: string | null;
|
|
323
|
+
currentAnnouncement?: AnnouncementOut | null;
|
|
324
|
+
createdAt: string;
|
|
325
|
+
}
|
|
307
326
|
/** One row from ``user_blocks`` for the caller. We DON'T echo ``blocker_user_id`` — it's always the caller, and including it in every list entry would just add noise. ``model_config`` lets the router return ORM instances directly via ``response_model``. */
|
|
308
327
|
interface BlockOut {
|
|
309
328
|
blockedUserId: string;
|
|
@@ -350,9 +369,74 @@ interface ReactionEntry {
|
|
|
350
369
|
userIds: string[];
|
|
351
370
|
updatedAt: number;
|
|
352
371
|
}
|
|
372
|
+
type RoomType = "ONE" | "GROUP" | "SUPER_GROUP";
|
|
373
|
+
type JoinPolicy = "OPEN" | "APPROVAL_REQUIRED";
|
|
353
374
|
type MemberRole = "OPERATOR" | "MEMBER" | "KICKED";
|
|
354
375
|
type PushTrigger = "ALL" | "MENTION_ONLY" | "OFF";
|
|
355
376
|
|
|
377
|
+
/**
|
|
378
|
+
* 스냅샷 캐시 — 콜드 스타트 스피너 제거용 렌더 힌트 계층.
|
|
379
|
+
* (noverachat_dart `lib/src/cache.dart`의 TS 미러 — 의미론 동일)
|
|
380
|
+
*
|
|
381
|
+
* 원칙:
|
|
382
|
+
* 1. 캐시 = 렌더 힌트, 서버가 항상 이긴다 — 시작 시 스냅샷 즉시 렌더,
|
|
383
|
+
* API 도착 시 통째 교체(diff 병합 없음).
|
|
384
|
+
* 2. 저장소는 SDK가 정하지 않는다 — `CacheStore` 인터페이스만 정의,
|
|
385
|
+
* 구현은 앱이 주입(localStorage/IndexedDB 등).
|
|
386
|
+
* 3. 버전 스탬프 + 통째 폐기 — 키에 스키마 버전(`nc.v1.…`), 파싱 실패
|
|
387
|
+
* 시 해당 키를 드랍. 삭제 타이머 켜진 방은 캐시 대상에서 제외.
|
|
388
|
+
*/
|
|
389
|
+
/**
|
|
390
|
+
* 앱이 주입하는 key-value 스냅샷 저장소.
|
|
391
|
+
*
|
|
392
|
+
* SDK는 저장 방식에 관여하지 않는다 — 값은 불투명한 JSON 문자열이며,
|
|
393
|
+
* 앱이 localStorage·IndexedDB·파일 등 아무 곳에나 보관하면 된다.
|
|
394
|
+
* 모든 메서드는 실패해도 SDK 동작에 영향이 없다(캐시는 힌트일 뿐).
|
|
395
|
+
*
|
|
396
|
+
* ```ts
|
|
397
|
+
* const localStorageCache: CacheStore = {
|
|
398
|
+
* async read(key) { return localStorage.getItem(key); },
|
|
399
|
+
* async write(key, value) { localStorage.setItem(key, value); },
|
|
400
|
+
* async remove(key) { localStorage.removeItem(key); },
|
|
401
|
+
* };
|
|
402
|
+
*
|
|
403
|
+
* const chat = new NoveraChat({
|
|
404
|
+
* appId, endpoint, tokenProvider,
|
|
405
|
+
* cacheStore: localStorageCache,
|
|
406
|
+
* });
|
|
407
|
+
* ```
|
|
408
|
+
*/
|
|
409
|
+
interface CacheStore {
|
|
410
|
+
/** `key`의 스냅샷을 돌려준다. 없으면 null. */
|
|
411
|
+
read(key: string): Promise<string | null>;
|
|
412
|
+
/** `key`에 스냅샷을 저장한다(덮어쓰기). */
|
|
413
|
+
write(key: string, value: string): Promise<void>;
|
|
414
|
+
/** `key`의 스냅샷을 지운다. 없어도 에러 없이 통과. */
|
|
415
|
+
remove(key: string): Promise<void>;
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* 캐시 스냅샷과 API 응답을 어떻게 합칠지.
|
|
419
|
+
*
|
|
420
|
+
* 현재는 `"replaceByApi"`만 구현되어 있다 — `"mergeDiff"`는 서버 측
|
|
421
|
+
* 무효화 규칙(삭제 타이머 만료 통지·재입장 컷오프 등)이 확정된 뒤
|
|
422
|
+
* 병합 함수가 채워질 진입점이다.
|
|
423
|
+
*/
|
|
424
|
+
type CachePolicy = "replaceByApi" | "mergeDiff";
|
|
425
|
+
/**
|
|
426
|
+
* 캐시 키 규칙 — `nc.<스키마버전>.<대상>`.
|
|
427
|
+
*
|
|
428
|
+
* 스키마 버전이 키에 박혀 있으므로, 저장 포맷이 바뀌면 버전을 올리는
|
|
429
|
+
* 것만으로 구버전 스냅샷은 다시는 읽히지 않는다(자연 폐기).
|
|
430
|
+
*/
|
|
431
|
+
declare const CacheKeys: {
|
|
432
|
+
/** 저장 포맷 스키마 버전. 저장되는 JSON 모양이 바뀌면 올린다. */
|
|
433
|
+
readonly version: "v1";
|
|
434
|
+
/** 방 목록 스냅샷 (`GET /users/me/unread` 원문). */
|
|
435
|
+
readonly rooms: "nc.v1.rooms";
|
|
436
|
+
/** 방 히스토리 최신 페이지 스냅샷 (`GET /rooms/{id}/messages` 원문). */
|
|
437
|
+
readonly msgs: (roomId: string) => string;
|
|
438
|
+
};
|
|
439
|
+
|
|
356
440
|
/**
|
|
357
441
|
* Shared wire types. Must stay in lock-step with:
|
|
358
442
|
* noverachat-backend/src/noverachat/ws/protocol.py
|
|
@@ -433,8 +517,7 @@ interface SendParams {
|
|
|
433
517
|
forwardBlocked?: boolean;
|
|
434
518
|
replyToId?: MessageIdStr;
|
|
435
519
|
/** Set when this send is forwarding an existing message. The server
|
|
436
|
-
* uses it to enforce the source file's ``forwardBlocked`` flag
|
|
437
|
-
* C #7): if the source's file is no-forward AND the destination room
|
|
520
|
+
* uses it to enforce the source file's ``forwardBlocked`` flag: if the source's file is no-forward AND the destination room
|
|
438
521
|
* differs from the source room, the send is rejected with
|
|
439
522
|
* ``NC-FILE-014``. Pass the ORIGINAL message's ``messageId``. */
|
|
440
523
|
forwardOfMessageId?: MessageIdStr;
|
|
@@ -507,6 +590,15 @@ interface ClientOptions {
|
|
|
507
590
|
endpoint: string;
|
|
508
591
|
wsEndpoint?: string;
|
|
509
592
|
tokenProvider: () => Promise<string>;
|
|
593
|
+
/**
|
|
594
|
+
* 스냅샷 캐시 저장소 — 생략(기본)하면 캐시 기능이 전부 꺼진다.
|
|
595
|
+
* 주입하면 방 목록·방 히스토리 최신 페이지가 REST 원문 그대로 저장되고,
|
|
596
|
+
* `cachedUnreadSummary()` / `room.cachedRecent()` 로 콜드 스타트 즉시
|
|
597
|
+
* 렌더에 쓸 수 있다. 자세한 건 `CacheStore` 문서 참조.
|
|
598
|
+
*/
|
|
599
|
+
cacheStore?: CacheStore;
|
|
600
|
+
/** 캐시 스냅샷과 API 응답의 합성 방식. 현재 `"replaceByApi"`만 지원. */
|
|
601
|
+
cachePolicy?: CachePolicy;
|
|
510
602
|
autoReconnect?: boolean;
|
|
511
603
|
pingIntervalMs?: number;
|
|
512
604
|
readWatermarkDebounceMs?: number;
|
|
@@ -582,6 +674,238 @@ declare class ReconnectingWs {
|
|
|
582
674
|
private setState;
|
|
583
675
|
}
|
|
584
676
|
|
|
677
|
+
/**
|
|
678
|
+
* UI 지향 메시지 뷰모델 — sdk-react에서 코어로 이동.
|
|
679
|
+
* (`MessageCollection`이 이 모양으로 상태를 소유하고, 바인딩은 재수출만 한다.
|
|
680
|
+
* noverachat_dart `lib/src/chat_message.dart`의 TS 대응물.)
|
|
681
|
+
*/
|
|
682
|
+
|
|
683
|
+
/** Delivery status of an outgoing (optimistic) message. */
|
|
684
|
+
type ChatMessageStatus = "sending" | "sent" | "failed";
|
|
685
|
+
/**
|
|
686
|
+
* A single message as the UI wants it — one uniform shape over the SDK's
|
|
687
|
+
* three sources: `MessageOut` (history/REST), `WsChatReceive` (live/WS), and
|
|
688
|
+
* an optimistic bubble we just sent.
|
|
689
|
+
*
|
|
690
|
+
* Treat instances as immutable — `MessageCollection` evolves them by
|
|
691
|
+
* replacement, never mutation.
|
|
692
|
+
*/
|
|
693
|
+
interface ChatMessage {
|
|
694
|
+
/** The server message id, or the `tempId` while still `sending`. */
|
|
695
|
+
id: string;
|
|
696
|
+
/** Author user id. `null` means the current user (an optimistic bubble). */
|
|
697
|
+
senderId: string | null;
|
|
698
|
+
content: string | null;
|
|
699
|
+
/** Creation time in unix milliseconds. */
|
|
700
|
+
createdAtMs: number | null;
|
|
701
|
+
status: ChatMessageStatus;
|
|
702
|
+
/** `TEXT | FILE | EMOTICON | ADMIN | SYSTEM` — UIs use this to render
|
|
703
|
+
* system/admin lines (centered notices) differently from chat bubbles. */
|
|
704
|
+
messageType: string;
|
|
705
|
+
/** Sender identity (nickname / profile image) when the server included it.
|
|
706
|
+
* Null for optimistic bubbles and live frames (which carry only
|
|
707
|
+
* `senderId`) — UIs typically resolve the profile from their member list. */
|
|
708
|
+
sender: SenderMini | null;
|
|
709
|
+
/** App-defined subtype (e.g. a message-priority convention). */
|
|
710
|
+
customType: string | null;
|
|
711
|
+
/** Snowflake id (string) of the attached file, when this is a file message. */
|
|
712
|
+
fileId: string | null;
|
|
713
|
+
/** Inline metadata of the attached file — enough to render a placeholder
|
|
714
|
+
* without a round-trip. May be null even when `fileId` is set; fetch via
|
|
715
|
+
* `chat.getFileMeta` then. */
|
|
716
|
+
file: FileInline | null;
|
|
717
|
+
/** Multi-file bundle (`file_group`). Null for single-file / text messages.
|
|
718
|
+
* Check `files?.length` first and fall back to the `file` singleton. */
|
|
719
|
+
files: FileInline[] | null;
|
|
720
|
+
/** Id of the message this one replies to, if any. */
|
|
721
|
+
replyToId: string | null;
|
|
722
|
+
/** Emoji reactions grouped by key. Kept live by `MessageCollection` from
|
|
723
|
+
* the reaction WS events. */
|
|
724
|
+
reactions: ReactionEntry[];
|
|
725
|
+
/** Sender-attached custom metadata (`data._customMeta`). */
|
|
726
|
+
customMeta: Record<string, unknown> | null;
|
|
727
|
+
/** How many times the message was edited. `> 0` → show an "edited" mark. */
|
|
728
|
+
editedCount: number;
|
|
729
|
+
/** Upload progress in [0, 1] while an optimistic FILE message's bytes are
|
|
730
|
+
* still going up to S3. Null once committed (or for non-file messages). */
|
|
731
|
+
uploadProgress: number | null;
|
|
732
|
+
/** The optimistic temp id, kept for ack reconciliation. Null once confirmed. */
|
|
733
|
+
tempId: string | null;
|
|
734
|
+
isDeleted: boolean;
|
|
735
|
+
}
|
|
736
|
+
/** Build a `ChatMessage` from server history (REST). */
|
|
737
|
+
declare function chatMessageFromHistory(m: MessageOut): ChatMessage;
|
|
738
|
+
/** Build a `ChatMessage` from a live inbound frame (WS). */
|
|
739
|
+
declare function chatMessageFromLive(f: WsChatReceive): ChatMessage;
|
|
740
|
+
/** An optimistic bubble for a message we just sent, before the server acks. */
|
|
741
|
+
declare function pendingChatMessage(p: {
|
|
742
|
+
tempId: string;
|
|
743
|
+
content: string;
|
|
744
|
+
replyToId?: string;
|
|
745
|
+
customType?: string;
|
|
746
|
+
customMeta?: Record<string, unknown>;
|
|
747
|
+
}): ChatMessage;
|
|
748
|
+
/** An optimistic bubble for a FILE message whose bytes are still uploading.
|
|
749
|
+
* `uploadProgress` starts at 0 and is advanced by `MessageCollection`. */
|
|
750
|
+
declare function pendingFileChatMessage(p: {
|
|
751
|
+
tempId: string;
|
|
752
|
+
name: string;
|
|
753
|
+
mime?: string;
|
|
754
|
+
size?: number;
|
|
755
|
+
}): ChatMessage;
|
|
756
|
+
|
|
757
|
+
/**
|
|
758
|
+
* 코어 소유 메시지 상태 — noverachat_dart `MessageCollection`의 TS 미러.
|
|
759
|
+
*
|
|
760
|
+
* RoomStore(sdk-react)에 있던 이벤트 반영·낙관적 전송·캐시 hydrate→교체
|
|
761
|
+
* 로직의 코어 이관본. 바인딩(React·Flutter)은 이 컬렉션을 감싸는 얇은
|
|
762
|
+
* 접착층이 되고, "캐시는 렌더 힌트, 서버가 항상 이긴다" 같은 불변식은
|
|
763
|
+
* 코어가 강제한다. 추후 diff 병합은 `applyFresh`의 mergeDiff 분기
|
|
764
|
+
* 하나로 들어온다.
|
|
765
|
+
*/
|
|
766
|
+
|
|
767
|
+
/**
|
|
768
|
+
* 컬렉션 상태가 바뀐 이유. 지금 소비자(얇은 래퍼)는 "바뀌었다"만 알면
|
|
769
|
+
* 되므로 kind만 담는다 — 세밀한 렌더 최적화가 필요해지면 필드를
|
|
770
|
+
* **추가**(비파괴)로 확장한다.
|
|
771
|
+
*
|
|
772
|
+
* - `hydrated` 캐시 스냅샷이 렌더됨 (콜드 스타트 힌트 — 곧 `replaced`가 따라온다)
|
|
773
|
+
* - `replaced` API 응답이 스냅샷/기존 페이지를 통째로 대체함 (서버가 이겼다)
|
|
774
|
+
* - `inserted` 메시지가 추가됨 (라이브 수신·낙관적 버블·스크롤백 페이지)
|
|
775
|
+
* - `updated` 기존 메시지가 제자리 갱신됨 (수정·삭제 플래그·리액션·ack 플립)
|
|
776
|
+
* - `watermarks` 읽음 워터마크가 전진함
|
|
777
|
+
*/
|
|
778
|
+
type CollectionChangeKind = "hydrated" | "replaced" | "inserted" | "updated" | "watermarks";
|
|
779
|
+
/** `MessageCollection`의 `change` 이벤트로 흐르는 변경 통지. */
|
|
780
|
+
interface CollectionChange {
|
|
781
|
+
kind: CollectionChangeKind;
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
784
|
+
* A `Room`'s message list as owned, live state — the core-side equivalent
|
|
785
|
+
* of Sendbird's `MessageCollection` (without a local DB).
|
|
786
|
+
*
|
|
787
|
+
* Applies the room's events into one `messages` list, drives optimistic
|
|
788
|
+
* sends, and runs the snapshot-cache contract (`load()` hydrates from cache
|
|
789
|
+
* instantly, then wholesale-replaces when the API answers). Subscribe with
|
|
790
|
+
* `on("change", ...)` and re-render `messages` on every event — all state
|
|
791
|
+
* mutations and change emits are synchronous with the WS dispatch.
|
|
792
|
+
*
|
|
793
|
+
* ```ts
|
|
794
|
+
* const col = chat.room("room_123").collection();
|
|
795
|
+
* const unsub = col.on("change", () => render(col.messages));
|
|
796
|
+
* await col.load();
|
|
797
|
+
* col.send("hello");
|
|
798
|
+
* col.markRead(col.messages.at(-1)!.id);
|
|
799
|
+
* // ...
|
|
800
|
+
* col.dispose(); unsub();
|
|
801
|
+
* ```
|
|
802
|
+
*/
|
|
803
|
+
declare class MessageCollection {
|
|
804
|
+
/** The room this collection is bound to. */
|
|
805
|
+
readonly room: Room;
|
|
806
|
+
private readonly policy;
|
|
807
|
+
private readonly bus;
|
|
808
|
+
private readonly unsubs;
|
|
809
|
+
private _messages;
|
|
810
|
+
private _readWatermarks;
|
|
811
|
+
private _hasMore;
|
|
812
|
+
private oldestCursor;
|
|
813
|
+
private loadingMore;
|
|
814
|
+
/**
|
|
815
|
+
* 캐시 스냅샷으로 렌더된 머리쪽 메시지 수 — API 도착 시 이만큼 걷어내고
|
|
816
|
+
* 통째 교체한다(replaceByApi). 라이브 수신은 리스트 꼬리에 붙으므로
|
|
817
|
+
* 머리 range 제거로 스냅샷만 정확히 걷힌다.
|
|
818
|
+
*/
|
|
819
|
+
private hydratedCount;
|
|
820
|
+
constructor(
|
|
821
|
+
/** The room this collection is bound to. */
|
|
822
|
+
room: Room, policy?: CachePolicy);
|
|
823
|
+
/** The current messages, oldest first. Treat as read-only. */
|
|
824
|
+
get messages(): readonly ChatMessage[];
|
|
825
|
+
/** True when older history remains — fire `loadMore()` from the top of
|
|
826
|
+
* the scroll view. */
|
|
827
|
+
get hasMore(): boolean;
|
|
828
|
+
/** Per-user read watermarks (`userId` → last read `messageId`), kept live
|
|
829
|
+
* from `sync_tick` frames. Treat as read-only. */
|
|
830
|
+
get readWatermarks(): Readonly<Record<string, string>>;
|
|
831
|
+
/** State-change notifications. One event per mutation; re-read `messages`
|
|
832
|
+
* (and friends) when it fires. Returns an unsubscribe fn. */
|
|
833
|
+
on(event: "change", fn: (c: CollectionChange) => void): () => void;
|
|
834
|
+
/** Whether `userId` has read `messageId` according to the latest watermark. */
|
|
835
|
+
isReadBy(userId: string, messageId: string): boolean;
|
|
836
|
+
/** How many of the given users have read `messageId`. Pass the room's
|
|
837
|
+
* member ids (minus the sender) to get a KakaoTalk-style unread count:
|
|
838
|
+
* `members.length - readCount(...)`. */
|
|
839
|
+
readCount(messageId: string, userIds: Iterable<string>): number;
|
|
840
|
+
/**
|
|
841
|
+
* Load the most recent page of history.
|
|
842
|
+
*
|
|
843
|
+
* `ClientOptions.cacheStore`가 주입돼 있으면 캐시 스냅샷을 먼저 렌더한
|
|
844
|
+
* 뒤(`hydrated`), API 응답 도착 시 스냅샷을 걷어내고 통째로 교체한다
|
|
845
|
+
* (`replaced`). 스냅샷 렌더 중에는 `hasMore`/스크롤백을 열지 않는다 —
|
|
846
|
+
* 커서는 API 응답의 것만 신뢰한다.
|
|
847
|
+
*/
|
|
848
|
+
load(limit?: number): Promise<void>;
|
|
849
|
+
/** Scrollback: load the page of messages older than what's on screen and
|
|
850
|
+
* prepend it. No-op while a previous call is in flight or when `hasMore`
|
|
851
|
+
* is false. Returns the number of messages added. */
|
|
852
|
+
loadMore(limit?: number): Promise<number>;
|
|
853
|
+
/** 캐시 스냅샷 즉시 렌더 — 빈 리스트일 때만. 실패는 조용히 무시(캐시는
|
|
854
|
+
* 힌트일 뿐). */
|
|
855
|
+
private hydrateFromCache;
|
|
856
|
+
/**
|
|
857
|
+
* API 최신 페이지를 컬렉션에 반영한다 — **diff 병합 진입점**.
|
|
858
|
+
* replaceByApi(현재 유일 정책)는 스냅샷을 걷어내고 통째 교체하고,
|
|
859
|
+
* mergeDiff는 서버 무효화 규칙 확정 후 이 분기 안에 병합 함수가 채워진다.
|
|
860
|
+
*/
|
|
861
|
+
private applyFresh;
|
|
862
|
+
/**
|
|
863
|
+
* Send `text` optimistically: a `sending` bubble appears immediately (one
|
|
864
|
+
* microtask later — `Room.send` is async), then flips to `sent` (with the
|
|
865
|
+
* real id) on ack, or `failed` on error. Fire-and-forget: a WS-not-open
|
|
866
|
+
* failure surfaces as a `failed` bubble rather than a rejected promise.
|
|
867
|
+
*/
|
|
868
|
+
send(text: string, opts?: {
|
|
869
|
+
replyToId?: string;
|
|
870
|
+
customType?: string;
|
|
871
|
+
customMeta?: Record<string, unknown>;
|
|
872
|
+
}): void;
|
|
873
|
+
/** Send a file optimistically. A FILE bubble with `uploadProgress`
|
|
874
|
+
* appears immediately; progress advances during the S3 upload, then the
|
|
875
|
+
* bubble flips to `sent` on ack (or `failed`). Never throws — failures
|
|
876
|
+
* surface as the bubble's status. */
|
|
877
|
+
sendFile(file: File, opts?: SendFileOptions): Promise<void>;
|
|
878
|
+
/** Edit a message's content (server-side; peers get `message_updated`).
|
|
879
|
+
* The local copy updates optimistically. */
|
|
880
|
+
edit(messageId: string, content: string): Promise<void>;
|
|
881
|
+
/** Delete a message. `scope: "ALL"` (default) removes it for everyone;
|
|
882
|
+
* `"MY"` hides it only for the caller. Local copy flips to deleted. */
|
|
883
|
+
delete(messageId: string, scope?: DeleteScope): Promise<void>;
|
|
884
|
+
/** Add/remove the caller's reaction. Pass `myUserId` so the toggle can
|
|
885
|
+
* tell whether the caller already reacted with `key`. The authoritative
|
|
886
|
+
* state arrives back via the reaction WS events. */
|
|
887
|
+
toggleReaction(messageId: string, key: string, myUserId: string): Promise<void>;
|
|
888
|
+
/** Mark `messageId` read (debounced inside the SDK). */
|
|
889
|
+
markRead(messageId: string): void;
|
|
890
|
+
/** Force-send the pending read watermark now (e.g. on unmount / tab
|
|
891
|
+
* backgrounding). */
|
|
892
|
+
flushMarkRead(): void;
|
|
893
|
+
/** Unsubscribe from the room and flush the pending read watermark. The
|
|
894
|
+
* collection must not be used afterwards. */
|
|
895
|
+
dispose(): void;
|
|
896
|
+
private onIncoming;
|
|
897
|
+
private onUpdated;
|
|
898
|
+
private onDeleted;
|
|
899
|
+
private onCleared;
|
|
900
|
+
private onReactionAdded;
|
|
901
|
+
private onReactionRemoved;
|
|
902
|
+
private onSyncTick;
|
|
903
|
+
private bindAck;
|
|
904
|
+
private mutateById;
|
|
905
|
+
private mutateByTempId;
|
|
906
|
+
private emit;
|
|
907
|
+
}
|
|
908
|
+
|
|
585
909
|
interface RoomEvents {
|
|
586
910
|
message: WsChatReceive;
|
|
587
911
|
messageUpdated: WsMessageUpdated;
|
|
@@ -619,7 +943,32 @@ declare class Room {
|
|
|
619
943
|
* fine in practice (small strings, cleared on `chat.disconnect()`).
|
|
620
944
|
*/
|
|
621
945
|
private mySentTempIds;
|
|
622
|
-
|
|
946
|
+
private readonly cacheStore;
|
|
947
|
+
private readonly cachePolicy;
|
|
948
|
+
private readonly logger;
|
|
949
|
+
/** 삭제 타이머 여부 세션 메모 — null=미확인, 값=마지막 detail() 결과. */
|
|
950
|
+
private timerSeconds;
|
|
951
|
+
private timerKnown;
|
|
952
|
+
constructor(roomId: string, http: HttpClient, ws: ReconnectingWs, readDebounceMs: number, opts?: {
|
|
953
|
+
cacheStore?: CacheStore;
|
|
954
|
+
cachePolicy?: CachePolicy;
|
|
955
|
+
logger?: ClientOptions["logger"];
|
|
956
|
+
});
|
|
957
|
+
/**
|
|
958
|
+
* 이 방의 메시지 상태를 소유하는 `MessageCollection`을 만든다.
|
|
959
|
+
*
|
|
960
|
+
* 컬렉션이 이벤트 반영·낙관적 전송·캐시 hydrate→교체를 전부 담당하므로,
|
|
961
|
+
* 리스트 화면은 컬렉션 하나만 물면 된다. 여러 개 만들 수 있고 각자
|
|
962
|
+
* `dispose()`로 정리한다.
|
|
963
|
+
*
|
|
964
|
+
* ```ts
|
|
965
|
+
* const col = chat.room("room_123").collection();
|
|
966
|
+
* const unsub = col.on("change", () => render(col.messages));
|
|
967
|
+
* await col.load();
|
|
968
|
+
* col.send("hello");
|
|
969
|
+
* ```
|
|
970
|
+
*/
|
|
971
|
+
collection(policy?: CachePolicy): MessageCollection;
|
|
623
972
|
private _sendPendingMarkRead;
|
|
624
973
|
send(params: string | SendParams): Promise<{
|
|
625
974
|
tempId: string;
|
|
@@ -802,6 +1151,28 @@ declare class Room {
|
|
|
802
1151
|
nextCursor: MessageIdStr | null;
|
|
803
1152
|
hasMore: boolean;
|
|
804
1153
|
}>;
|
|
1154
|
+
/** 이 방의 상세 정보 (`GET /rooms/{id}`) — 이름·멤버 수·삭제 타이머 등. */
|
|
1155
|
+
detail(): Promise<RoomOut>;
|
|
1156
|
+
/**
|
|
1157
|
+
* 히스토리 최신 페이지의 캐시 스냅샷 — 마지막 `listRecent()` 성공 응답의
|
|
1158
|
+
* 원문. 콜드 스타트 렌더 힌트 용도다: 있으면 즉시 그리고, `listRecent()`가
|
|
1159
|
+
* 도착하면 통째로 교체할 것. 캐시가 없거나(`cacheStore` 미주입 포함)
|
|
1160
|
+
* 파싱이 실패하면 `null`을 돌려주고, 깨진 스냅샷은 지운다.
|
|
1161
|
+
*
|
|
1162
|
+
* 스냅샷의 `nextCursor`/`hasMore`는 시점이 지난 값이므로 스크롤백
|
|
1163
|
+
* 커서로 쓰지 말 것 — API 응답이 도착한 뒤의 값만 신뢰한다.
|
|
1164
|
+
*/
|
|
1165
|
+
cachedRecent(): Promise<{
|
|
1166
|
+
items: MessageOut[];
|
|
1167
|
+
nextCursor: MessageIdStr | null;
|
|
1168
|
+
hasMore: boolean;
|
|
1169
|
+
} | null>;
|
|
1170
|
+
/**
|
|
1171
|
+
* 최신 페이지 스냅샷 저장. 삭제 타이머가 켜진 방은 저장하지 않고 기존
|
|
1172
|
+
* 스냅샷도 지운다 — 타이머 만료로 서버에서 사라진 메시지가 로컬 캐시에
|
|
1173
|
+
* 살아남는 보안 충돌을 원천 차단한다.
|
|
1174
|
+
*/
|
|
1175
|
+
private cacheWriteRecent;
|
|
805
1176
|
/**
|
|
806
1177
|
* Scrollback. Fetches the page of `limit` messages immediately preceding
|
|
807
1178
|
* `beforeMessageId` (exclusive), in ASC order. The returned
|
|
@@ -813,7 +1184,7 @@ declare class Room {
|
|
|
813
1184
|
nextCursor: MessageIdStr | null;
|
|
814
1185
|
hasMore: boolean;
|
|
815
1186
|
}>;
|
|
816
|
-
/**
|
|
1187
|
+
/** 방 내 메시지 검색.
|
|
817
1188
|
*
|
|
818
1189
|
* ``content`` 필드에 대한 대소문자 무시 부분 문자열 매칭 (ILIKE). 삭제된
|
|
819
1190
|
* 메시지는 제외. 결과는 최신순 (message_id DESC) 으로 반환되고, ``before``
|
|
@@ -1136,6 +1507,15 @@ declare class NoveraChat {
|
|
|
1136
1507
|
*/
|
|
1137
1508
|
getFileMeta(fileId: MessageIdStr): Promise<FileMeta>;
|
|
1138
1509
|
unreadSummary(): Promise<UnreadSummary>;
|
|
1510
|
+
/**
|
|
1511
|
+
* 방 목록의 캐시 스냅샷 — 마지막 `unreadSummary()` 성공 응답의 원문.
|
|
1512
|
+
*
|
|
1513
|
+
* 콜드 스타트 렌더 힌트 용도다: 있으면 즉시 그리고, `unreadSummary()`가
|
|
1514
|
+
* 도착하면 통째로 교체할 것. 캐시가 없거나(`cacheStore` 미주입 포함)
|
|
1515
|
+
* 파싱이 실패하면 `null`을 돌려주고, 깨진 스냅샷은 지운다.
|
|
1516
|
+
*/
|
|
1517
|
+
cachedUnreadSummary(): Promise<UnreadSummary | null>;
|
|
1518
|
+
private cacheWriteRooms;
|
|
1139
1519
|
/**
|
|
1140
1520
|
* Accept an invite link — call this in flows where you receive a
|
|
1141
1521
|
* shareable URL (e.g. after parsing a deep link like
|
|
@@ -1191,4 +1571,4 @@ declare class NoveraChatError extends Error {
|
|
|
1191
1571
|
static fromResponse(status: number, body: unknown): NoveraChatError;
|
|
1192
1572
|
}
|
|
1193
1573
|
|
|
1194
|
-
export { type InviteAcceptResponse as AcceptInviteResult, type AnnouncementOut as Announcement, type AppPolicy, type BlockOut as Block, type ClientEvents, type ClientOptions, type DeleteScope, ErrorCode, type ErrorCodeValue, type FileInline, type FileKind, type FileMeta, type FileThumbnailStatus, type InviteTokenOut as InviteToken, type JoinRequestCreateResponse, type JoinRequestListResponse, type JoinRequestOut, type MemberPreview, type MemberRole, type MessageIdStr, type MessageOut, type MessageType, NoveraChat, NoveraChatError, type PushTrigger, type ReactionEntry, Room, type RoomEvents, type RoomMemberOut, type RoomUnread, type SendFileOptions, type SendParams, type SenderMini, type UnreadRoomItem, type UnreadSummary, type WsAnnouncementEvent, type WsChatReceive, type WsInbound, type WsMessageDeleted, type WsMessageUpdated, type WsMessagesCleared, type WsOutbound, type WsPresence, type WsReactionAdded, type WsReactionRemoved, type WsRoomEvent, type WsSyncTick, type WsTypingBroadcast };
|
|
1574
|
+
export { type InviteAcceptResponse as AcceptInviteResult, type AnnouncementOut as Announcement, type AppPolicy, type BlockOut as Block, CacheKeys, type CachePolicy, type CacheStore, type ChatMessage, type ChatMessageStatus, type ClientEvents, type ClientOptions, type CollectionChange, type CollectionChangeKind, type DeleteScope, ErrorCode, type ErrorCodeValue, type FileInline, type FileKind, type FileMeta, type FileThumbnailStatus, type InviteTokenOut as InviteToken, type JoinRequestCreateResponse, type JoinRequestListResponse, type JoinRequestOut, type MemberPreview, type MemberRole, MessageCollection, type MessageIdStr, type MessageOut, type MessageType, NoveraChat, NoveraChatError, type PushTrigger, type ReactionEntry, Room, type RoomEvents, type RoomMemberOut, type RoomOut, type RoomUnread, type SendFileOptions, type SendParams, type SenderMini, type UnreadRoomItem, type UnreadSummary, type WsAnnouncementEvent, type WsChatReceive, type WsInbound, type WsMessageDeleted, type WsMessageUpdated, type WsMessagesCleared, type WsOutbound, type WsPresence, type WsReactionAdded, type WsReactionRemoved, type WsRoomEvent, type WsSyncTick, type WsTypingBroadcast, chatMessageFromHistory, chatMessageFromLive, pendingChatMessage, pendingFileChatMessage };
|