Skip to content

Commit b0fb3aa

Browse files
committed
enhancement: optimize markAs APIs
1 parent 3c00c29 commit b0fb3aa

File tree

8 files changed

+139
-59
lines changed

8 files changed

+139
-59
lines changed

packages/uikit-chat-hooks/src/channel/useGroupChannelList/useGroupChannelListWithCollection.ts

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useEffect, useRef } from 'react';
22

3-
import { GroupChannelFilter, GroupChannelListOrder } from '@sendbird/chat/groupChannel';
4-
import type { SendbirdChatSDK, SendbirdGroupChannelCollection } from '@sendbird/uikit-utils';
3+
import { GroupChannelEventSource, GroupChannelFilter, GroupChannelListOrder } from '@sendbird/chat/groupChannel';
4+
import type { SendbirdBaseChannel, SendbirdChatSDK, SendbirdGroupChannelCollection } from '@sendbird/uikit-utils';
55
import { confirmAndMarkAsDelivered, useAsyncEffect, useFreshCallback, useUniqId } from '@sendbird/uikit-utils';
66

77
import { useAppFeatures } from '../../common/useAppFeatures';
@@ -37,11 +37,23 @@ export const useGroupChannelListWithCollection: UseGroupChannelList = (sdk, user
3737
const { loading, groupChannels, refreshing, setChannels, deleteChannels, updateRefreshing, updateLoading } =
3838
useGroupChannelListReducer();
3939

40-
const updateChannelsAndMarkAsDelivered = (markAsDelivered: boolean) => {
40+
const updateChannelsAndMarkAsDelivered = (
41+
markAsDelivered: boolean,
42+
source?: GroupChannelEventSource,
43+
updatedChannels?: SendbirdBaseChannel[],
44+
) => {
4145
const channels = collectionRef.current?.channels ?? [];
4246
setChannels(channels, true);
4347
if (markAsDelivered && deliveryReceiptEnabled) {
44-
channels.forEach((channel) => confirmAndMarkAsDelivered(sdk, channel));
48+
switch (source) {
49+
case GroupChannelEventSource.EVENT_MESSAGE_RECEIVED:
50+
case GroupChannelEventSource.EVENT_MESSAGE_SENT:
51+
case GroupChannelEventSource.SYNC_CHANNEL_BACKGROUND:
52+
case GroupChannelEventSource.SYNC_CHANNEL_CHANGELOGS:
53+
case undefined:
54+
confirmAndMarkAsDelivered(updatedChannels ?? channels);
55+
break;
56+
}
4557
}
4658
};
4759

@@ -52,11 +64,11 @@ export const useGroupChannelListWithCollection: UseGroupChannelList = (sdk, user
5264
collectionRef.current = createGroupChannelListCollection(sdk, options?.collectionCreator);
5365

5466
collectionRef.current?.setGroupChannelCollectionHandler({
55-
onChannelsAdded: () => {
56-
updateChannelsAndMarkAsDelivered(true);
67+
onChannelsAdded: (context, channels) => {
68+
updateChannelsAndMarkAsDelivered(true, context.source, channels);
5769
},
58-
onChannelsUpdated: () => {
59-
updateChannelsAndMarkAsDelivered(true);
70+
onChannelsUpdated: (context, channels) => {
71+
updateChannelsAndMarkAsDelivered(true, context.source, channels);
6072
},
6173
onChannelsDeleted: () => {
6274
updateChannelsAndMarkAsDelivered(false);

packages/uikit-chat-hooks/src/channel/useGroupChannelList/useGroupChannelListWithQuery.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export const useGroupChannelListWithQuery: UseGroupChannelList = (sdk, userId, o
4343

4444
const updateChannelsAndMarkAsDelivered = (channels: SendbirdChannel[]) => {
4545
updateChannels(channels);
46-
if (deliveryReceiptEnabled) channels.forEach((channel) => confirmAndMarkAsDelivered(sdk, channel));
46+
if (deliveryReceiptEnabled) confirmAndMarkAsDelivered(channels);
4747
};
4848

4949
const init = useFreshCallback(async (uid?: string) => {
@@ -55,7 +55,7 @@ export const useGroupChannelListWithQuery: UseGroupChannelList = (sdk, userId, o
5555
const channels = await queryRef.current.next();
5656

5757
setChannels(channels, true);
58-
if (deliveryReceiptEnabled) channels.forEach((channel) => confirmAndMarkAsDelivered(sdk, channel));
58+
if (deliveryReceiptEnabled) confirmAndMarkAsDelivered(channels);
5959
}
6060
}
6161
});
@@ -67,21 +67,24 @@ export const useGroupChannelListWithQuery: UseGroupChannelList = (sdk, userId, o
6767
}, [init, userId]);
6868

6969
useChannelHandler(sdk, HOOK_NAME, {
70-
onChannelChanged: (channel) => updateChannelsAndMarkAsDelivered([channel]),
70+
onChannelChanged: (channel) => updateChannels([channel]),
7171
onChannelFrozen: (channel) => updateChannels([channel]),
7272
onChannelUnfrozen: (channel) => updateChannels([channel]),
7373
onChannelMemberCountChanged: (channels) => updateChannels(channels),
7474
onChannelDeleted: (url) => deleteChannels([url]),
75-
onUserJoined: (channel) => updateChannelsAndMarkAsDelivered([channel]),
75+
onUserJoined: (channel) => updateChannels([channel]),
7676
onUserLeft: (channel, user) => {
7777
const isMe = user.userId === userId;
7878
if (isMe) deleteChannels([channel.url]);
79-
else updateChannelsAndMarkAsDelivered([channel]);
79+
else updateChannels([channel]);
8080
},
8181
onUserBanned(channel, user) {
8282
const isMe = user.userId === userId;
8383
if (isMe) deleteChannels([channel.url]);
84-
else updateChannelsAndMarkAsDelivered([channel]);
84+
else updateChannels([channel]);
85+
},
86+
onMessageReceived(channel) {
87+
updateChannelsAndMarkAsDelivered([channel]);
8588
},
8689
});
8790

@@ -95,7 +98,7 @@ export const useGroupChannelListWithQuery: UseGroupChannelList = (sdk, userId, o
9598
if (queryRef.current?.hasNext) {
9699
const channels = await queryRef.current.next();
97100
setChannels(channels, false);
98-
if (deliveryReceiptEnabled) channels.forEach((channel) => confirmAndMarkAsDelivered(sdk, channel));
101+
if (deliveryReceiptEnabled) confirmAndMarkAsDelivered(channels);
99102
}
100103
});
101104

packages/uikit-chat-hooks/src/channel/useGroupChannelMessages/useGroupChannelMessagesWithCollection.ts

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,14 @@
11
import { useCallback, useEffect, useRef } from 'react';
22

3-
import { MessageCollectionInitPolicy, MessageFilter } from '@sendbird/chat/groupChannel';
3+
import { MessageCollectionInitPolicy, MessageEventSource, MessageFilter } from '@sendbird/chat/groupChannel';
44
import type {
55
SendbirdChannel,
66
SendbirdFileMessage,
77
SendbirdGroupChannel,
88
SendbirdMessageCollection,
99
} from '@sendbird/uikit-utils';
10-
import {
11-
Logger,
12-
confirmAndMarkAsDelivered,
13-
confirmAndMarkAsRead,
14-
isDifferentChannel,
15-
useForceUpdate,
16-
} from '@sendbird/uikit-utils';
10+
import { Logger, confirmAndMarkAsRead, isDifferentChannel, useForceUpdate } from '@sendbird/uikit-utils';
1711

18-
import { useAppFeatures } from '../../common/useAppFeatures';
1912
import { useChannelHandler } from '../../handler/useChannelHandler';
2013
import type { UseGroupChannelMessages, UseGroupChannelMessagesOptions } from '../../types';
2114
import { useActiveGroupChannel } from '../useActiveGroupChannel';
@@ -33,8 +26,6 @@ const createMessageCollection = (
3326
const HOOK_NAME = 'useGroupChannelMessagesWithCollection';
3427

3528
export const useGroupChannelMessagesWithCollection: UseGroupChannelMessages = (sdk, channel, userId, options) => {
36-
const { deliveryReceiptEnabled } = useAppFeatures(sdk);
37-
3829
const collectionRef = useRef<SendbirdMessageCollection>();
3930

4031
// NOTE: We cannot determine the channel object of Sendbird SDK is stale or not, so force update af
@@ -57,12 +48,7 @@ export const useGroupChannelMessagesWithCollection: UseGroupChannelMessages = (s
5748

5849
const channelMarkAs = async () => {
5950
try {
60-
if (deliveryReceiptEnabled) await confirmAndMarkAsDelivered(sdk, activeChannel);
61-
} catch (e) {
62-
Logger.warn(`[${HOOK_NAME}/channelMarkAs/Delivered]`, e);
63-
}
64-
try {
65-
await confirmAndMarkAsRead(sdk, [activeChannel]);
51+
await confirmAndMarkAsRead([activeChannel]);
6652
} catch (e) {
6753
Logger.warn(`[${HOOK_NAME}/channelMarkAs/Read]`, e);
6854
}
@@ -84,8 +70,14 @@ export const useGroupChannelMessagesWithCollection: UseGroupChannelMessages = (s
8470
channelMarkAs();
8571

8672
collectionRef.current?.setMessageCollectionHandler({
87-
onMessagesAdded: (_, channel, messages) => {
88-
channelMarkAs();
73+
onMessagesAdded: (_, __, messages) => {
74+
switch (_.source) {
75+
case MessageEventSource.EVENT_MESSAGE_RECEIVED:
76+
case MessageEventSource.EVENT_MESSAGE_SENT_SUCCESS:
77+
case MessageEventSource.SYNC_MESSAGE_FILL:
78+
channelMarkAs();
79+
break;
80+
}
8981
updateNextMessages(messages, false, sdk.currentUser.userId);
9082
updateChannel(channel);
9183
},

packages/uikit-chat-hooks/src/channel/useGroupChannelMessages/useGroupChannelMessagesWithQuery.ts

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,13 @@ import { useCallback, useRef } from 'react';
33
import type { SendbirdBaseChannel, SendbirdGroupChannel } from '@sendbird/uikit-utils';
44
import {
55
Logger,
6-
confirmAndMarkAsDelivered,
76
confirmAndMarkAsRead,
87
isDifferentChannel,
98
useAsyncEffect,
109
useForceUpdate,
1110
} from '@sendbird/uikit-utils';
1211
import type { SendbirdPreviousMessageListQuery } from '@sendbird/uikit-utils';
1312

14-
import { useAppFeatures } from '../../common/useAppFeatures';
1513
import { useChannelHandler } from '../../handler/useChannelHandler';
1614
import type { UseGroupChannelMessages, UseGroupChannelMessagesOptions } from '../../types';
1715
import { useActiveGroupChannel } from '../useActiveGroupChannel';
@@ -30,8 +28,6 @@ const createMessageQuery = (
3028

3129
const HOOK_NAME = 'useGroupChannelMessagesWithQuery';
3230
export const useGroupChannelMessagesWithQuery: UseGroupChannelMessages = (sdk, channel, userId, options) => {
33-
const { deliveryReceiptEnabled } = useAppFeatures(sdk);
34-
3531
const queryRef = useRef<SendbirdPreviousMessageListQuery>();
3632

3733
// NOTE: We cannot determine the channel object of Sendbird SDK is stale or not, so force update after setActiveChannel
@@ -54,12 +50,7 @@ export const useGroupChannelMessagesWithQuery: UseGroupChannelMessages = (sdk, c
5450

5551
const channelMarkAs = async () => {
5652
try {
57-
if (deliveryReceiptEnabled) await confirmAndMarkAsDelivered(sdk, activeChannel);
58-
} catch (e) {
59-
Logger.warn(`[${HOOK_NAME}/channelMarkAs/Delivered]`, e);
60-
}
61-
try {
62-
await confirmAndMarkAsRead(sdk, [activeChannel]);
53+
await confirmAndMarkAsRead([channel]);
6354
} catch (e) {
6455
Logger.warn(`[${HOOK_NAME}/channelMarkAs/Read]`, e);
6556
}

packages/uikit-react-native/src/contexts/SendbirdChat.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ export const SendbirdChatProvider = ({
8787

8888
const markAsDeliveredWithChannel: Context['markAsDeliveredWithChannel'] = useCallback(
8989
(channel: SendbirdGroupChannel) => {
90-
if (appFeatures.deliveryReceiptEnabled) confirmAndMarkAsDelivered(sdkInstance, channel);
90+
if (appFeatures.deliveryReceiptEnabled) confirmAndMarkAsDelivered([channel]);
9191
},
9292
[sdkInstance, appFeatures.deliveryReceiptEnabled],
9393
);

packages/uikit-utils/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export {
1616
emailRegex,
1717
phoneRegex,
1818
} from './shared/regex';
19+
export { BufferedRequest } from './shared/bufferedRequest';
1920

2021
export * from './hooks';
2122
export * from './ui-format/groupChannel';

packages/uikit-utils/src/sendbird/channel.ts

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
1-
import type {
2-
SendbirdBaseChannel,
3-
SendbirdChannel,
4-
SendbirdChatSDK,
5-
SendbirdGroupChannel,
6-
SendbirdOpenChannel,
7-
} from '../types';
1+
import { BufferedRequest } from '../shared/bufferedRequest';
2+
import type { SendbirdBaseChannel, SendbirdChannel, SendbirdGroupChannel, SendbirdOpenChannel } from '../types';
83

94
/**
105
* Diff utils for channel
@@ -21,15 +16,16 @@ export const isGroupChannelChatUnavailable = (channel: SendbirdGroupChannel) =>
2116
return channel.myMutedState === 'muted' || (channel.isFrozen && channel.myRole !== 'operator');
2217
};
2318

24-
export const confirmAndMarkAsRead = async (sdk: SendbirdChatSDK, channels: SendbirdBaseChannel[]) => {
25-
const channelUrls = channels.filter((it) => it.isGroupChannel() && it.unreadMessageCount > 0).map((it) => it.url);
26-
await sdk.groupChannel.markAsReadWithChannelUrls(channelUrls);
19+
export const confirmAndMarkAsRead = async (channels: SendbirdBaseChannel[]) => {
20+
channels
21+
.filter((it): it is SendbirdGroupChannel => it.isGroupChannel() && it.unreadMessageCount > 0)
22+
.forEach((it) => BufferedRequest.markAsRead.push(() => it.markAsRead()));
2723
};
2824

29-
export const confirmAndMarkAsDelivered = async (sdk: SendbirdChatSDK, channel: SendbirdBaseChannel) => {
30-
if (channel.isGroupChannel() && channel.unreadMessageCount > 0) {
31-
await sdk.groupChannel.markAsDelivered(channel.url);
32-
}
25+
export const confirmAndMarkAsDelivered = async (channels: SendbirdBaseChannel[]) => {
26+
channels
27+
.filter((it): it is SendbirdGroupChannel => it.isGroupChannel() && it.unreadMessageCount > 0)
28+
.forEach((it) => BufferedRequest.markAsDelivered.push(() => it.markAsDelivered()));
3329
};
3430

3531
export function isDefaultCoverImage(coverUrl: string) {
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
type Func = (...args: unknown[]) => Promise<unknown>;
2+
type State = 'idle' | 'processing';
3+
4+
let REQ_PER_TIMEOUT = 5;
5+
let TIMEOUT_MILLS = 1000;
6+
const SAFE_TIMEOUT_BUFFER = 100;
7+
8+
export class BufferedRequest {
9+
public static markAsRead = BufferedRequest.create();
10+
public static markAsDelivered = BufferedRequest.create();
11+
12+
public static get reqPerTimeout() {
13+
return REQ_PER_TIMEOUT;
14+
}
15+
public static set reqPerTimeout(value: number) {
16+
REQ_PER_TIMEOUT = value;
17+
BufferedRequest.markAsRead = BufferedRequest.create();
18+
BufferedRequest.markAsDelivered = BufferedRequest.create();
19+
}
20+
21+
public static get timeoutMills() {
22+
return TIMEOUT_MILLS;
23+
}
24+
public static set timeoutMills(value: number) {
25+
TIMEOUT_MILLS = value;
26+
BufferedRequest.markAsRead = BufferedRequest.create();
27+
BufferedRequest.markAsDelivered = BufferedRequest.create();
28+
}
29+
30+
public static create(reqPerTimeout = REQ_PER_TIMEOUT, timeoutMills = TIMEOUT_MILLS) {
31+
const waitQueue: Func[] = [];
32+
const nextQueue: Func[] = [];
33+
34+
let state: State = 'idle';
35+
let timeout: NodeJS.Timeout | undefined;
36+
37+
return {
38+
push(func: Func) {
39+
waitQueue.push(func);
40+
this.invoke();
41+
},
42+
shift() {
43+
if (nextQueue.length < reqPerTimeout) {
44+
const nextRemains = Math.min(reqPerTimeout - nextQueue.length, waitQueue.length);
45+
for (let n = 0; n < nextRemains; n++) {
46+
const func = waitQueue.shift();
47+
if (func) nextQueue.push(func);
48+
}
49+
}
50+
},
51+
handleIdle() {
52+
if (0 < nextQueue.length) {
53+
state = 'processing';
54+
this.invoke();
55+
}
56+
},
57+
handleProcessing() {
58+
if (timeout) return;
59+
60+
timeout = setTimeout(() => {
61+
timeout = undefined;
62+
if (0 < nextQueue.length || 0 < waitQueue.length) {
63+
this.invoke();
64+
} else {
65+
state = 'idle';
66+
}
67+
}, timeoutMills + SAFE_TIMEOUT_BUFFER);
68+
69+
nextQueue.forEach((func) => func());
70+
nextQueue.length = 0;
71+
},
72+
async invoke() {
73+
this.shift();
74+
75+
if (state === 'idle') {
76+
this.handleIdle();
77+
}
78+
79+
if (state === 'processing') {
80+
this.handleProcessing();
81+
}
82+
},
83+
};
84+
}
85+
}

0 commit comments

Comments
 (0)