stream-chat-react-native-core 6.7.3-beta.1 → 6.7.3-beta.3
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/lib/commonjs/components/Channel/Channel.js +296 -293
- package/lib/commonjs/components/Channel/Channel.js.map +1 -1
- package/lib/commonjs/components/Channel/hooks/useMessageListPagination.js +133 -147
- package/lib/commonjs/components/Channel/hooks/useMessageListPagination.js.map +1 -1
- package/lib/commonjs/components/KeyboardCompatibleView/KeyboardCompatibleView.js +7 -12
- package/lib/commonjs/components/KeyboardCompatibleView/KeyboardCompatibleView.js.map +1 -1
- package/lib/commonjs/components/MessageList/MessageList.js +167 -179
- package/lib/commonjs/components/MessageList/MessageList.js.map +1 -1
- package/lib/commonjs/components/MessageList/hooks/useMessageList.js +60 -37
- package/lib/commonjs/components/MessageList/hooks/useMessageList.js.map +1 -1
- package/lib/commonjs/contexts/messageInputContext/MessageInputContext.js +450 -459
- package/lib/commonjs/contexts/messageInputContext/MessageInputContext.js.map +1 -1
- package/lib/commonjs/contexts/messagesContext/MessagesContext.js.map +1 -1
- package/lib/commonjs/hooks/index.js +11 -0
- package/lib/commonjs/hooks/index.js.map +1 -1
- package/lib/commonjs/hooks/useStableCallback.js +13 -0
- package/lib/commonjs/hooks/useStableCallback.js.map +1 -0
- package/lib/commonjs/version.json +1 -1
- package/lib/module/components/Channel/Channel.js +296 -293
- package/lib/module/components/Channel/Channel.js.map +1 -1
- package/lib/module/components/Channel/hooks/useMessageListPagination.js +133 -147
- package/lib/module/components/Channel/hooks/useMessageListPagination.js.map +1 -1
- package/lib/module/components/KeyboardCompatibleView/KeyboardCompatibleView.js +7 -12
- package/lib/module/components/KeyboardCompatibleView/KeyboardCompatibleView.js.map +1 -1
- package/lib/module/components/MessageList/MessageList.js +167 -179
- package/lib/module/components/MessageList/MessageList.js.map +1 -1
- package/lib/module/components/MessageList/hooks/useMessageList.js +60 -37
- package/lib/module/components/MessageList/hooks/useMessageList.js.map +1 -1
- package/lib/module/contexts/messageInputContext/MessageInputContext.js +450 -459
- package/lib/module/contexts/messageInputContext/MessageInputContext.js.map +1 -1
- package/lib/module/contexts/messagesContext/MessagesContext.js.map +1 -1
- package/lib/module/hooks/index.js +11 -0
- package/lib/module/hooks/index.js.map +1 -1
- package/lib/module/hooks/useStableCallback.js +13 -0
- package/lib/module/hooks/useStableCallback.js.map +1 -0
- package/lib/module/version.json +1 -1
- package/lib/typescript/components/Channel/Channel.d.ts.map +1 -1
- package/lib/typescript/components/Channel/hooks/useMessageListPagination.d.ts +3 -3
- package/lib/typescript/components/Channel/hooks/useMessageListPagination.d.ts.map +1 -1
- package/lib/typescript/components/KeyboardCompatibleView/KeyboardCompatibleView.d.ts +3 -0
- package/lib/typescript/components/KeyboardCompatibleView/KeyboardCompatibleView.d.ts.map +1 -1
- package/lib/typescript/components/MessageList/MessageList.d.ts.map +1 -1
- package/lib/typescript/components/MessageList/hooks/useMessageList.d.ts +4 -0
- package/lib/typescript/components/MessageList/hooks/useMessageList.d.ts.map +1 -1
- package/lib/typescript/contexts/messageInputContext/MessageInputContext.d.ts.map +1 -1
- package/lib/typescript/contexts/messagesContext/MessagesContext.d.ts +1 -1
- package/lib/typescript/contexts/messagesContext/MessagesContext.d.ts.map +1 -1
- package/lib/typescript/hooks/index.d.ts +1 -0
- package/lib/typescript/hooks/index.d.ts.map +1 -1
- package/lib/typescript/hooks/useStableCallback.d.ts +26 -0
- package/lib/typescript/hooks/useStableCallback.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/components/Channel/Channel.tsx +462 -431
- package/src/components/Channel/__tests__/Channel.test.js +8 -3
- package/src/components/Channel/hooks/useMessageListPagination.tsx +152 -147
- package/src/components/KeyboardCompatibleView/KeyboardCompatibleView.tsx +6 -4
- package/src/components/MessageList/MessageList.tsx +147 -112
- package/src/components/MessageList/hooks/useMessageList.ts +69 -38
- package/src/contexts/messageInputContext/MessageInputContext.tsx +293 -267
- package/src/contexts/messageInputContext/__tests__/pickFile.test.tsx +2 -1
- package/src/contexts/messagesContext/MessagesContext.tsx +1 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useStableCallback.ts +37 -0
- package/src/version.json +1 -1
|
@@ -74,6 +74,7 @@ import {
|
|
|
74
74
|
useTranslationContext,
|
|
75
75
|
} from '../../contexts/translationContext/TranslationContext';
|
|
76
76
|
import { TypingProvider } from '../../contexts/typingContext/TypingContext';
|
|
77
|
+
import { useStableCallback } from '../../hooks';
|
|
77
78
|
import { useAppStateListener } from '../../hooks/useAppStateListener';
|
|
78
79
|
|
|
79
80
|
import {
|
|
@@ -716,6 +717,12 @@ const ChannelWithContext = <
|
|
|
716
717
|
* Its a map of filename to AbortController
|
|
717
718
|
*/
|
|
718
719
|
const uploadAbortControllerRef = useRef<Map<string, AbortController>>(new Map());
|
|
720
|
+
/**
|
|
721
|
+
* This ref keeps track of message IDs which have already been optimistically updated.
|
|
722
|
+
* We need it to make sure we don't react on message.new/notification.message_new events
|
|
723
|
+
* if this is indeed the case, as it's a full list update for nothing.
|
|
724
|
+
*/
|
|
725
|
+
const optimisticallyUpdatedNewMessages = useMemo<Set<string>>(() => new Set(), []);
|
|
719
726
|
|
|
720
727
|
const channelId = channel?.id || '';
|
|
721
728
|
const pollCreationEnabled = !channel.disconnected && !!channel?.id && channel?.getConfig()?.polls;
|
|
@@ -741,14 +748,33 @@ const ChannelWithContext = <
|
|
|
741
748
|
channel,
|
|
742
749
|
});
|
|
743
750
|
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
751
|
+
const setReadThrottled = useMemo(
|
|
752
|
+
() =>
|
|
753
|
+
throttle(
|
|
754
|
+
() => {
|
|
755
|
+
if (channel) {
|
|
756
|
+
setRead(channel);
|
|
757
|
+
}
|
|
758
|
+
},
|
|
759
|
+
stateUpdateThrottleInterval,
|
|
760
|
+
throttleOptions,
|
|
761
|
+
),
|
|
762
|
+
[channel, stateUpdateThrottleInterval, setRead],
|
|
763
|
+
);
|
|
764
|
+
|
|
765
|
+
const copyMessagesStateFromChannelThrottled = useMemo(
|
|
766
|
+
() =>
|
|
767
|
+
throttle(
|
|
768
|
+
() => {
|
|
769
|
+
if (channel) {
|
|
770
|
+
copyMessagesStateFromChannel(channel);
|
|
771
|
+
}
|
|
772
|
+
},
|
|
773
|
+
newMessageStateUpdateThrottleInterval,
|
|
774
|
+
throttleOptions,
|
|
775
|
+
),
|
|
776
|
+
[channel, newMessageStateUpdateThrottleInterval, copyMessagesStateFromChannel],
|
|
777
|
+
);
|
|
752
778
|
|
|
753
779
|
const copyChannelState = useMemo(
|
|
754
780
|
() =>
|
|
@@ -759,13 +785,13 @@ const ChannelWithContext = <
|
|
|
759
785
|
copyMessagesStateFromChannel(channel);
|
|
760
786
|
}
|
|
761
787
|
},
|
|
762
|
-
|
|
788
|
+
stateUpdateThrottleInterval,
|
|
763
789
|
throttleOptions,
|
|
764
790
|
),
|
|
765
|
-
[channel,
|
|
791
|
+
[stateUpdateThrottleInterval, channel, copyStateFromChannel, copyMessagesStateFromChannel],
|
|
766
792
|
);
|
|
767
793
|
|
|
768
|
-
const handleEvent: EventHandler<StreamChatGenerics> = (event) => {
|
|
794
|
+
const handleEvent: EventHandler<StreamChatGenerics> = useStableCallback((event) => {
|
|
769
795
|
if (shouldSyncChannel) {
|
|
770
796
|
/**
|
|
771
797
|
* Ignore user.watching.start and user.watching.stop as we should not copy the entire state when
|
|
@@ -819,17 +845,35 @@ const ChannelWithContext = <
|
|
|
819
845
|
|
|
820
846
|
// only update channel state if the events are not the previously subscribed useEffect's subscription events
|
|
821
847
|
if (channel && channel.initialized) {
|
|
848
|
+
// we skip the new message events if we've already done an optimistic update for the new message
|
|
849
|
+
if (event.type === 'message.new' || event.type === 'notification.message_new') {
|
|
850
|
+
const messageId = event.message?.id ?? '';
|
|
851
|
+
if (
|
|
852
|
+
event.user?.id !== client.userID ||
|
|
853
|
+
!optimisticallyUpdatedNewMessages.has(messageId)
|
|
854
|
+
) {
|
|
855
|
+
copyMessagesStateFromChannelThrottled();
|
|
856
|
+
}
|
|
857
|
+
optimisticallyUpdatedNewMessages.delete(messageId);
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
if (event.type === 'message.read' || event.type === 'notification.mark_read') {
|
|
862
|
+
setReadThrottled();
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
|
|
822
866
|
copyChannelState();
|
|
823
867
|
}
|
|
824
868
|
}
|
|
825
|
-
};
|
|
869
|
+
});
|
|
826
870
|
|
|
827
871
|
useEffect(() => {
|
|
828
872
|
let listener: ReturnType<typeof channel.on>;
|
|
829
873
|
const initChannel = async () => {
|
|
830
874
|
setLastRead(new Date());
|
|
831
875
|
const unreadCount = channel.countUnread();
|
|
832
|
-
if (!channel || !shouldSyncChannel
|
|
876
|
+
if (!channel || !shouldSyncChannel) {
|
|
833
877
|
return;
|
|
834
878
|
}
|
|
835
879
|
let errored = false;
|
|
@@ -900,20 +944,6 @@ const ChannelWithContext = <
|
|
|
900
944
|
return unsubscribe;
|
|
901
945
|
}, [channel?.cid, client]);
|
|
902
946
|
|
|
903
|
-
/**
|
|
904
|
-
* Subscription to the Notification mark_read event.
|
|
905
|
-
*/
|
|
906
|
-
useEffect(() => {
|
|
907
|
-
const handleEvent: EventHandler<StreamChatGenerics> = (event) => {
|
|
908
|
-
if (channel.cid === event.cid) {
|
|
909
|
-
setRead(channel);
|
|
910
|
-
}
|
|
911
|
-
};
|
|
912
|
-
|
|
913
|
-
const { unsubscribe } = client.on('notification.mark_read', handleEvent);
|
|
914
|
-
return unsubscribe;
|
|
915
|
-
}, [channel, client, setRead]);
|
|
916
|
-
|
|
917
947
|
const threadPropsExists = !!threadProps;
|
|
918
948
|
|
|
919
949
|
useEffect(() => {
|
|
@@ -946,7 +976,7 @@ const ChannelWithContext = <
|
|
|
946
976
|
/**
|
|
947
977
|
* CHANNEL METHODS
|
|
948
978
|
*/
|
|
949
|
-
const
|
|
979
|
+
const markReadInternal: ChannelContextValue<StreamChatGenerics>['markRead'] = throttle(
|
|
950
980
|
async (options?: MarkReadFunctionOptions) => {
|
|
951
981
|
const { updateChannelUnreadState = true } = options ?? {};
|
|
952
982
|
if (!channel || channel?.disconnected || !clientChannelConfig?.read_events) {
|
|
@@ -975,7 +1005,9 @@ const ChannelWithContext = <
|
|
|
975
1005
|
throttleOptions,
|
|
976
1006
|
);
|
|
977
1007
|
|
|
978
|
-
const
|
|
1008
|
+
const markRead = useStableCallback(markReadInternal);
|
|
1009
|
+
|
|
1010
|
+
const reloadThread = useStableCallback(async () => {
|
|
979
1011
|
if (!channel || !thread?.id) {
|
|
980
1012
|
return;
|
|
981
1013
|
}
|
|
@@ -1008,9 +1040,9 @@ const ChannelWithContext = <
|
|
|
1008
1040
|
setThreadLoadingMore(false);
|
|
1009
1041
|
throw err;
|
|
1010
1042
|
}
|
|
1011
|
-
};
|
|
1043
|
+
});
|
|
1012
1044
|
|
|
1013
|
-
const resyncChannel = async () => {
|
|
1045
|
+
const resyncChannel = useStableCallback(async () => {
|
|
1014
1046
|
if (!channel || syncingChannelRef.current) {
|
|
1015
1047
|
return;
|
|
1016
1048
|
}
|
|
@@ -1066,7 +1098,7 @@ const ChannelWithContext = <
|
|
|
1066
1098
|
}
|
|
1067
1099
|
|
|
1068
1100
|
syncingChannelRef.current = false;
|
|
1069
|
-
};
|
|
1101
|
+
});
|
|
1070
1102
|
|
|
1071
1103
|
// resync channel is added to ref so that it can be used in useEffect without adding it as a dependency
|
|
1072
1104
|
const resyncChannelRef = useRef(resyncChannel);
|
|
@@ -1117,16 +1149,16 @@ const ChannelWithContext = <
|
|
|
1117
1149
|
*/
|
|
1118
1150
|
const clientChannelConfig = getChannelConfigSafely();
|
|
1119
1151
|
|
|
1120
|
-
const reloadChannel = async () => {
|
|
1152
|
+
const reloadChannel = useStableCallback(async () => {
|
|
1121
1153
|
try {
|
|
1122
1154
|
await loadLatestMessages();
|
|
1123
1155
|
} catch (err) {
|
|
1124
1156
|
console.warn('Reloading channel failed with error:', err);
|
|
1125
1157
|
}
|
|
1126
|
-
};
|
|
1158
|
+
});
|
|
1127
1159
|
|
|
1128
1160
|
const loadChannelAroundMessage: ChannelContextValue<StreamChatGenerics>['loadChannelAroundMessage'] =
|
|
1129
|
-
async ({ messageId: messageIdToLoadAround }): Promise<void> => {
|
|
1161
|
+
useStableCallback(async ({ messageId: messageIdToLoadAround }): Promise<void> => {
|
|
1130
1162
|
if (!messageIdToLoadAround) {
|
|
1131
1163
|
return;
|
|
1132
1164
|
}
|
|
@@ -1157,350 +1189,354 @@ const ChannelWithContext = <
|
|
|
1157
1189
|
} catch (err) {
|
|
1158
1190
|
console.warn('Loading channel around message failed with error:', err);
|
|
1159
1191
|
}
|
|
1160
|
-
};
|
|
1192
|
+
});
|
|
1161
1193
|
|
|
1162
1194
|
/**
|
|
1163
1195
|
* MESSAGE METHODS
|
|
1164
1196
|
*/
|
|
1165
|
-
const updateMessage: MessagesContextValue<StreamChatGenerics>['updateMessage'] =
|
|
1166
|
-
updatedMessage,
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
return;
|
|
1171
|
-
}
|
|
1197
|
+
const updateMessage: MessagesContextValue<StreamChatGenerics>['updateMessage'] =
|
|
1198
|
+
useStableCallback((updatedMessage, extraState = {}, throttled = false) => {
|
|
1199
|
+
if (!channel) {
|
|
1200
|
+
return;
|
|
1201
|
+
}
|
|
1172
1202
|
|
|
1173
|
-
|
|
1174
|
-
|
|
1203
|
+
channel.state.addMessageSorted(updatedMessage, true);
|
|
1204
|
+
if (throttled) {
|
|
1205
|
+
copyMessagesStateFromChannelThrottled();
|
|
1206
|
+
} else {
|
|
1207
|
+
copyMessagesStateFromChannel(channel);
|
|
1208
|
+
}
|
|
1175
1209
|
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1210
|
+
if (thread && updatedMessage.parent_id) {
|
|
1211
|
+
extraState.threadMessages = channel.state.threads[updatedMessage.parent_id] || [];
|
|
1212
|
+
setThreadMessages(extraState.threadMessages);
|
|
1213
|
+
}
|
|
1214
|
+
});
|
|
1181
1215
|
|
|
1182
|
-
const replaceMessage = (
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
channel
|
|
1188
|
-
|
|
1189
|
-
|
|
1216
|
+
const replaceMessage = useStableCallback(
|
|
1217
|
+
(
|
|
1218
|
+
oldMessage: MessageResponse<StreamChatGenerics>,
|
|
1219
|
+
newMessage: MessageResponse<StreamChatGenerics>,
|
|
1220
|
+
) => {
|
|
1221
|
+
if (channel) {
|
|
1222
|
+
channel.state.removeMessage(oldMessage);
|
|
1223
|
+
channel.state.addMessageSorted(newMessage, true);
|
|
1224
|
+
copyMessagesStateFromChannel(channel);
|
|
1190
1225
|
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1226
|
+
if (thread && newMessage.parent_id) {
|
|
1227
|
+
const threadMessages = channel.state.threads[newMessage.parent_id] || [];
|
|
1228
|
+
setThreadMessages(threadMessages);
|
|
1229
|
+
}
|
|
1194
1230
|
}
|
|
1195
|
-
}
|
|
1196
|
-
|
|
1231
|
+
},
|
|
1232
|
+
);
|
|
1197
1233
|
|
|
1198
|
-
const createMessagePreview = (
|
|
1199
|
-
|
|
1200
|
-
mentioned_users,
|
|
1201
|
-
parent_id,
|
|
1202
|
-
poll,
|
|
1203
|
-
poll_id,
|
|
1204
|
-
text,
|
|
1205
|
-
...extraFields
|
|
1206
|
-
}: Partial<StreamMessage<StreamChatGenerics>>) => {
|
|
1207
|
-
// Exclude following properties from message.user within message preview,
|
|
1208
|
-
// since they could be long arrays and have no meaning as sender of message.
|
|
1209
|
-
// Storing such large value within user's table may cause sqlite queries to crash.
|
|
1210
|
-
// @ts-ignore
|
|
1211
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1212
|
-
const { channel_mutes, devices, mutes, ...messageUser } = client.user;
|
|
1213
|
-
|
|
1214
|
-
const preview = {
|
|
1215
|
-
__html: text,
|
|
1234
|
+
const createMessagePreview = useStableCallback(
|
|
1235
|
+
({
|
|
1216
1236
|
attachments,
|
|
1217
|
-
|
|
1218
|
-
html: text,
|
|
1219
|
-
id: `${client.userID}-${generateRandomId()}`,
|
|
1220
|
-
mentioned_users:
|
|
1221
|
-
mentioned_users?.map((userId) => ({
|
|
1222
|
-
id: userId,
|
|
1223
|
-
})) || [],
|
|
1237
|
+
mentioned_users,
|
|
1224
1238
|
parent_id,
|
|
1225
1239
|
poll,
|
|
1226
1240
|
poll_id,
|
|
1227
|
-
reactions: [],
|
|
1228
|
-
status: MessageStatusTypes.SENDING,
|
|
1229
1241
|
text,
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1242
|
+
...extraFields
|
|
1243
|
+
}: Partial<StreamMessage<StreamChatGenerics>>) => {
|
|
1244
|
+
// Exclude following properties from message.user within message preview,
|
|
1245
|
+
// since they could be long arrays and have no meaning as sender of message.
|
|
1246
|
+
// Storing such large value within user's table may cause sqlite queries to crash.
|
|
1247
|
+
// @ts-ignore
|
|
1248
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1249
|
+
const { channel_mutes, devices, mutes, ...messageUser } = client.user;
|
|
1250
|
+
|
|
1251
|
+
const preview = {
|
|
1252
|
+
__html: text,
|
|
1253
|
+
attachments,
|
|
1254
|
+
created_at: new Date(),
|
|
1255
|
+
html: text,
|
|
1256
|
+
id: `${client.userID}-${generateRandomId()}`,
|
|
1257
|
+
mentioned_users:
|
|
1258
|
+
mentioned_users?.map((userId) => ({
|
|
1259
|
+
id: userId,
|
|
1260
|
+
})) || [],
|
|
1261
|
+
parent_id,
|
|
1262
|
+
poll,
|
|
1263
|
+
poll_id,
|
|
1264
|
+
reactions: [],
|
|
1265
|
+
status: MessageStatusTypes.SENDING,
|
|
1266
|
+
text,
|
|
1267
|
+
type: 'regular',
|
|
1268
|
+
user: {
|
|
1269
|
+
...messageUser,
|
|
1270
|
+
id: client.userID,
|
|
1271
|
+
},
|
|
1272
|
+
...extraFields,
|
|
1273
|
+
} as unknown as MessageResponse<StreamChatGenerics>;
|
|
1247
1274
|
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1275
|
+
/**
|
|
1276
|
+
* This is added to the message for local rendering prior to the message
|
|
1277
|
+
* being returned from the backend, it is removed when the message is sent
|
|
1278
|
+
* as quoted_message is a reserved field.
|
|
1279
|
+
*/
|
|
1280
|
+
if (preview.quoted_message_id) {
|
|
1281
|
+
const quotedMessage = channelMessagesState.messages?.find(
|
|
1282
|
+
(message) => message.id === preview.quoted_message_id,
|
|
1283
|
+
);
|
|
1253
1284
|
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
const file = attachment.originalFile;
|
|
1261
|
-
// check if image_url is not a remote url
|
|
1262
|
-
if (
|
|
1263
|
-
attachment.type === FileTypes.Image &&
|
|
1264
|
-
image?.uri &&
|
|
1265
|
-
attachment.image_url &&
|
|
1266
|
-
isLocalUrl(attachment.image_url)
|
|
1267
|
-
) {
|
|
1268
|
-
const filename = image.name ?? getFileNameFromPath(image.uri);
|
|
1269
|
-
// if any upload is in progress, cancel it
|
|
1270
|
-
const controller = uploadAbortControllerRef.current.get(filename);
|
|
1271
|
-
if (controller) {
|
|
1272
|
-
controller.abort();
|
|
1273
|
-
uploadAbortControllerRef.current.delete(filename);
|
|
1274
|
-
}
|
|
1275
|
-
const compressedUri = await compressedImageURI(image, compressImageQuality);
|
|
1276
|
-
const contentType = lookup(filename) || 'multipart/form-data';
|
|
1285
|
+
preview.quoted_message =
|
|
1286
|
+
quotedMessage as MessageResponse<StreamChatGenerics>['quoted_message'];
|
|
1287
|
+
}
|
|
1288
|
+
return preview;
|
|
1289
|
+
},
|
|
1290
|
+
);
|
|
1277
1291
|
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1292
|
+
const uploadPendingAttachments = useStableCallback(
|
|
1293
|
+
async (message: MessageResponse<StreamChatGenerics>) => {
|
|
1294
|
+
const updatedMessage = { ...message };
|
|
1295
|
+
if (updatedMessage.attachments?.length) {
|
|
1296
|
+
for (let i = 0; i < updatedMessage.attachments?.length; i++) {
|
|
1297
|
+
const attachment = updatedMessage.attachments[i];
|
|
1298
|
+
const image = attachment.originalImage;
|
|
1299
|
+
const file = attachment.originalFile;
|
|
1300
|
+
// check if image_url is not a remote url
|
|
1301
|
+
if (
|
|
1302
|
+
attachment.type === FileTypes.Image &&
|
|
1303
|
+
image?.uri &&
|
|
1304
|
+
attachment.image_url &&
|
|
1305
|
+
isLocalUrl(attachment.image_url)
|
|
1306
|
+
) {
|
|
1307
|
+
const filename = image.name ?? getFileNameFromPath(image.uri);
|
|
1308
|
+
// if any upload is in progress, cancel it
|
|
1309
|
+
const controller = uploadAbortControllerRef.current.get(filename);
|
|
1310
|
+
if (controller) {
|
|
1311
|
+
controller.abort();
|
|
1312
|
+
uploadAbortControllerRef.current.delete(filename);
|
|
1313
|
+
}
|
|
1314
|
+
const compressedUri = await compressedImageURI(image, compressImageQuality);
|
|
1315
|
+
const contentType = lookup(filename) || 'multipart/form-data';
|
|
1281
1316
|
|
|
1282
|
-
|
|
1283
|
-
|
|
1317
|
+
const uploadResponse = doImageUploadRequest
|
|
1318
|
+
? await doImageUploadRequest(image, channel)
|
|
1319
|
+
: await channel.sendImage(compressedUri, filename, contentType);
|
|
1284
1320
|
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
});
|
|
1288
|
-
}
|
|
1321
|
+
attachment.image_url = uploadResponse.file;
|
|
1322
|
+
delete attachment.originalFile;
|
|
1289
1323
|
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
attachment.type === FileTypes.VoiceRecording ||
|
|
1294
|
-
attachment.type === FileTypes.Video) &&
|
|
1295
|
-
attachment.asset_url &&
|
|
1296
|
-
isLocalUrl(attachment.asset_url) &&
|
|
1297
|
-
file?.uri
|
|
1298
|
-
) {
|
|
1299
|
-
// if any upload is in progress, cancel it
|
|
1300
|
-
const controller = uploadAbortControllerRef.current.get(file.name);
|
|
1301
|
-
if (controller) {
|
|
1302
|
-
controller.abort();
|
|
1303
|
-
uploadAbortControllerRef.current.delete(file.name);
|
|
1304
|
-
}
|
|
1305
|
-
const response = doDocUploadRequest
|
|
1306
|
-
? await doDocUploadRequest(file, channel)
|
|
1307
|
-
: await channel.sendFile(file.uri, file.name, file.mimeType);
|
|
1308
|
-
attachment.asset_url = response.file;
|
|
1309
|
-
if (response.thumb_url) {
|
|
1310
|
-
attachment.thumb_url = response.thumb_url;
|
|
1324
|
+
await dbApi.updateMessage({
|
|
1325
|
+
message: { ...updatedMessage, cid: channel.cid },
|
|
1326
|
+
});
|
|
1311
1327
|
}
|
|
1312
1328
|
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1329
|
+
if (
|
|
1330
|
+
(attachment.type === FileTypes.File ||
|
|
1331
|
+
attachment.type === FileTypes.Audio ||
|
|
1332
|
+
attachment.type === FileTypes.VoiceRecording ||
|
|
1333
|
+
attachment.type === FileTypes.Video) &&
|
|
1334
|
+
attachment.asset_url &&
|
|
1335
|
+
isLocalUrl(attachment.asset_url) &&
|
|
1336
|
+
file?.uri
|
|
1337
|
+
) {
|
|
1338
|
+
// if any upload is in progress, cancel it
|
|
1339
|
+
const controller = uploadAbortControllerRef.current.get(file.name);
|
|
1340
|
+
if (controller) {
|
|
1341
|
+
controller.abort();
|
|
1342
|
+
uploadAbortControllerRef.current.delete(file.name);
|
|
1343
|
+
}
|
|
1344
|
+
const response = doDocUploadRequest
|
|
1345
|
+
? await doDocUploadRequest(file, channel)
|
|
1346
|
+
: await channel.sendFile(file.uri, file.name, file.mimeType);
|
|
1347
|
+
attachment.asset_url = response.file;
|
|
1348
|
+
if (response.thumb_url) {
|
|
1349
|
+
attachment.thumb_url = response.thumb_url;
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
delete attachment.originalFile;
|
|
1353
|
+
await dbApi.updateMessage({
|
|
1354
|
+
message: { ...updatedMessage, cid: channel.cid },
|
|
1355
|
+
});
|
|
1356
|
+
}
|
|
1317
1357
|
}
|
|
1318
1358
|
}
|
|
1319
|
-
}
|
|
1320
|
-
|
|
1321
|
-
return updatedMessage;
|
|
1322
|
-
};
|
|
1323
1359
|
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
) => {
|
|
1328
|
-
try {
|
|
1329
|
-
const updatedMessage = await uploadPendingAttachments(message);
|
|
1330
|
-
const extraFields = omit(updatedMessage, [
|
|
1331
|
-
'__html',
|
|
1332
|
-
'attachments',
|
|
1333
|
-
'created_at',
|
|
1334
|
-
'deleted_at',
|
|
1335
|
-
'html',
|
|
1336
|
-
'id',
|
|
1337
|
-
'latest_reactions',
|
|
1338
|
-
'mentioned_users',
|
|
1339
|
-
'own_reactions',
|
|
1340
|
-
'parent_id',
|
|
1341
|
-
'quoted_message',
|
|
1342
|
-
'reaction_counts',
|
|
1343
|
-
'reaction_groups',
|
|
1344
|
-
'reactions',
|
|
1345
|
-
'status',
|
|
1346
|
-
'text',
|
|
1347
|
-
'type',
|
|
1348
|
-
'updated_at',
|
|
1349
|
-
'user',
|
|
1350
|
-
]);
|
|
1351
|
-
const { attachments, id, mentioned_users, parent_id, text } = updatedMessage;
|
|
1352
|
-
if (!channel.id) {
|
|
1353
|
-
return;
|
|
1354
|
-
}
|
|
1360
|
+
return updatedMessage;
|
|
1361
|
+
},
|
|
1362
|
+
);
|
|
1355
1363
|
|
|
1356
|
-
|
|
1364
|
+
const sendMessageRequest = useStableCallback(
|
|
1365
|
+
async (message: MessageResponse<StreamChatGenerics>, retrying?: boolean) => {
|
|
1366
|
+
try {
|
|
1367
|
+
const updatedMessage = await uploadPendingAttachments(message);
|
|
1368
|
+
const extraFields = omit(updatedMessage, [
|
|
1369
|
+
'__html',
|
|
1370
|
+
'attachments',
|
|
1371
|
+
'created_at',
|
|
1372
|
+
'deleted_at',
|
|
1373
|
+
'html',
|
|
1374
|
+
'id',
|
|
1375
|
+
'latest_reactions',
|
|
1376
|
+
'mentioned_users',
|
|
1377
|
+
'own_reactions',
|
|
1378
|
+
'parent_id',
|
|
1379
|
+
'quoted_message',
|
|
1380
|
+
'reaction_counts',
|
|
1381
|
+
'reaction_groups',
|
|
1382
|
+
'reactions',
|
|
1383
|
+
'status',
|
|
1384
|
+
'text',
|
|
1385
|
+
'type',
|
|
1386
|
+
'updated_at',
|
|
1387
|
+
'user',
|
|
1388
|
+
]);
|
|
1389
|
+
const { attachments, id, mentioned_users, parent_id, text } = updatedMessage;
|
|
1390
|
+
if (!channel.id) {
|
|
1391
|
+
return;
|
|
1392
|
+
}
|
|
1357
1393
|
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1394
|
+
const mentionedUserIds = mentioned_users?.map((user) => user.id) || [];
|
|
1395
|
+
|
|
1396
|
+
const messageData = {
|
|
1397
|
+
attachments,
|
|
1398
|
+
id,
|
|
1399
|
+
mentioned_users: mentionedUserIds,
|
|
1400
|
+
parent_id,
|
|
1401
|
+
text: patchMessageTextCommand(text ?? '', mentionedUserIds),
|
|
1402
|
+
...extraFields,
|
|
1403
|
+
} as StreamMessage<StreamChatGenerics>;
|
|
1404
|
+
|
|
1405
|
+
let messageResponse = {} as SendMessageAPIResponse<StreamChatGenerics>;
|
|
1406
|
+
if (doSendMessageRequest) {
|
|
1407
|
+
messageResponse = await doSendMessageRequest(channel?.cid || '', messageData);
|
|
1408
|
+
} else if (channel) {
|
|
1409
|
+
messageResponse = await channel.sendMessage(messageData);
|
|
1410
|
+
}
|
|
1366
1411
|
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
messageResponse = await doSendMessageRequest(channel?.cid || '', messageData);
|
|
1370
|
-
} else if (channel) {
|
|
1371
|
-
messageResponse = await channel.sendMessage(messageData);
|
|
1372
|
-
}
|
|
1412
|
+
if (messageResponse.message) {
|
|
1413
|
+
messageResponse.message.status = MessageStatusTypes.RECEIVED;
|
|
1373
1414
|
|
|
1374
|
-
|
|
1375
|
-
|
|
1415
|
+
if (enableOfflineSupport) {
|
|
1416
|
+
await dbApi.updateMessage({
|
|
1417
|
+
message: { ...messageResponse.message, cid: channel.cid },
|
|
1418
|
+
});
|
|
1419
|
+
}
|
|
1420
|
+
if (retrying) {
|
|
1421
|
+
replaceMessage(message, messageResponse.message);
|
|
1422
|
+
} else {
|
|
1423
|
+
updateMessage(messageResponse.message, {}, true);
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
} catch (err) {
|
|
1427
|
+
console.log(err);
|
|
1428
|
+
message.status = MessageStatusTypes.FAILED;
|
|
1429
|
+
const updatedMessage = { ...message, cid: channel.cid };
|
|
1430
|
+
updateMessage(updatedMessage);
|
|
1431
|
+
threadInstance?.upsertReplyLocally?.({ message: updatedMessage });
|
|
1432
|
+
optimisticallyUpdatedNewMessages.delete(message.id);
|
|
1376
1433
|
|
|
1377
1434
|
if (enableOfflineSupport) {
|
|
1378
1435
|
await dbApi.updateMessage({
|
|
1379
|
-
message: { ...
|
|
1436
|
+
message: { ...message, cid: channel.cid },
|
|
1380
1437
|
});
|
|
1381
1438
|
}
|
|
1382
|
-
if (retrying) {
|
|
1383
|
-
replaceMessage(message, messageResponse.message);
|
|
1384
|
-
} else {
|
|
1385
|
-
updateMessage(messageResponse.message);
|
|
1386
|
-
}
|
|
1387
1439
|
}
|
|
1388
|
-
}
|
|
1389
|
-
|
|
1390
|
-
message.status = MessageStatusTypes.FAILED;
|
|
1391
|
-
const updatedMessage = { ...message, cid: channel.cid };
|
|
1392
|
-
updateMessage(updatedMessage);
|
|
1393
|
-
threadInstance?.upsertReplyLocally?.({ message: updatedMessage });
|
|
1440
|
+
},
|
|
1441
|
+
);
|
|
1394
1442
|
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1443
|
+
const sendMessage: InputMessageInputContextValue<StreamChatGenerics>['sendMessage'] =
|
|
1444
|
+
useStableCallback(async (message) => {
|
|
1445
|
+
if (channel?.state?.filterErrorMessages) {
|
|
1446
|
+
channel.state.filterErrorMessages();
|
|
1399
1447
|
}
|
|
1400
|
-
}
|
|
1401
|
-
};
|
|
1402
1448
|
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
channel.state.filterErrorMessages();
|
|
1408
|
-
}
|
|
1449
|
+
const messagePreview = createMessagePreview({
|
|
1450
|
+
...message,
|
|
1451
|
+
attachments: message.attachments || [],
|
|
1452
|
+
});
|
|
1409
1453
|
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1454
|
+
updateMessage(messagePreview, {
|
|
1455
|
+
commands: [],
|
|
1456
|
+
messageInput: '',
|
|
1457
|
+
});
|
|
1458
|
+
threadInstance?.upsertReplyLocally?.({ message: messagePreview });
|
|
1459
|
+
optimisticallyUpdatedNewMessages.add(messagePreview.id);
|
|
1414
1460
|
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1461
|
+
if (enableOfflineSupport) {
|
|
1462
|
+
// While sending a message, we add the message to local db with failed status, so that
|
|
1463
|
+
// if app gets closed before message gets sent and next time user opens the app
|
|
1464
|
+
// then user can see that message in failed state and can retry.
|
|
1465
|
+
// If succesfull, it will be updated with received status.
|
|
1466
|
+
await dbApi.upsertMessages({
|
|
1467
|
+
messages: [{ ...messagePreview, cid: channel.cid, status: MessageStatusTypes.FAILED }],
|
|
1468
|
+
});
|
|
1469
|
+
}
|
|
1420
1470
|
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
// if app gets closed before message gets sent and next time user opens the app
|
|
1424
|
-
// then user can see that message in failed state and can retry.
|
|
1425
|
-
// If succesfull, it will be updated with received status.
|
|
1426
|
-
await dbApi.upsertMessages({
|
|
1427
|
-
messages: [{ ...messagePreview, cid: channel.cid, status: MessageStatusTypes.FAILED }],
|
|
1428
|
-
});
|
|
1429
|
-
}
|
|
1471
|
+
await sendMessageRequest(messagePreview);
|
|
1472
|
+
});
|
|
1430
1473
|
|
|
1431
|
-
|
|
1432
|
-
|
|
1474
|
+
const retrySendMessage: MessagesContextValue<StreamChatGenerics>['retrySendMessage'] =
|
|
1475
|
+
useStableCallback(async (message) => {
|
|
1476
|
+
const statusPendingMessage = {
|
|
1477
|
+
...message,
|
|
1478
|
+
status: MessageStatusTypes.SENDING,
|
|
1479
|
+
};
|
|
1433
1480
|
|
|
1434
|
-
|
|
1435
|
-
message,
|
|
1436
|
-
) => {
|
|
1437
|
-
const statusPendingMessage = {
|
|
1438
|
-
...message,
|
|
1439
|
-
status: MessageStatusTypes.SENDING,
|
|
1440
|
-
};
|
|
1481
|
+
const messageWithoutReservedFields = removeReservedFields(statusPendingMessage);
|
|
1441
1482
|
|
|
1442
|
-
|
|
1483
|
+
// For bounced messages, we don't need to update the message, instead always send a new message.
|
|
1484
|
+
if (!isBouncedMessage(message)) {
|
|
1485
|
+
updateMessage(messageWithoutReservedFields as MessageResponse<StreamChatGenerics>);
|
|
1486
|
+
}
|
|
1443
1487
|
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1488
|
+
await sendMessageRequest(
|
|
1489
|
+
messageWithoutReservedFields as MessageResponse<StreamChatGenerics>,
|
|
1490
|
+
true,
|
|
1491
|
+
);
|
|
1492
|
+
});
|
|
1448
1493
|
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1494
|
+
const editMessage: InputMessageInputContextValue<StreamChatGenerics>['editMessage'] =
|
|
1495
|
+
useStableCallback((updatedMessage) =>
|
|
1496
|
+
doUpdateMessageRequest
|
|
1497
|
+
? doUpdateMessageRequest(channel?.cid || '', updatedMessage)
|
|
1498
|
+
: client.updateMessage(updatedMessage),
|
|
1452
1499
|
);
|
|
1453
|
-
};
|
|
1454
1500
|
|
|
1455
|
-
const
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
: client.updateMessage(updatedMessage);
|
|
1461
|
-
|
|
1462
|
-
const setEditingState: MessagesContextValue<StreamChatGenerics>['setEditingState'] = (
|
|
1463
|
-
message,
|
|
1464
|
-
) => {
|
|
1465
|
-
clearQuotedMessageState();
|
|
1466
|
-
setEditing(message);
|
|
1467
|
-
};
|
|
1501
|
+
const setEditingState: MessagesContextValue<StreamChatGenerics>['setEditingState'] =
|
|
1502
|
+
useStableCallback((message) => {
|
|
1503
|
+
clearQuotedMessageState();
|
|
1504
|
+
setEditing(message);
|
|
1505
|
+
});
|
|
1468
1506
|
|
|
1469
|
-
const setQuotedMessageState: MessagesContextValue<StreamChatGenerics>['setQuotedMessageState'] =
|
|
1470
|
-
messageOrBoolean
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
};
|
|
1507
|
+
const setQuotedMessageState: MessagesContextValue<StreamChatGenerics>['setQuotedMessageState'] =
|
|
1508
|
+
useStableCallback((messageOrBoolean) => {
|
|
1509
|
+
setQuotedMessage(messageOrBoolean);
|
|
1510
|
+
});
|
|
1474
1511
|
|
|
1475
1512
|
const clearEditingState: InputMessageInputContextValue<StreamChatGenerics>['clearEditingState'] =
|
|
1476
|
-
() => setEditing(undefined);
|
|
1513
|
+
useStableCallback(() => setEditing(undefined));
|
|
1477
1514
|
|
|
1478
1515
|
const clearQuotedMessageState: InputMessageInputContextValue<StreamChatGenerics>['clearQuotedMessageState'] =
|
|
1479
|
-
() => setQuotedMessage(undefined);
|
|
1516
|
+
useStableCallback(() => setQuotedMessage(undefined));
|
|
1480
1517
|
|
|
1481
1518
|
/**
|
|
1482
1519
|
* Removes the message from local state
|
|
1483
1520
|
*/
|
|
1484
|
-
const removeMessage: MessagesContextValue<StreamChatGenerics>['removeMessage'] =
|
|
1485
|
-
message
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
copyMessagesStateFromChannel(channel);
|
|
1521
|
+
const removeMessage: MessagesContextValue<StreamChatGenerics>['removeMessage'] =
|
|
1522
|
+
useStableCallback(async (message) => {
|
|
1523
|
+
if (channel) {
|
|
1524
|
+
channel.state.removeMessage(message);
|
|
1525
|
+
copyMessagesStateFromChannel(channel);
|
|
1490
1526
|
|
|
1491
|
-
|
|
1492
|
-
|
|
1527
|
+
if (thread) {
|
|
1528
|
+
setThreadMessages(channel.state.threads[thread.id] || []);
|
|
1529
|
+
}
|
|
1493
1530
|
}
|
|
1494
|
-
}
|
|
1495
1531
|
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1532
|
+
if (enableOfflineSupport) {
|
|
1533
|
+
await dbApi.deleteMessage({
|
|
1534
|
+
id: message.id,
|
|
1535
|
+
});
|
|
1536
|
+
}
|
|
1537
|
+
});
|
|
1502
1538
|
|
|
1503
|
-
const sendReaction = async (type: string, messageId: string) => {
|
|
1539
|
+
const sendReaction = useStableCallback(async (type: string, messageId: string) => {
|
|
1504
1540
|
if (!channel?.id || !client.user) {
|
|
1505
1541
|
throw new Error('Channel has not been initialized');
|
|
1506
1542
|
}
|
|
@@ -1541,90 +1577,87 @@ const ChannelWithContext = <
|
|
|
1541
1577
|
if (sendReactionResponse?.message) {
|
|
1542
1578
|
threadInstance?.upsertReplyLocally?.({ message: sendReactionResponse.message });
|
|
1543
1579
|
}
|
|
1544
|
-
};
|
|
1580
|
+
});
|
|
1545
1581
|
|
|
1546
|
-
const deleteMessage: MessagesContextValue<StreamChatGenerics>['deleteMessage'] =
|
|
1547
|
-
message
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1582
|
+
const deleteMessage: MessagesContextValue<StreamChatGenerics>['deleteMessage'] =
|
|
1583
|
+
useStableCallback(async (message) => {
|
|
1584
|
+
if (!channel.id) {
|
|
1585
|
+
throw new Error('Channel has not been initialized yet');
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
if (!enableOfflineSupport) {
|
|
1589
|
+
if (message.status === MessageStatusTypes.FAILED) {
|
|
1590
|
+
await removeMessage(message);
|
|
1591
|
+
return;
|
|
1592
|
+
}
|
|
1593
|
+
await client.deleteMessage(message.id);
|
|
1594
|
+
return;
|
|
1595
|
+
}
|
|
1552
1596
|
|
|
1553
|
-
if (!enableOfflineSupport) {
|
|
1554
1597
|
if (message.status === MessageStatusTypes.FAILED) {
|
|
1598
|
+
await DBSyncManager.dropPendingTasks({ messageId: message.id });
|
|
1555
1599
|
await removeMessage(message);
|
|
1600
|
+
} else {
|
|
1601
|
+
const updatedMessage = {
|
|
1602
|
+
...message,
|
|
1603
|
+
cid: channel.cid,
|
|
1604
|
+
deleted_at: new Date().toISOString(),
|
|
1605
|
+
type: 'deleted',
|
|
1606
|
+
};
|
|
1607
|
+
updateMessage(updatedMessage);
|
|
1608
|
+
|
|
1609
|
+
threadInstance?.upsertReplyLocally({ message: updatedMessage });
|
|
1610
|
+
|
|
1611
|
+
const data = await DBSyncManager.queueTask<StreamChatGenerics>({
|
|
1612
|
+
client,
|
|
1613
|
+
task: {
|
|
1614
|
+
channelId: channel.id,
|
|
1615
|
+
channelType: channel.type,
|
|
1616
|
+
messageId: message.id,
|
|
1617
|
+
payload: [message.id],
|
|
1618
|
+
type: 'delete-message',
|
|
1619
|
+
},
|
|
1620
|
+
});
|
|
1621
|
+
|
|
1622
|
+
if (data?.message) {
|
|
1623
|
+
updateMessage({ ...data.message });
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
});
|
|
1627
|
+
|
|
1628
|
+
const deleteReaction: MessagesContextValue<StreamChatGenerics>['deleteReaction'] =
|
|
1629
|
+
useStableCallback(async (type: string, messageId: string) => {
|
|
1630
|
+
if (!channel?.id || !client.user) {
|
|
1631
|
+
throw new Error('Channel has not been initialized');
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
const payload: Parameters<ChannelClass['deleteReaction']> = [messageId, type];
|
|
1635
|
+
|
|
1636
|
+
if (!enableOfflineSupport) {
|
|
1637
|
+
await channel.deleteReaction(...payload);
|
|
1556
1638
|
return;
|
|
1557
1639
|
}
|
|
1558
|
-
await client.deleteMessage(message.id);
|
|
1559
|
-
return;
|
|
1560
|
-
}
|
|
1561
1640
|
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
cid: channel.cid,
|
|
1569
|
-
deleted_at: new Date().toISOString(),
|
|
1570
|
-
type: 'deleted',
|
|
1571
|
-
};
|
|
1572
|
-
updateMessage(updatedMessage);
|
|
1641
|
+
removeReactionFromLocalState({
|
|
1642
|
+
channel,
|
|
1643
|
+
messageId,
|
|
1644
|
+
reactionType: type,
|
|
1645
|
+
user: client.user,
|
|
1646
|
+
});
|
|
1573
1647
|
|
|
1574
|
-
|
|
1648
|
+
copyMessagesStateFromChannel(channel);
|
|
1575
1649
|
|
|
1576
|
-
|
|
1650
|
+
await DBSyncManager.queueTask<StreamChatGenerics>({
|
|
1577
1651
|
client,
|
|
1578
1652
|
task: {
|
|
1579
1653
|
channelId: channel.id,
|
|
1580
1654
|
channelType: channel.type,
|
|
1581
|
-
messageId
|
|
1582
|
-
payload
|
|
1583
|
-
type: 'delete-
|
|
1655
|
+
messageId,
|
|
1656
|
+
payload,
|
|
1657
|
+
type: 'delete-reaction',
|
|
1584
1658
|
},
|
|
1585
1659
|
});
|
|
1586
|
-
|
|
1587
|
-
if (data?.message) {
|
|
1588
|
-
updateMessage({ ...data.message });
|
|
1589
|
-
}
|
|
1590
|
-
}
|
|
1591
|
-
};
|
|
1592
|
-
|
|
1593
|
-
const deleteReaction: MessagesContextValue<StreamChatGenerics>['deleteReaction'] = async (
|
|
1594
|
-
type: string,
|
|
1595
|
-
messageId: string,
|
|
1596
|
-
) => {
|
|
1597
|
-
if (!channel?.id || !client.user) {
|
|
1598
|
-
throw new Error('Channel has not been initialized');
|
|
1599
|
-
}
|
|
1600
|
-
|
|
1601
|
-
const payload: Parameters<ChannelClass['deleteReaction']> = [messageId, type];
|
|
1602
|
-
|
|
1603
|
-
if (!enableOfflineSupport) {
|
|
1604
|
-
await channel.deleteReaction(...payload);
|
|
1605
|
-
return;
|
|
1606
|
-
}
|
|
1607
|
-
|
|
1608
|
-
removeReactionFromLocalState({
|
|
1609
|
-
channel,
|
|
1610
|
-
messageId,
|
|
1611
|
-
reactionType: type,
|
|
1612
|
-
user: client.user,
|
|
1613
|
-
});
|
|
1614
|
-
|
|
1615
|
-
copyMessagesStateFromChannel(channel);
|
|
1616
|
-
|
|
1617
|
-
await DBSyncManager.queueTask<StreamChatGenerics>({
|
|
1618
|
-
client,
|
|
1619
|
-
task: {
|
|
1620
|
-
channelId: channel.id,
|
|
1621
|
-
channelType: channel.type,
|
|
1622
|
-
messageId,
|
|
1623
|
-
payload,
|
|
1624
|
-
type: 'delete-reaction',
|
|
1625
|
-
},
|
|
1626
1660
|
});
|
|
1627
|
-
};
|
|
1628
1661
|
|
|
1629
1662
|
/**
|
|
1630
1663
|
* THREAD METHODS
|
|
@@ -1666,46 +1699,47 @@ const ChannelWithContext = <
|
|
|
1666
1699
|
),
|
|
1667
1700
|
).current;
|
|
1668
1701
|
|
|
1669
|
-
const loadMoreThread: ThreadContextValue<StreamChatGenerics>['loadMoreThread'] =
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1702
|
+
const loadMoreThread: ThreadContextValue<StreamChatGenerics>['loadMoreThread'] =
|
|
1703
|
+
useStableCallback(async () => {
|
|
1704
|
+
if (threadLoadingMore || !thread?.id) {
|
|
1705
|
+
return;
|
|
1706
|
+
}
|
|
1707
|
+
setThreadLoadingMore(true);
|
|
1674
1708
|
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1709
|
+
try {
|
|
1710
|
+
if (channel) {
|
|
1711
|
+
const parentID = thread.id;
|
|
1712
|
+
|
|
1713
|
+
/**
|
|
1714
|
+
* In the channel is re-initializing, then threads may get wiped out during the process
|
|
1715
|
+
* (check `addMessagesSorted` method on channel.state). In those cases, we still want to
|
|
1716
|
+
* preserve the messages on active thread, so lets simply copy messages from UI state to
|
|
1717
|
+
* `channel.state`.
|
|
1718
|
+
*/
|
|
1719
|
+
channel.state.threads[parentID] = threadMessages;
|
|
1720
|
+
const oldestMessageID = threadMessages?.[0]?.id;
|
|
1721
|
+
|
|
1722
|
+
const limit = 50;
|
|
1723
|
+
const queryResponse = await channel.getReplies(parentID, {
|
|
1724
|
+
id_lt: oldestMessageID,
|
|
1725
|
+
limit,
|
|
1726
|
+
});
|
|
1693
1727
|
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1728
|
+
const updatedHasMore = queryResponse.messages.length === limit;
|
|
1729
|
+
const updatedThreadMessages = channel.state.threads[parentID] || [];
|
|
1730
|
+
loadMoreThreadFinished(updatedHasMore, updatedThreadMessages);
|
|
1731
|
+
}
|
|
1732
|
+
} catch (err) {
|
|
1733
|
+
console.warn('Message pagination request failed with error', err);
|
|
1734
|
+
if (err instanceof Error) {
|
|
1735
|
+
setError(err);
|
|
1736
|
+
} else {
|
|
1737
|
+
setError(true);
|
|
1738
|
+
}
|
|
1739
|
+
setThreadLoadingMore(false);
|
|
1740
|
+
throw err;
|
|
1704
1741
|
}
|
|
1705
|
-
|
|
1706
|
-
throw err;
|
|
1707
|
-
}
|
|
1708
|
-
};
|
|
1742
|
+
});
|
|
1709
1743
|
|
|
1710
1744
|
const ownCapabilitiesContext = useCreateOwnCapabilitiesContext({
|
|
1711
1745
|
channel,
|
|
@@ -1757,14 +1791,11 @@ const ChannelWithContext = <
|
|
|
1757
1791
|
// but it is definitely not trivial, especially considering it depends on other inline functions that
|
|
1758
1792
|
// are not wrapped in a useCallback() themselves hence creating a huge cascading change. Can be removed
|
|
1759
1793
|
// once our memoization issues are fixed in most places in the app or we move to a reactive state store.
|
|
1760
|
-
const sendMessageRef =
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
>((...args) => {
|
|
1766
|
-
return sendMessageRef.current(...args);
|
|
1767
|
-
}, []);
|
|
1794
|
+
// const sendMessageRef = useRef<InputMessageInputContextValue['sendMessage']>(sendMessage);
|
|
1795
|
+
// sendMessageRef.current = sendMessage;
|
|
1796
|
+
// const sendMessageStable = useCallback<InputMessageInputContextValue['sendMessage']>((...args) => {
|
|
1797
|
+
// return sendMessageRef.current(...args);
|
|
1798
|
+
// }, []);
|
|
1768
1799
|
|
|
1769
1800
|
const inputMessageInputContext = useCreateInputMessageInputContext<StreamChatGenerics>({
|
|
1770
1801
|
additionalTextInputProps,
|
|
@@ -1818,7 +1849,7 @@ const ChannelWithContext = <
|
|
|
1818
1849
|
quotedMessage,
|
|
1819
1850
|
SendButton,
|
|
1820
1851
|
sendImageAsync,
|
|
1821
|
-
sendMessage
|
|
1852
|
+
sendMessage,
|
|
1822
1853
|
SendMessageDisallowedIndicator,
|
|
1823
1854
|
setInputRef,
|
|
1824
1855
|
setQuotedMessageState,
|