@@ -106,7 +106,6 @@ import {
106106 ChannelUnreadStateStore ,
107107 ChannelUnreadStateStoreType ,
108108} from '../../state-store/channel-unread-state' ;
109- import * as dbApi from '../../store/apis' ;
110109import { FileTypes } from '../../types/types' ;
111110import { addReactionToLocalState } from '../../utils/addReactionToLocalState' ;
112111import { compressedImageURI } from '../../utils/compressImage' ;
@@ -437,6 +436,20 @@ export type ChannelPropsWithContext = Pick<ChannelContextValue, 'channel'> &
437436 messageData : StreamMessage ,
438437 options ?: SendMessageOptions ,
439438 ) => Promise < SendMessageAPIResponse > ;
439+
440+ /**
441+ * A method invoked just after the first optimistic update of a new message,
442+ * but before any other HTTP requests happen. Can be used to do extra work
443+ * (such as creating a channel, or editing a message) before the local message
444+ * is sent.
445+ * @param channelId
446+ * @param messageData Message object
447+ */
448+ preSendMessageRequest ?: ( options : {
449+ localMessage : LocalMessage ;
450+ message : StreamMessage ;
451+ options ?: SendMessageOptions ;
452+ } ) => Promise < SendMessageAPIResponse > ;
440453 /**
441454 * Overrides the Stream default update message request (Advanced usage only)
442455 * @param channelId
@@ -496,10 +509,24 @@ export type ChannelPropsWithContext = Pick<ChannelContextValue, 'channel'> &
496509 * Tells if channel is rendering a thread list
497510 */
498511 threadList ?: boolean ;
512+ /**
513+ * A boolean signifying whether the Channel component should run channel.watch()
514+ * whenever it mounts up a new channel. If set to `false`, it is the integrator's
515+ * responsibility to run channel.watch() if they wish to receive WebSocket events
516+ * for that channel.
517+ *
518+ * Can be particularly useful whenever we are viewing channels in a read-only mode
519+ * or perhaps want them in an ephemeral state (i.e not created until the first message
520+ * is sent).
521+ */
522+ initializeOnMount ?: boolean ;
499523 } & Partial <
500524 Pick <
501525 InputMessageInputContextValue ,
502- 'openPollCreationDialog' | 'CreatePollContent' | 'StopMessageStreamingButton'
526+ | 'openPollCreationDialog'
527+ | 'CreatePollContent'
528+ | 'StopMessageStreamingButton'
529+ | 'allowSendBeforeAttachmentsUpload'
503530 >
504531 > ;
505532
@@ -571,10 +598,12 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
571598 doFileUploadRequest,
572599 doMarkReadRequest,
573600 doSendMessageRequest,
601+ preSendMessageRequest,
574602 doUpdateMessageRequest,
575603 EmptyStateIndicator = EmptyStateIndicatorDefault ,
576604 enableMessageGroupingByUser = true ,
577605 enableOfflineSupport,
606+ allowSendBeforeAttachmentsUpload = enableOfflineSupport ,
578607 enableSwipeToReply = true ,
579608 enforceUniqueReaction = false ,
580609 FileAttachment = FileAttachmentDefault ,
@@ -719,6 +748,7 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
719748 VideoThumbnail = VideoThumbnailDefault ,
720749 isOnline,
721750 maximumMessageLimit,
751+ initializeOnMount = true ,
722752 } = props ;
723753
724754 const { thread : threadProps , threadInstance } = threadFromProps ;
@@ -886,7 +916,7 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
886916 }
887917
888918 // only update channel state if the events are not the previously subscribed useEffect's subscription events
889- if ( channel && channel . initialized ) {
919+ if ( channel ) {
890920 // we skip the new message events if we've already done an optimistic update for the new message
891921 if ( event . type === 'message.new' || event . type === 'notification.message_new' ) {
892922 const messageId = event . message ?. id ?? '' ;
@@ -920,13 +950,14 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
920950 }
921951 let errored = false ;
922952
923- if ( ! channel . initialized || ! channel . state . isUpToDate ) {
953+ if ( ( ! channel . initialized || ! channel . state . isUpToDate ) && initializeOnMount ) {
924954 try {
925955 await channel ?. watch ( ) ;
926956 } catch ( err ) {
927957 console . warn ( 'Channel watch request failed with error:' , err ) ;
928958 setError ( true ) ;
929959 errored = true ;
960+ channel . offlineMode = true ;
930961 }
931962 }
932963
@@ -1083,7 +1114,7 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
10831114 } ) ;
10841115
10851116 const resyncChannel = useStableCallback ( async ( ) => {
1086- if ( ! channel || syncingChannelRef . current ) {
1117+ if ( ! channel || syncingChannelRef . current || ( ! channel . initialized && ! channel . offlineMode ) ) {
10871118 return ;
10881119 }
10891120 syncingChannelRef . current = true ;
@@ -1104,6 +1135,7 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
11041135 limit : channelMessagesState . messages . length + 30 ,
11051136 } ,
11061137 } ) ;
1138+ channel . offlineMode = false ;
11071139 }
11081140
11091141 if ( ! thread ) {
@@ -1305,9 +1337,13 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
13051337 attachment . image_url = uploadResponse . file ;
13061338 delete attachment . originalFile ;
13071339
1308- await dbApi . updateMessage ( {
1309- message : { ...updatedMessage , cid : channel . cid } ,
1310- } ) ;
1340+ client . offlineDb ?. executeQuerySafely (
1341+ ( db ) =>
1342+ db . updateMessage ( {
1343+ message : { ...updatedMessage , cid : channel . cid } ,
1344+ } ) ,
1345+ { method : 'updateMessage' } ,
1346+ ) ;
13111347 }
13121348
13131349 if ( attachment . type !== FileTypes . Image && file ?. uri ) {
@@ -1326,9 +1362,13 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
13261362 }
13271363
13281364 delete attachment . originalFile ;
1329- await dbApi . updateMessage ( {
1330- message : { ...updatedMessage , cid : channel . cid } ,
1331- } ) ;
1365+ client . offlineDb ?. executeQuerySafely (
1366+ ( db ) =>
1367+ db . updateMessage ( {
1368+ message : { ...updatedMessage , cid : channel . cid } ,
1369+ } ) ,
1370+ { method : 'updateMessage' } ,
1371+ ) ;
13321372 }
13331373 }
13341374 }
@@ -1349,7 +1389,7 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
13491389 retrying ?: boolean ;
13501390 } ) => {
13511391 let failedMessageUpdated = false ;
1352- const handleFailedMessage = async ( ) => {
1392+ const handleFailedMessage = ( ) => {
13531393 if ( ! failedMessageUpdated ) {
13541394 const updatedMessage = {
13551395 ...localMessage ,
@@ -1360,11 +1400,13 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
13601400 threadInstance ?. upsertReplyLocally ?.( { message : updatedMessage } ) ;
13611401 optimisticallyUpdatedNewMessages . delete ( localMessage . id ) ;
13621402
1363- if ( enableOfflineSupport ) {
1364- await dbApi . updateMessage ( {
1365- message : updatedMessage ,
1366- } ) ;
1367- }
1403+ client . offlineDb ?. executeQuerySafely (
1404+ ( db ) =>
1405+ db . updateMessage ( {
1406+ message : updatedMessage ,
1407+ } ) ,
1408+ { method : 'updateMessage' } ,
1409+ ) ;
13681410
13691411 failedMessageUpdated = true ;
13701412 }
@@ -1402,11 +1444,14 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
14021444 status : MessageStatusTypes . RECEIVED ,
14031445 } ;
14041446
1405- if ( enableOfflineSupport ) {
1406- await dbApi . updateMessage ( {
1407- message : { ...newMessageResponse , cid : channel . cid } ,
1408- } ) ;
1409- }
1447+ client . offlineDb ?. executeQuerySafely (
1448+ ( db ) =>
1449+ db . updateMessage ( {
1450+ message : { ...newMessageResponse , cid : channel . cid } ,
1451+ } ) ,
1452+ { method : 'updateMessage' } ,
1453+ ) ;
1454+
14101455 if ( retrying ) {
14111456 replaceMessage ( localMessage , newMessageResponse ) ;
14121457 } else {
@@ -1430,16 +1475,22 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
14301475 threadInstance ?. upsertReplyLocally ?.( { message : localMessage } ) ;
14311476 optimisticallyUpdatedNewMessages . add ( localMessage . id ) ;
14321477
1433- if ( enableOfflineSupport ) {
1434- // While sending a message, we add the message to local db with failed status, so that
1435- // if app gets closed before message gets sent and next time user opens the app
1436- // then user can see that message in failed state and can retry.
1437- // If succesfull, it will be updated with received status.
1438- await dbApi . upsertMessages ( {
1439- messages : [ { ...localMessage , cid : channel . cid , status : MessageStatusTypes . FAILED } ] ,
1440- } ) ;
1441- }
1478+ // While sending a message, we add the message to local db with failed status, so that
1479+ // if app gets closed before message gets sent and next time user opens the app
1480+ // then user can see that message in failed state and can retry.
1481+ // If succesfull, it will be updated with received status.
1482+ client . offlineDb ?. executeQuerySafely (
1483+ ( db ) =>
1484+ db . upsertMessages ( {
1485+ // @ts -ignore
1486+ messages : [ { ...localMessage , cid : channel . cid , status : MessageStatusTypes . FAILED } ] ,
1487+ } ) ,
1488+ { method : 'upsertMessages' } ,
1489+ ) ;
14421490
1491+ if ( preSendMessageRequest ) {
1492+ await preSendMessageRequest ( { localMessage, message, options } ) ;
1493+ }
14431494 await sendMessageRequest ( { localMessage, message, options } ) ;
14441495 } ,
14451496 ) ;
@@ -1762,6 +1813,7 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
17621813
17631814 const inputMessageInputContext = useCreateInputMessageInputContext ( {
17641815 additionalTextInputProps,
1816+ allowSendBeforeAttachmentsUpload,
17651817 asyncMessagesLockDistance,
17661818 asyncMessagesMinimumPressDuration,
17671819 asyncMessagesMultiSendEnabled,
0 commit comments