updateLastConsumedMessageIndex freezes causing no update in messages

284 Views Asked by At

When component is mounted the channel is either grabbed from existing or created a new. This channel is later saved in the state for persist-ency in component. When the component is clicked all the channel messages are updated to consumed.

The Code belongs to the 'Today's Shift Channel' column. The Right side code is separate.

Senario : When I click a User onTaskSelect is called which triggers the whole user chat in right side after setting all the messages consumed. here problem 1 and 2 occurs. Now I click another user and click back to previously clicked user problem 3 occurs

Problems :
1) It does not update the consumed messages and always return zero.
2) It stops receiving the new messages from the .on listener called in joinOrCreate function.
3) on recurrent click response with error
SyncError: Access forbidden for identity (status: 403, code: 54007) at mapTransportError (http://localhost:3000/static/js/0.chunk.js:162153:12) at http://localhost:3000/static/js/0.chunk.js:162210:20

NOTE : Channels are set once and never left unless page refreshes
NOTE : The whole chat on right side is working fine and is in separate module.
NOTE : The Client was initialized at the start of application as context which persists for the whole lifecycle of application from opening to closing.

const TaskCard = props => {

    const [lastMessage, setLastMessage] = useState({})
    const [open, setOpen] = useState(false)
    const [unread, setUnread] = useState(0)
    const [channel, setChannel] = useState({})

    const joinOrCreate = useCallback(async() => {
        try{
            const fetchedChannel = await props.client.getChannelByUniqueName(props.task.id.toString())
            let joined = fetchedChannel
            if(fetchedChannel.state.status !== "joined") {
                joined = await fetchedChannel.join()
            }
            console.log()
            joined.getMessages()
            .then(messages => {
                if(messages.items.length > 0) {
                    if(joined.lastConsumedMessageIndex < messages.items.length - 1) {
                        setUnread(true)
                    }
                    const recent_message = messages.items[messages.items.length - 1]
                    const limit_message = recent_message.body.slice(0,14)
                    setLastMessage({
                        body: limit_message.length === 14? (limit_message + '...') : limit_message,
                        time: recent_message.timestamp,
                        index: recent_message.index,
                    })
                }
            })
            joined.on('messageAdded', messageUpdated)
            setChannel(joined)
        }
        catch(ch) {
            console.log(ch)
            try{
                const newChannel = await props.client.createChannel({
                    uniqueName: props.task.id.toString(),
                    friendlyName: 'General Chat Channel'
                })

                let joined = newChannel
                if(newChannel.state.status !== "joined") {   
                    joined = await newChannel.join()                 
                }
                joined.getMessages()
                .then(messages => {
                    if(messages.items.length > 0) {
                        const recent_message = messages.items[messages.items.length - 1]
                        setLastMessage({
                            body: recent_message.body,
                            time: recent_message.timestamp,
                        })
                    }
                })
                joined.on('messageAdded', messageUpdated)
                setChannel(joined)
            }

            catch(e) {
                console.log(e)
            }
        }
    }, [props.client, props.task.id])

    const messageUpdated = message => {
        const limit_message = message.body.slice(0,14)
        setLastMessage({
            body: limit_message.length === 14? (limit_message + '...') : limit_message,
            time: message.timestamp,
            index: message.index
        })
    }

    const onTaskSelect = () => {
        // console.log(lastMessage.index)
        console.log(channel.uniqueName)
        if(lastMessage.body) {
            channel.updateLastConsumedMessageIndex(+lastMessage.index)
            .then(res => {
                console.log(res)
            })
            .catch(e => {
                // console.log(props.client)
                // console.log(channel)
                console.log(e)
            })
        }
        props.onTaskClick(props.task.id.toString())
    }

    useEffect(() => {
        if(props.channelId === props.task.id) {
            setOpen(true)
            setUnread(false)
        }
        else {
            setOpen(false)
        }
    }, [props.channelId, props.task.id])

    useEffect(() => {
        joinOrCreate()
    }, [joinOrCreate])

    useEffect(() => {
        if(channel.lastConsumedMessageIndex < lastMessage.index && !open) {
            setUnread(true)
        }
    }, [channel.lastConsumedMessageIndex, lastMessage, open])

return (
        <Row key={props.task.id} >
            <Col className='justify-center'>
                <Card className={'more-than-90 ' + (open? 'background-active' : null)}
                    onClick={e => onTaskSelect()}
                >
                    <Row>
                        <Col md={2} style={{alignSelf: 'center', paddingLeft:'15px'}}>
                            {
                                props.task.worker.pic_url === "" || props.task.worker.pic_url === null ?
                                <div className="name-image">
                                    {props.task.worker.first_name[0] + props.task.worker.last_name[0]}
                                </div>
                                :
                                <Image width={50} height={50} src={props.task.worker.pic_url} roundedCircle />                                                
                            }
                        </Col>
                        <Col md={10}>
                            <Row>
                                <Col md={8}>
                                    <p style={{fontSize:'.9rem'}}>{props.task.worker.name}</p>
                                </Col>
                                <Col>
                                    <p style={{fontSize:'.7rem'}} className='left-align-text'>{lastMessage.time? moment(lastMessage.time).format('hh:mm A') : null}</p>
                                </Col>
                            </Row>
                            <Row>
                                <Col md={8}>
                                    <p style={{fontSize:'.7rem'}}>{lastMessage.body? lastMessage.body : null}</p>
                                </Col>
                                <Col>
                                    {
                                        unread ?
                                        <FontAwesomeIcon 
                                            icon={faEnvelopeOpenText}
                                            size="lg"
                                            color={"#0064bb"}
                                        />
                                        :
                                        null
                                    }
                                </Col>
                            </Row>
                        </Col>
                    </Row>
                </Card>
            </Col>
        </Row>
    )
}

enter image description here

1

There are 1 best solutions below

0
On

Twilio developer evangelist here.

I think Will Sams has a good point in their comment. You do need to set an unread index to start with for a channel to then have a valid unread index. From the docs:

Note: Chat does not automatically set the Consumption Horizon. If you do not explicitly set this within your application, no Consumption Horizon will exist for a User within the Channel. Without a Consumption Horizon, your user's Consumption Horizon (read status) will not synchronize correctly across clients. If a user does not have a Consumption Horizon set on a channel, getting unconsumed messages will always return 0. If a member of a Channel has no consumption status, their last consumed index and timestamp will be null or 0 depending on the platform.

So, when creating a channel, I would set the channel's messages to unconsumed to kick off that measure. You can also use the function setAllMessagesConsumed rather than having to read the index of the last message.

I also noticed that when you set the last message you miss setting its index at one point:

                        const recent_message = messages.items[messages.items.length - 1]
                        setLastMessage({
                            body: recent_message.body,
                            time: recent_message.timestamp,
                        })

which could cause trouble.

I am concerned about the way your callbacks and effects are set up. When you trigger onTaskSelect it looks as though you send the task ID back to the parent.

props.onTaskClick(props.task.id.toString());

Presumably this is so you can see all the messages in the main panel.

However, I also assume this sets a task in the parent, which is then passed as props.tasks to this child. The joinOrCreate callback is set to update when props.task.id is updated and useEffect is predicated on joinOrCreate changing. So my guess is that whenever you change between tasks you trigger the cleanup and re-evaluation of joinOrCreate. Except there is no cleanup function, so you end up reloading the channel, for each of these components, each time. I would guess that had something to do with breaking the events and the eventual errors you get when you click again.

Instead, I would probably re-architect this so that the TaskCard component doesn't control the lifecycle of the channel object. I would load and join the list of channels you intend to render in a parent component and then pass the channel object to the TaskCard. That would simplify the components as the parent would deal with the data and the TaskCard could handle just rendering. It would also mean that when the status of a card is changed (from showing to not showing) then you are only re-rendering the data. This way, you could also share the channel object between the TaskCard and the chat view on the right of your screenshot (otherwise I'm guessing you have to load it in that component too, and that's probably not helping either).

These are just some ideas based on what I can see in your application. I hope they help!