@manonero/chat-client-sdk 1.0.0-beta.11 → 1.0.0-beta.12
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.md +64 -11
- package/dist/http/ConversationApi.d.ts +2 -2
- package/dist/http/ConversationApi.js +2 -2
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/types/conversation.d.ts +41 -3
- package/dist/types/conversation.d.ts.map +1 -1
- package/dist/types/notification-events.d.ts +24 -5
- package/dist/types/notification-events.d.ts.map +1 -1
- package/dist/utils/unread.d.ts +31 -0
- package/dist/utils/unread.d.ts.map +1 -0
- package/dist/utils/unread.js +10 -0
- package/dist/utils/unread.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -85,6 +85,11 @@ import { ChatApiError } from '@manonero/chat-client-sdk'; // class — dùng imp
|
|
|
85
85
|
import { TypedEventEmitter } from '@manonero/chat-client-sdk';
|
|
86
86
|
import type { Unsubscribe } from '@manonero/chat-client-sdk';
|
|
87
87
|
|
|
88
|
+
// computeUnreadCount — tính unread badge từ ConversationDto / ConversationListItemDto
|
|
89
|
+
// hoặc bất kỳ object nào có { messageSequence, lastReadSequence, markedUnread }
|
|
90
|
+
import { computeUnreadCount } from '@manonero/chat-client-sdk';
|
|
91
|
+
import type { UnreadCountFields } from '@manonero/chat-client-sdk';
|
|
92
|
+
|
|
88
93
|
// HttpClient — tự xây dựng API module bổ sung theo cùng pattern
|
|
89
94
|
import { HttpClient } from '@manonero/chat-client-sdk';
|
|
90
95
|
import type { HttpClientOptions } from '@manonero/chat-client-sdk';
|
|
@@ -490,7 +495,7 @@ await client.conversations.delete('conv-id');
|
|
|
490
495
|
|
|
491
496
|
### `leaveGroup(id: string): Promise<void>`
|
|
492
497
|
|
|
493
|
-
Rời cuộc hội thoại **Group** một cách tường minh. Trả về `
|
|
498
|
+
Rời cuộc hội thoại **Group** một cách tường minh. Trả về `400 Bad Request` nếu conversation không phải Group.
|
|
494
499
|
|
|
495
500
|
> Dùng `leaveGroup()` khi cần ngữ nghĩa Group-only với phản hồi lỗi rõ ràng cho non-Group. Dùng `delete()` khi muốn xử lý cả Group lẫn OneToOne/Self trong cùng một flow.
|
|
496
501
|
|
|
@@ -596,11 +601,19 @@ interface ConversationDto {
|
|
|
596
601
|
ownerId: string;
|
|
597
602
|
lastMessageAt?: string | null; // ISO 8601, null khi chưa có tin nhắn
|
|
598
603
|
lastMessageId?: string | null; // null khi chưa có tin nhắn
|
|
604
|
+
messageSequence: number; // Sequence tin nhắn mới nhất trong conversation
|
|
599
605
|
createdAt: string; // ISO 8601
|
|
600
606
|
updatedAt: string; // ISO 8601
|
|
607
|
+
selfReadState: SelfReadState | null; // null khi caller không phải active participant
|
|
601
608
|
participants: ConversationParticipantDto[];
|
|
602
609
|
}
|
|
603
610
|
|
|
611
|
+
// Read state của chính caller — dùng để tính unread badge phía client.
|
|
612
|
+
interface SelfReadState {
|
|
613
|
+
lastReadSequence: number;
|
|
614
|
+
markedUnread: boolean;
|
|
615
|
+
}
|
|
616
|
+
|
|
604
617
|
interface ConversationParticipantDto {
|
|
605
618
|
participantId: string;
|
|
606
619
|
joinedAt: string; // ISO 8601
|
|
@@ -610,7 +623,6 @@ interface ConversationParticipantDto {
|
|
|
610
623
|
pinnedAt?: string | null; // ISO 8601
|
|
611
624
|
lastReadMessageId?: string | null;
|
|
612
625
|
lastReadAt?: string | null; // ISO 8601
|
|
613
|
-
unreadCount: number;
|
|
614
626
|
}
|
|
615
627
|
|
|
616
628
|
interface ConversationListItemDto {
|
|
@@ -620,13 +632,43 @@ interface ConversationListItemDto {
|
|
|
620
632
|
avatar?: MediaReference | null;
|
|
621
633
|
lastMessageAt?: string | null; // ISO 8601
|
|
622
634
|
lastMessageId?: string | null;
|
|
635
|
+
messageSequence: number; // Sequence tin nhắn mới nhất
|
|
636
|
+
lastReadSequence: number; // Sequence đã đọc của caller (0 nếu chưa đọc)
|
|
637
|
+
markedUnread: boolean; // Cờ mark-as-unread của caller
|
|
623
638
|
lastMessage?: MessageDto | null; // Preview tin nhắn cuối, null khi chưa có tin nhắn
|
|
624
639
|
isPinned: boolean;
|
|
625
640
|
pinnedAt?: string | null; // ISO 8601, null khi chưa pin
|
|
626
|
-
unreadCount: number;
|
|
627
641
|
participantCount: number;
|
|
628
642
|
}
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
> **Unread badge tính tại client.** Server không trả số tin chưa đọc được tính sẵn — thay vào đó gửi 3 field (`messageSequence`, `lastReadSequence`, `markedUnread`) để client tự tính và cập nhật từ các SignalR event `UnreadCountChanged` / `ReadStatusChanged` mà không cần refetch. Công thức:
|
|
646
|
+
>
|
|
647
|
+
> ```
|
|
648
|
+
> unread = max(messageSequence - lastReadSequence, markedUnread ? 1 : 0)
|
|
649
|
+
> ```
|
|
650
|
+
>
|
|
651
|
+
> SDK export sẵn helper `computeUnreadCount` cài đặt đúng công thức trên (bao gồm clamp về 0 khi race condition khiến `lastReadSequence > messageSequence`). Helper nhận cả `ConversationListItemDto`, `ConversationDto`, hoặc bất kỳ object nào có 3 field `{ messageSequence, lastReadSequence, markedUnread }`:
|
|
652
|
+
>
|
|
653
|
+
> ```ts
|
|
654
|
+
> import { computeUnreadCount } from '@manonero/chat-client-sdk';
|
|
655
|
+
>
|
|
656
|
+
> // Từ list item (shape phẳng)
|
|
657
|
+
> const unreadList = computeUnreadCount(item);
|
|
658
|
+
>
|
|
659
|
+
> // Từ ConversationDto (selfReadState lồng bên trong); trả về 0 nếu
|
|
660
|
+
> // selfReadState === null (caller không phải active participant)
|
|
661
|
+
> const unread = computeUnreadCount(conv);
|
|
662
|
+
>
|
|
663
|
+
> // Từ cache nội bộ của bạn
|
|
664
|
+
> const unreadFromCache = computeUnreadCount({
|
|
665
|
+
> messageSequence: cached.messageSequence,
|
|
666
|
+
> lastReadSequence: cached.lastReadSequence,
|
|
667
|
+
> markedUnread: cached.markedUnread,
|
|
668
|
+
> });
|
|
669
|
+
> ```
|
|
629
670
|
|
|
671
|
+
```ts
|
|
630
672
|
// Media listing (getMedia)
|
|
631
673
|
type ConversationMediaKindFilter = 'all' | 'attachment' | 'link';
|
|
632
674
|
type ConversationMediaKind = 'Attachment' | 'Link';
|
|
@@ -1488,14 +1530,14 @@ await client.realtime.notifications.unsubscribeFromPresence(['user-1']);
|
|
|
1488
1530
|
| `presenceChanged` | `PresenceChangedDto` | Thay đổi trạng thái online |
|
|
1489
1531
|
| `newMessageNotification` | `NewMessageNotificationDto` | Có tin nhắn mới (thông báo cho tất cả participants trừ người gửi) |
|
|
1490
1532
|
| `mentionedNotification` | `MentionedNotificationDto` | Bị mention trong tin nhắn |
|
|
1491
|
-
| `unreadCountChanged` | `UnreadCountChangedDto` |
|
|
1533
|
+
| `unreadCountChanged` | `UnreadCountChangedDto` | `MessageSequence` của conversation tăng (có tin mới từ user khác) — client tự tính lại badge |
|
|
1492
1534
|
| `conversationCreated` | `ConversationCreatedDto` | Cuộc hội thoại mới được tạo (hoặc bị thêm vào) |
|
|
1493
1535
|
| `conversationUpdated` | `ConversationUpdatedDto` | Metadata cuộc hội thoại thay đổi |
|
|
1494
1536
|
| `participantJoined` | `ParticipantJoinedDto` | Thành viên mới tham gia |
|
|
1495
1537
|
| `participantLeft` | `ParticipantLeftDto` | Thành viên rời nhóm |
|
|
1496
1538
|
| `conversationPinned` | `ConversationPinnedDto` | Cuộc hội thoại được pin |
|
|
1497
1539
|
| `conversationUnpinned` | `ConversationUnpinnedDto` | Cuộc hội thoại được unpin |
|
|
1498
|
-
| `readStatusChanged` | `ReadStatusChangedDto` | Trạng thái đọc thay đổi |
|
|
1540
|
+
| `readStatusChanged` | `ReadStatusChangedDto` | Trạng thái đọc thay đổi (do chính user gọi `POST/DELETE /api/conversations/{id}/read`) — chỉ gửi tới owner |
|
|
1499
1541
|
| `conversationDeleted` | `ConversationDeletedDto` | Conversation bị ẩn/xóa khỏi danh sách (chỉ OneToOne & Self, chỉ user thực hiện) |
|
|
1500
1542
|
| `conversationRestored` | `ConversationRestoredDto` | Conversation bị ẩn được khôi phục tự động do có tin nhắn mới (chỉ OneToOne & Self) |
|
|
1501
1543
|
| `error` | `HubErrorDto` | Lỗi từ server |
|
|
@@ -1542,9 +1584,13 @@ interface MentionedNotificationDto {
|
|
|
1542
1584
|
sentAt: string; // ISO 8601
|
|
1543
1585
|
}
|
|
1544
1586
|
|
|
1587
|
+
// Server KHÔNG gửi unread count tuyệt đối — chỉ push sequence mới.
|
|
1588
|
+
// Client tự compute: unread = max(newSequence - cached lastReadSequence, markedUnread ? 1 : 0)
|
|
1589
|
+
// Event chỉ gửi tới participant đang online, không gửi cho sender. System messages không bump sequence.
|
|
1545
1590
|
interface UnreadCountChangedDto {
|
|
1546
1591
|
conversationId: string;
|
|
1547
|
-
|
|
1592
|
+
newSequence: number; // MessageSequence mới sau khi bump
|
|
1593
|
+
senderId: string; // User vừa gửi tin (dùng để hiển thị preview)
|
|
1548
1594
|
lastMessageAt: string; // ISO 8601
|
|
1549
1595
|
lastMessageId: string;
|
|
1550
1596
|
}
|
|
@@ -1592,11 +1638,13 @@ interface ConversationUnpinnedDto {
|
|
|
1592
1638
|
conversationId: string;
|
|
1593
1639
|
}
|
|
1594
1640
|
|
|
1641
|
+
// Chỉ gửi tới owner. Client cập nhật cached lastReadSequence rồi tính lại badge:
|
|
1642
|
+
// unread = max(cachedMessageSequence - lastReadSequence, markedUnread ? 1 : 0)
|
|
1595
1643
|
interface ReadStatusChangedDto {
|
|
1596
1644
|
conversationId: string;
|
|
1597
|
-
lastReadMessageId?: string;
|
|
1598
|
-
|
|
1599
|
-
|
|
1645
|
+
lastReadMessageId?: string | null; // null khi MarkAsUnread
|
|
1646
|
+
lastReadSequence: number; // High-water mark mới
|
|
1647
|
+
markedUnread: boolean; // true khi user chủ động đánh dấu chưa đọc
|
|
1600
1648
|
}
|
|
1601
1649
|
|
|
1602
1650
|
// Conversation bị ẩn/xóa khỏi danh sách của user.
|
|
@@ -2437,7 +2485,7 @@ const ms = Number(health.totalDuration); // NaN
|
|
|
2437
2485
|
### Ví dụ 1: Chat app cơ bản
|
|
2438
2486
|
|
|
2439
2487
|
```ts
|
|
2440
|
-
import { ChatClient, ChatApiError } from '@manonero/chat-client-sdk';
|
|
2488
|
+
import { ChatClient, ChatApiError, computeUnreadCount } from '@manonero/chat-client-sdk';
|
|
2441
2489
|
import type { ChatMessageDto } from '@manonero/chat-client-sdk';
|
|
2442
2490
|
|
|
2443
2491
|
// 1. Khởi tạo
|
|
@@ -2456,8 +2504,13 @@ client.realtime.notifications.on('newMessageNotification', (notification) => {
|
|
|
2456
2504
|
console.log(`[${notification.conversationId}] ${notification.senderName}: ${notification.contentPreview}`);
|
|
2457
2505
|
});
|
|
2458
2506
|
|
|
2507
|
+
// Server chỉ push newSequence — client cập nhật cache rồi gọi helper để tính lại badge
|
|
2459
2508
|
client.realtime.notifications.on('unreadCountChanged', (dto) => {
|
|
2460
|
-
|
|
2509
|
+
const cached = getCachedConversation(dto.conversationId);
|
|
2510
|
+
cached.messageSequence = dto.newSequence;
|
|
2511
|
+
cached.lastMessageAt = dto.lastMessageAt;
|
|
2512
|
+
cached.lastMessageId = dto.lastMessageId;
|
|
2513
|
+
updateBadge(dto.conversationId, computeUnreadCount(cached));
|
|
2461
2514
|
});
|
|
2462
2515
|
|
|
2463
2516
|
// 5. Mở cuộc hội thoại và chat
|
|
@@ -48,11 +48,11 @@ export declare class ConversationApi {
|
|
|
48
48
|
/**
|
|
49
49
|
* POST /api/conversations/{id}/leave
|
|
50
50
|
* Leave a Group conversation explicitly (Group only).
|
|
51
|
-
* Returns
|
|
51
|
+
* Returns 400 Bad Request if the conversation is not a Group.
|
|
52
52
|
*
|
|
53
53
|
* NOTE: `delete()` (DELETE) also performs the leave action for Group conversations and
|
|
54
54
|
* additionally hides OneToOne/Self conversations. Use this method when you need the
|
|
55
|
-
* strict Group-only semantic with explicit
|
|
55
|
+
* strict Group-only semantic with explicit 400 feedback for non-Group conversations.
|
|
56
56
|
*/
|
|
57
57
|
leaveGroup(id: string): Promise<void>;
|
|
58
58
|
/**
|
|
@@ -66,11 +66,11 @@ export class ConversationApi {
|
|
|
66
66
|
/**
|
|
67
67
|
* POST /api/conversations/{id}/leave
|
|
68
68
|
* Leave a Group conversation explicitly (Group only).
|
|
69
|
-
* Returns
|
|
69
|
+
* Returns 400 Bad Request if the conversation is not a Group.
|
|
70
70
|
*
|
|
71
71
|
* NOTE: `delete()` (DELETE) also performs the leave action for Group conversations and
|
|
72
72
|
* additionally hides OneToOne/Self conversations. Use this method when you need the
|
|
73
|
-
* strict Group-only semantic with explicit
|
|
73
|
+
* strict Group-only semantic with explicit 400 feedback for non-Group conversations.
|
|
74
74
|
*/
|
|
75
75
|
leaveGroup(id) {
|
|
76
76
|
return this.http.post(`/api/conversations/${encodeURIComponent(id)}/leave`);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export type { CursorPaginatedResult, PagedResult, MediaReference, ProblemDetails, ProblemError, UploadProgressCallback, HealthStatus, HealthCheckResult, } from './types/common.js';
|
|
2
2
|
export type { GoogleLoginRequest, DsAccountLoginRequest, DevLoginRequest, LoginRequest, AuthResponse, AuthUserInfo } from './types/auth.js';
|
|
3
3
|
export type { ParticipantDto, CreateOrUpdateParticipantRequest } from './types/participant.js';
|
|
4
|
-
export type { ConversationType, ConversationParticipantDto, ConversationDto, ConversationListItemDto, ConversationMediaKindFilter, ConversationMediaKind, ConversationMediaBlockType, ConversationMediaItemDto, CreateConversationRequest, UpdateConversationRequest, AddParticipantRequest, } from './types/conversation.js';
|
|
4
|
+
export type { ConversationType, ConversationParticipantDto, SelfReadState, ConversationDto, ConversationListItemDto, ConversationMediaKindFilter, ConversationMediaKind, ConversationMediaBlockType, ConversationMediaItemDto, CreateConversationRequest, UpdateConversationRequest, AddParticipantRequest, } from './types/conversation.js';
|
|
5
5
|
export type { SenderType, SystemEventType, SystemEventInfo, ReactionGroup, ReactionSummary, MessageDto, SendMessageRequest, EditMessageRequest, MarkAsReadRequest, } from './types/message.js';
|
|
6
6
|
export type { TextFormat, ButtonAction, ButtonStyle, ChoiceMode, QuickReplyAction, MentionType, TextBlock, ImageBlock, VideoBlock, AudioBlock, FileBlock, LinkPreviewBlock, EmbedBlock, LocationBlock, ContactBlock, ChoiceOption, ChoiceBlock, CardField, ActionButton, CardBlock, CarouselBlock, DividerBlock, CustomBlock, Block, QuickReply, Mention, } from './types/block.js';
|
|
7
7
|
export { isTextBlock, isImageBlock, isVideoBlock, isAudioBlock, isFileBlock, isLinkPreviewBlock, isEmbedBlock, isLocationBlock, isContactBlock, isChoiceBlock, isCardBlock, isCarouselBlock, isDividerBlock, isCustomBlock, withTypeFirst, normalizeBlocksForSend, } from './types/block.js';
|
|
@@ -15,6 +15,8 @@ export { HttpClient } from './http/HttpClient.js';
|
|
|
15
15
|
export type { HttpClientOptions } from './http/HttpClient.js';
|
|
16
16
|
export { TypedEventEmitter } from './utils/TypedEventEmitter.js';
|
|
17
17
|
export type { Unsubscribe } from './utils/TypedEventEmitter.js';
|
|
18
|
+
export { computeUnreadCount } from './utils/unread.js';
|
|
19
|
+
export type { UnreadCountFields } from './utils/unread.js';
|
|
18
20
|
export { ChatHubClient } from './realtime/ChatHubClient.js';
|
|
19
21
|
export type { ChatHubClientOptions, ChatHubEventMap } from './realtime/ChatHubClient.js';
|
|
20
22
|
export { NotificationHubClient } from './realtime/NotificationHubClient.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,8 @@ export { ChatApiError } from './errors/ChatApiError.js';
|
|
|
7
7
|
export { HttpClient } from './http/HttpClient.js';
|
|
8
8
|
// Utils — TypedEventEmitter
|
|
9
9
|
export { TypedEventEmitter } from './utils/TypedEventEmitter.js';
|
|
10
|
+
// Utils — unread badge helper
|
|
11
|
+
export { computeUnreadCount } from './utils/unread.js';
|
|
10
12
|
// Real-time — ChatHubClient
|
|
11
13
|
export { ChatHubClient } from './realtime/ChatHubClient.js';
|
|
12
14
|
// Real-time — NotificationHubClient
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,oDAAoD;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,oDAAoD;AA+EpD,4CAA4C;AAC5C,OAAO,EACL,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,kBAAkB,EAClB,YAAY,EACZ,eAAe,EACf,cAAc,EACd,aAAa,EACb,WAAW,EACX,eAAe,EACf,cAAc,EACd,aAAa,EACb,aAAa,EACb,sBAAsB,GACvB,MAAM,kBAAkB,CAAC;AAgE1B,SAAS;AACT,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAExD,cAAc;AACd,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAGlD,4BAA4B;AAC5B,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAGjE,8BAA8B;AAC9B,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAGvD,4BAA4B;AAC5B,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAG5D,oCAAoC;AACpC,OAAO,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AAM5E,kCAAkC;AAClC,OAAO,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AAGxE,yCAAyC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -15,7 +15,20 @@ export interface ConversationParticipantDto {
|
|
|
15
15
|
lastReadMessageId?: string | null;
|
|
16
16
|
/** ISO 8601 */
|
|
17
17
|
lastReadAt?: string | null;
|
|
18
|
-
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Caller's per-conversation read state. Included on `ConversationDto`.
|
|
21
|
+
*
|
|
22
|
+
* `null` only when the caller is not an active participant; otherwise it
|
|
23
|
+
* carries the caller's own read pointer and mark-unread flag (other
|
|
24
|
+
* participants' values are private and not exposed by the server).
|
|
25
|
+
*
|
|
26
|
+
* Unread badge for this caller:
|
|
27
|
+
* `max(conversation.messageSequence - lastReadSequence, markedUnread ? 1 : 0)`
|
|
28
|
+
*/
|
|
29
|
+
export interface SelfReadState {
|
|
30
|
+
lastReadSequence: number;
|
|
31
|
+
markedUnread: boolean;
|
|
19
32
|
}
|
|
20
33
|
/** Full conversation object */
|
|
21
34
|
export interface ConversationDto {
|
|
@@ -28,13 +41,33 @@ export interface ConversationDto {
|
|
|
28
41
|
/** ISO 8601, null when no messages yet */
|
|
29
42
|
lastMessageAt?: string | null;
|
|
30
43
|
lastMessageId?: string | null;
|
|
44
|
+
/**
|
|
45
|
+
* Monotonically increasing sequence assigned to the latest message in the
|
|
46
|
+
* conversation. Used together with `selfReadState` to compute the unread
|
|
47
|
+
* badge locally without a server-side precomputed count.
|
|
48
|
+
*/
|
|
49
|
+
messageSequence: number;
|
|
31
50
|
/** ISO 8601 */
|
|
32
51
|
createdAt: string;
|
|
33
52
|
/** ISO 8601 */
|
|
34
53
|
updatedAt: string;
|
|
54
|
+
/** Caller's own read state. `null` when the caller is not an active participant. */
|
|
55
|
+
selfReadState: SelfReadState | null;
|
|
35
56
|
participants: ConversationParticipantDto[];
|
|
36
57
|
}
|
|
37
|
-
/**
|
|
58
|
+
/**
|
|
59
|
+
* Lightweight conversation item for listing.
|
|
60
|
+
*
|
|
61
|
+
* The server does NOT send a precomputed `unreadCount`. Instead it sends
|
|
62
|
+
* three fields that let clients compute the badge locally and keep it in
|
|
63
|
+
* sync with `UnreadCountChanged` / `ReadStatusChanged` SignalR events
|
|
64
|
+
* without refetching:
|
|
65
|
+
*
|
|
66
|
+
* unread = max(messageSequence - lastReadSequence, markedUnread ? 1 : 0)
|
|
67
|
+
*
|
|
68
|
+
* `lastReadSequence` and `markedUnread` are the **caller's** values — the
|
|
69
|
+
* list is already filtered to conversations the caller participates in.
|
|
70
|
+
*/
|
|
38
71
|
export interface ConversationListItemDto {
|
|
39
72
|
id: string;
|
|
40
73
|
type: ConversationType;
|
|
@@ -45,12 +78,17 @@ export interface ConversationListItemDto {
|
|
|
45
78
|
lastMessageAt?: string | null;
|
|
46
79
|
/** null when conversation has no messages yet */
|
|
47
80
|
lastMessageId?: string | null;
|
|
81
|
+
/** Latest sequence number assigned to a message in this conversation. */
|
|
82
|
+
messageSequence: number;
|
|
83
|
+
/** Caller's last-read sequence (0 if never read). */
|
|
84
|
+
lastReadSequence: number;
|
|
85
|
+
/** Caller's mark-as-unread flag. */
|
|
86
|
+
markedUnread: boolean;
|
|
48
87
|
/** Full message DTO for preview. null when conversation has no messages yet */
|
|
49
88
|
lastMessage?: MessageDto | null;
|
|
50
89
|
isPinned: boolean;
|
|
51
90
|
/** ISO 8601, null when not pinned */
|
|
52
91
|
pinnedAt?: string | null;
|
|
53
|
-
unreadCount: number;
|
|
54
92
|
participantCount: number;
|
|
55
93
|
}
|
|
56
94
|
export interface CreateConversationRequest {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conversation.d.ts","sourceRoot":"","sources":["../../src/types/conversation.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE/C,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,UAAU,GAAG,MAAM,CAAC;AAE7D,wDAAwD;AACxD,MAAM,WAAW,0BAA0B;IACzC,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,qCAAqC;IACrC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,eAAe;IACf,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,eAAe;IACf,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"conversation.d.ts","sourceRoot":"","sources":["../../src/types/conversation.ts"],"names":[],"mappings":"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"}
|
|
@@ -39,9 +39,20 @@ export interface MentionedNotificationDto {
|
|
|
39
39
|
/** ISO 8601 */
|
|
40
40
|
sentAt: string;
|
|
41
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Server không gửi unread count tuyệt đối — chỉ push sequence mới khi
|
|
44
|
+
* `MessageSequence` của conversation tăng. Client tự compute badge:
|
|
45
|
+
* `unread = max(newSequence - cached lastReadSequence, markedUnread ? 1 : 0)`
|
|
46
|
+
*
|
|
47
|
+
* Event chỉ gửi tới participant đang online và KHÔNG gửi cho sender.
|
|
48
|
+
* System messages không bump sequence nên không phát sinh event này.
|
|
49
|
+
*/
|
|
42
50
|
export interface UnreadCountChangedDto {
|
|
43
51
|
conversationId: string;
|
|
44
|
-
|
|
52
|
+
/** `MessageSequence` mới sau khi bump */
|
|
53
|
+
newSequence: number;
|
|
54
|
+
/** User vừa gửi tin (client có thể dùng để hiển thị preview) */
|
|
55
|
+
senderId: string;
|
|
45
56
|
/** ISO 8601 */
|
|
46
57
|
lastMessageAt: string;
|
|
47
58
|
lastMessageId: string;
|
|
@@ -90,12 +101,20 @@ export interface ConversationPinnedDto {
|
|
|
90
101
|
export interface ConversationUnpinnedDto {
|
|
91
102
|
conversationId: string;
|
|
92
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Trạng thái đọc của conversation thay đổi do chính user này gọi
|
|
106
|
+
* `POST /api/conversations/{id}/read` hoặc `DELETE /api/conversations/{id}/read`.
|
|
107
|
+
* Chỉ gửi tới owner. Client cập nhật cached `lastReadSequence` rồi tính lại badge:
|
|
108
|
+
* `unread = max(cachedMessageSequence - lastReadSequence, markedUnread ? 1 : 0)`
|
|
109
|
+
*/
|
|
93
110
|
export interface ReadStatusChangedDto {
|
|
94
111
|
conversationId: string;
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
|
|
112
|
+
/** ID tin nhắn cuối đã đọc (null khi MarkAsUnread) */
|
|
113
|
+
lastReadMessageId?: string | null;
|
|
114
|
+
/** High-water mark mới (`Conversation.MessageSequence` tại thời điểm read) */
|
|
115
|
+
lastReadSequence: number;
|
|
116
|
+
/** `true` khi user thủ công Mark as Unread; `false` sau khi đọc thành công */
|
|
117
|
+
markedUnread: boolean;
|
|
99
118
|
}
|
|
100
119
|
/**
|
|
101
120
|
* Conversation bị ẩn/xóa khỏi danh sách của user thực hiện hành động.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notification-events.d.ts","sourceRoot":"","sources":["../../src/types/notification-events.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"notification-events.d.ts","sourceRoot":"","sources":["../../src/types/notification-events.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ConversationDto, ConversationListItemDto } from '../types/conversation.js';
|
|
2
|
+
/**
|
|
3
|
+
* Minimal shape needed to compute an unread badge. `ConversationListItemDto`
|
|
4
|
+
* is structurally assignable to this; callers with their own cache shape can
|
|
5
|
+
* pass any object carrying the three fields.
|
|
6
|
+
*/
|
|
7
|
+
export interface UnreadCountFields {
|
|
8
|
+
messageSequence: number;
|
|
9
|
+
lastReadSequence: number;
|
|
10
|
+
markedUnread: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Compute the caller's unread badge for a conversation.
|
|
14
|
+
*
|
|
15
|
+
* The server intentionally does not send a precomputed `unreadCount`; it sends
|
|
16
|
+
* the latest `messageSequence` plus the caller's `lastReadSequence` and
|
|
17
|
+
* `markedUnread` flag so clients can derive the badge locally and keep it in
|
|
18
|
+
* sync with `UnreadCountChanged` / `ReadStatusChanged` SignalR events without
|
|
19
|
+
* refetching. The formula is:
|
|
20
|
+
*
|
|
21
|
+
* max(messageSequence - lastReadSequence, markedUnread ? 1 : 0)
|
|
22
|
+
*
|
|
23
|
+
* The `max` with `0` (when `markedUnread` is false) also clamps the result if
|
|
24
|
+
* a transient race makes `lastReadSequence > messageSequence`. Returns `0`
|
|
25
|
+
* when given a `ConversationDto` whose `selfReadState` is `null` (caller is
|
|
26
|
+
* not an active participant).
|
|
27
|
+
*/
|
|
28
|
+
export declare function computeUnreadCount(conversation: ConversationDto): number;
|
|
29
|
+
export declare function computeUnreadCount(item: ConversationListItemDto): number;
|
|
30
|
+
export declare function computeUnreadCount(fields: UnreadCountFields): number;
|
|
31
|
+
//# sourceMappingURL=unread.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unread.d.ts","sourceRoot":"","sources":["../../src/utils/unread.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AAEzF;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,OAAO,CAAC;CACvB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,eAAe,GAAG,MAAM,CAAC;AAC1E,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,uBAAuB,GAAG,MAAM,CAAC;AAC1E,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// utils/unread.ts — Unread count helper
|
|
2
|
+
export function computeUnreadCount(input) {
|
|
3
|
+
if ('selfReadState' in input) {
|
|
4
|
+
if (input.selfReadState === null)
|
|
5
|
+
return 0;
|
|
6
|
+
return Math.max(input.messageSequence - input.selfReadState.lastReadSequence, input.selfReadState.markedUnread ? 1 : 0);
|
|
7
|
+
}
|
|
8
|
+
return Math.max(input.messageSequence - input.lastReadSequence, input.markedUnread ? 1 : 0);
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=unread.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unread.js","sourceRoot":"","sources":["../../src/utils/unread.ts"],"names":[],"mappings":"AAAA,wCAAwC;AAkCxC,MAAM,UAAU,kBAAkB,CAChC,KAA0C;IAE1C,IAAI,eAAe,IAAI,KAAK,EAAE,CAAC;QAC7B,IAAI,KAAK,CAAC,aAAa,KAAK,IAAI;YAAE,OAAO,CAAC,CAAC;QAC3C,OAAO,IAAI,CAAC,GAAG,CACb,KAAK,CAAC,eAAe,GAAG,KAAK,CAAC,aAAa,CAAC,gBAAgB,EAC5D,KAAK,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CACzC,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CAAC,GAAG,CACb,KAAK,CAAC,eAAe,GAAG,KAAK,CAAC,gBAAgB,EAC9C,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAC3B,CAAC;AACJ,CAAC"}
|