@@ -102,7 +102,6 @@ import {
102102 isImagePickerAvailable ,
103103 NativeHandlers ,
104104} from '../../native' ;
105- import * as dbApi from '../../store/apis' ;
106105import { ChannelUnreadState , FileTypes } from '../../types/types' ;
107106import { addReactionToLocalState } from '../../utils/addReactionToLocalState' ;
108107import { compressedImageURI } from '../../utils/compressImage' ;
@@ -433,6 +432,20 @@ export type ChannelPropsWithContext = Pick<ChannelContextValue, 'channel'> &
433432 messageData : StreamMessage ,
434433 options ?: SendMessageOptions ,
435434 ) => Promise < SendMessageAPIResponse > ;
435+
436+ /**
437+ * A method invoked just after the first optimistic update of a new message,
438+ * but before any other HTTP requests happen. Can be used to do extra work
439+ * (such as creating a channel, or editing a message) before the local message
440+ * is sent.
441+ * @param channelId
442+ * @param messageData Message object
443+ */
444+ preSendMessageRequest ?: ( options : {
445+ localMessage : LocalMessage ;
446+ message : StreamMessage ;
447+ options ?: SendMessageOptions ;
448+ } ) => Promise < SendMessageAPIResponse > ;
436449 /**
437450 * Overrides the Stream default update message request (Advanced usage only)
438451 * @param channelId
@@ -492,10 +505,24 @@ export type ChannelPropsWithContext = Pick<ChannelContextValue, 'channel'> &
492505 * Tells if channel is rendering a thread list
493506 */
494507 threadList ?: boolean ;
508+ /**
509+ * A boolean signifying whether the Channel component should run channel.watch()
510+ * whenever it mounts up a new channel. If set to `false`, it is the integrator's
511+ * responsibility to run channel.watch() if they wish to receive WebSocket events
512+ * for that channel.
513+ *
514+ * Can be particularly useful whenever we are viewing channels in a read-only mode
515+ * or perhaps want them in an ephemeral state (i.e not created until the first message
516+ * is sent).
517+ */
518+ initializeOnMount ?: boolean ;
495519 } & Partial <
496520 Pick <
497521 InputMessageInputContextValue ,
498- 'openPollCreationDialog' | 'CreatePollContent' | 'StopMessageStreamingButton'
522+ | 'openPollCreationDialog'
523+ | 'CreatePollContent'
524+ | 'StopMessageStreamingButton'
525+ | 'allowSendBeforeAttachmentsUpload'
499526 >
500527 > ;
501528
@@ -567,10 +594,12 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
567594 doFileUploadRequest,
568595 doMarkReadRequest,
569596 doSendMessageRequest,
597+ preSendMessageRequest,
570598 doUpdateMessageRequest,
571599 EmptyStateIndicator = EmptyStateIndicatorDefault ,
572600 enableMessageGroupingByUser = true ,
573601 enableOfflineSupport,
602+ allowSendBeforeAttachmentsUpload = enableOfflineSupport ,
574603 enableSwipeToReply = true ,
575604 enforceUniqueReaction = false ,
576605 FileAttachment = FileAttachmentDefault ,
@@ -715,6 +744,7 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
715744 VideoThumbnail = VideoThumbnailDefault ,
716745 isOnline,
717746 maximumMessageLimit,
747+ initializeOnMount = true ,
718748 } = props ;
719749
720750 const { thread : threadProps , threadInstance } = threadFromProps ;
@@ -881,7 +911,7 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
881911 }
882912
883913 // only update channel state if the events are not the previously subscribed useEffect's subscription events
884- if ( channel && channel . initialized ) {
914+ if ( channel ) {
885915 // we skip the new message events if we've already done an optimistic update for the new message
886916 if ( event . type === 'message.new' || event . type === 'notification.message_new' ) {
887917 const messageId = event . message ?. id ?? '' ;
@@ -915,13 +945,14 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
915945 }
916946 let errored = false ;
917947
918- if ( ! channel . initialized || ! channel . state . isUpToDate ) {
948+ if ( ( ! channel . initialized || ! channel . state . isUpToDate ) && initializeOnMount ) {
919949 try {
920950 await channel ?. watch ( ) ;
921951 } catch ( err ) {
922952 console . warn ( 'Channel watch request failed with error:' , err ) ;
923953 setError ( true ) ;
924954 errored = true ;
955+ channel . offlineMode = true ;
925956 }
926957 }
927958
@@ -1078,7 +1109,7 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
10781109 } ) ;
10791110
10801111 const resyncChannel = useStableCallback ( async ( ) => {
1081- if ( ! channel || syncingChannelRef . current ) {
1112+ if ( ! channel || syncingChannelRef . current || ( ! channel . initialized && ! channel . offlineMode ) ) {
10821113 return ;
10831114 }
10841115 syncingChannelRef . current = true ;
@@ -1099,6 +1130,7 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
10991130 limit : channelMessagesState . messages . length + 30 ,
11001131 } ,
11011132 } ) ;
1133+ channel . offlineMode = false ;
11021134 }
11031135
11041136 if ( ! thread ) {
@@ -1300,9 +1332,13 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
13001332 attachment . image_url = uploadResponse . file ;
13011333 delete attachment . originalFile ;
13021334
1303- await dbApi . updateMessage ( {
1304- message : { ...updatedMessage , cid : channel . cid } ,
1305- } ) ;
1335+ client . offlineDb ?. executeQuerySafely (
1336+ ( db ) =>
1337+ db . updateMessage ( {
1338+ message : { ...updatedMessage , cid : channel . cid } ,
1339+ } ) ,
1340+ { method : 'updateMessage' } ,
1341+ ) ;
13061342 }
13071343
13081344 if ( attachment . type !== FileTypes . Image && file ?. uri ) {
@@ -1321,9 +1357,13 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
13211357 }
13221358
13231359 delete attachment . originalFile ;
1324- await dbApi . updateMessage ( {
1325- message : { ...updatedMessage , cid : channel . cid } ,
1326- } ) ;
1360+ client . offlineDb ?. executeQuerySafely (
1361+ ( db ) =>
1362+ db . updateMessage ( {
1363+ message : { ...updatedMessage , cid : channel . cid } ,
1364+ } ) ,
1365+ { method : 'updateMessage' } ,
1366+ ) ;
13271367 }
13281368 }
13291369 }
@@ -1344,7 +1384,7 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
13441384 retrying ?: boolean ;
13451385 } ) => {
13461386 let failedMessageUpdated = false ;
1347- const handleFailedMessage = async ( ) => {
1387+ const handleFailedMessage = ( ) => {
13481388 if ( ! failedMessageUpdated ) {
13491389 const updatedMessage = {
13501390 ...localMessage ,
@@ -1355,11 +1395,13 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
13551395 threadInstance ?. upsertReplyLocally ?.( { message : updatedMessage } ) ;
13561396 optimisticallyUpdatedNewMessages . delete ( localMessage . id ) ;
13571397
1358- if ( enableOfflineSupport ) {
1359- await dbApi . updateMessage ( {
1360- message : updatedMessage ,
1361- } ) ;
1362- }
1398+ client . offlineDb ?. executeQuerySafely (
1399+ ( db ) =>
1400+ db . updateMessage ( {
1401+ message : updatedMessage ,
1402+ } ) ,
1403+ { method : 'updateMessage' } ,
1404+ ) ;
13631405
13641406 failedMessageUpdated = true ;
13651407 }
@@ -1397,11 +1439,14 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
13971439 status : MessageStatusTypes . RECEIVED ,
13981440 } ;
13991441
1400- if ( enableOfflineSupport ) {
1401- await dbApi . updateMessage ( {
1402- message : { ...newMessageResponse , cid : channel . cid } ,
1403- } ) ;
1404- }
1442+ client . offlineDb ?. executeQuerySafely (
1443+ ( db ) =>
1444+ db . updateMessage ( {
1445+ message : { ...newMessageResponse , cid : channel . cid } ,
1446+ } ) ,
1447+ { method : 'updateMessage' } ,
1448+ ) ;
1449+
14051450 if ( retrying ) {
14061451 replaceMessage ( localMessage , newMessageResponse ) ;
14071452 } else {
@@ -1425,16 +1470,22 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
14251470 threadInstance ?. upsertReplyLocally ?.( { message : localMessage } ) ;
14261471 optimisticallyUpdatedNewMessages . add ( localMessage . id ) ;
14271472
1428- if ( enableOfflineSupport ) {
1429- // While sending a message, we add the message to local db with failed status, so that
1430- // if app gets closed before message gets sent and next time user opens the app
1431- // then user can see that message in failed state and can retry.
1432- // If succesfull, it will be updated with received status.
1433- await dbApi . upsertMessages ( {
1434- messages : [ { ...localMessage , cid : channel . cid , status : MessageStatusTypes . FAILED } ] ,
1435- } ) ;
1436- }
1473+ // While sending a message, we add the message to local db with failed status, so that
1474+ // if app gets closed before message gets sent and next time user opens the app
1475+ // then user can see that message in failed state and can retry.
1476+ // If succesfull, it will be updated with received status.
1477+ client . offlineDb ?. executeQuerySafely (
1478+ ( db ) =>
1479+ db . upsertMessages ( {
1480+ // @ts -ignore
1481+ messages : [ { ...localMessage , cid : channel . cid , status : MessageStatusTypes . FAILED } ] ,
1482+ } ) ,
1483+ { method : 'upsertMessages' } ,
1484+ ) ;
14371485
1486+ if ( preSendMessageRequest ) {
1487+ await preSendMessageRequest ( { localMessage, message, options } ) ;
1488+ }
14381489 await sendMessageRequest ( { localMessage, message, options } ) ;
14391490 } ,
14401491 ) ;
@@ -1756,6 +1807,7 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
17561807
17571808 const inputMessageInputContext = useCreateInputMessageInputContext ( {
17581809 additionalTextInputProps,
1810+ allowSendBeforeAttachmentsUpload,
17591811 asyncMessagesLockDistance,
17601812 asyncMessagesMinimumPressDuration,
17611813 asyncMessagesMultiSendEnabled,
0 commit comments