Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

question: message.new message text not updated #2931

Open
bigint-zz opened this issue Feb 3, 2025 · 25 comments
Open

question: message.new message text not updated #2931

bigint-zz opened this issue Feb 3, 2025 · 25 comments

Comments

@bigint-zz
Copy link

I am trying to implement the encryption of the chat stream, I successfully encrypt the message on send by using the prop doSendMessageRequest on Channel component, but now I am struggling to decrypt any new message...

I tried different approaches but none works. the following one with the message.new event handler, the setMessages doesn't update the message on the UI

useEffect(() => {
        const handleNewMessage = async (event: any) => {
            const { message } = event;
            const decryptedText = await decryptMessage(message.text, sharedKey, iv);

            setMessages(prev => [
                ...prev,
                {
                    id: message.id,
                    text: decryptedText || message.text, // In caso di errore, usa il testo originale
                    user: message.user,
                    created_at: message.created_at,
                }
            ]);
        };

        channel.on("message.new", handleNewMessage);

        return () => {
            channel.off("message.new", handleNewMessage);
        };
    }, [channel]);
@bigint-zz bigint-zz added the Bug Something isn't working in the SDK label Feb 3, 2025
@MartinCupela
Copy link

Hey @bigint-zz , where does the setMessages function come from? Could you try to recreate the issue in a sandbox? No need to use the decryption logic, just some modification as a POC. Thank you

@bigint-zz
Copy link
Author

bigint-zz commented Feb 3, 2025

const [messages, setMessages] = useState<MessageResponse[]>([]);

below the whole code of the ChannelScreen view


export default function ChannelScreen() {
    const [channel, setChannel] = useState<ChannelType | null>(null);
    const { cid } = useLocalSearchParams<{ cid: string }>();
    const [messages, setMessages] = useState<MessageResponse[]>([]);
    const { client } = useChatContext();

    useEffect(() => {
        const fetchChannel = async () => {
            const channels = await client.queryChannels({ cid });
            setChannel(channels[0]);
        };

        fetchChannel();
    }, [cid]);

    useEffect(() => {
        // Funzione gestisce i nuovi messaggi in arrivo
        const handleNewMessage = async (event: any) => {
            const { message } = event;
            console.log(message.text)
            const decryptedText = "test"
            setMessages(prev => [
                ...prev,
                {
                    id: message.id,
                    text: decryptedText || message.text, // In caso di errore, usa il testo originale
                    user: message.user,
                    created_at: message.created_at,
                }
            ]);
        };

        // Registra il listener per l'evento "message.new"
        channel?.on("message.new", handleNewMessage);

        // Pulisci il listener all'unmount
        return () => {
            channel?.off("message.new", handleNewMessage);
        };
    }, [channel]);

    if (!channel) {
        return <ActivityIndicator />;
    }

    const handleSendMessage = async (
        channelId: string,
        messageData: Message<DefaultGenerics>
    ): Promise<SendMessageAPIResponse> => {
        try {
            // Genera l'IV e cifra il messaggio
            const iv = "1234" //await generateRandomIV();
            const encryptedText = "testtest"

            // Restituisci il messaggio cifrato con l'IV in custom
            const newMessage: Message<DefaultGenerics> = {
                ...messageData,
                text: encryptedText,
                custom: {
                    ...messageData.custom,
                    iv, // Questo IV verrà utilizzato per la decifratura sul lato ricevente
                },
            };

            // Invia il messaggio modificato e restituisci la risposta dell'API
            return channel.sendMessage(newMessage);
        } catch (error) {
            console.error("Errore in handleSendMessage:", error);
            throw error;
        }
    };

    return (
        <Channel
            channel={channel}
            audioRecordingEnabled
            doSendMessageRequest={handleSendMessage}
            deletedMessagesVisibilityType="never" // nascondere messaggi cancellati
        >
            <Stack.Screen
                options={{
                    title: 'Channel'
                }}
            />
            <MessageList messages={messages} />
            <SafeAreaView edges={['bottom']}>
                <MessageInput />
            </SafeAreaView>
        </Channel >
    );
}

what I have in my mind is the following one, I send a message "ciao" , the doSendMessageRequest prop crypt the message "ciao" and send it to Stream, but I am not understanding how to show in my ui the text decrypted. I tried with message.new but the ui is not updated

@MartinCupela
Copy link

Where do the messages that are being set are passed?

@bigint-zz
Copy link
Author

in the MessageList
<MessageList messages={messages} />

@bigint-zz
Copy link
Author

any news? I tried also another approach below


export default function ChannelScreen() {
    const [channel, setChannel] = useState<ChannelType | null>(null);
    const { cid } = useLocalSearchParams<{ cid: string }>();
    const { profile } = useAuth();
    const { client } = useChatContext();

    useEffect(() => {
        const fetchChannel = async () => {
            const channels = await client.queryChannels({ cid });
            setChannel(channels[0]);
        };

        fetchChannel();
    }, [cid]);

    if (!channel) {
        return <ActivityIndicator />;
    }

    const handleSendMessage = async (
        channelId: string,
        messageData: Message
    ): Promise<SendMessageAPIResponse> => {
        console.log("sendMSG")
        const encryptedMessage = await encrypt(messageData.text, client.user.extraData.publicKey)
        const modifiedMessage: Message = {
            ...messageData,
            text: `${encryptedMessage}`,
        };
        return channel.sendMessage(modifiedMessage);
    };

    const handleNewMessage = (
        props: any
    ) => {
        const encryptedMessage = decrypt(props.message.text, profile.id)
        //console.log(encryptedMessage)
        //console.log(profile)
        console.log("handleNewMessage")
        return <Text style={{ padding: 10 }} >{encryptedMessage}</Text>

    };

    return (
        <Channel
            channel={channel}
            audioRecordingEnabled
            doSendMessageRequest={handleSendMessage}
            MessageText={handleNewMessage}
            deletedMessagesVisibilityType="never" // nascondere messaggi cancellati
        >
            <Stack.Screen
                options={{
                    title: 'Channel'
                }}
            />
            <MessageList />
            <SafeAreaView edges={['bottom']}>
                <MessageInput />
            </SafeAreaView>
        </Channel >
    );
}

with the property MessageText, but the problem here is that I triggered 2 times... when I send the message and when I render it

@MartinCupela
Copy link

Hey @bigint-zz , if you want to pass the messages to the MessageList, I would recommend you to wrap that component in your custom component.

const decryptMessage = (m: StreamMessage) => ({ ...m, text: 'XXX' + m.text });

const DecryptedMessageList = () => {
  const { messages } = useChannelStateContext();
  const decryptedMessages = useMemo(() => messages?.map(decryptMessage), [messages]);
  return <MessageList messages={decryptedMessages} />;
};

And render:

<Channel>
  <DecryptedMessageList/>
</Channel>

@MartinCupela MartinCupela removed the Bug Something isn't working in the SDK label Feb 5, 2025
@MartinCupela MartinCupela changed the title bug: message.new message text not updated question: message.new message text not updated Feb 5, 2025
@bigint-zz
Copy link
Author

useChannelStateContext();

doesnt exists

@MartinCupela
Copy link

import { useChannelStateContext } from 'stream-chat-react'

@bigint-zz
Copy link
Author

i am using React Native, stream-chat-react is not compatible

@MartinCupela
Copy link

Why do you report to stream-chat-react repo?

@bigint-zz
Copy link
Author

you are right, sorry for that I didn't notice it. Do you still have a solution or should I move to the other repo?

@MartinCupela
Copy link

I will transfer this issue to the correct repo.

@MartinCupela MartinCupela transferred this issue from GetStream/stream-chat-react Feb 5, 2025
@bigint-zz bigint-zz marked this as a duplicate of #2932 Feb 5, 2025
@bigint-zz
Copy link
Author

up

@isekovanic
Copy link
Contributor

isekovanic commented Feb 5, 2025

Hello @bigint-zz,

Which version of the SDK are you currently using ? Our MessageList component does not accept messages as a property, nor is this mentioned in the docs anywhere (types too) as far as I'm aware. You can see the React Native docs here for the latest version and some of the previous versions too.

Anyway, back to the task at hand. If you wish to decrypt the messages the best I can advise is to override the MessageSimple component and passing a custom message to it, which contains the decrypted text you're looking for. Of course, make sure to pass all of the other properties to it too so that you preserve the default implementation and only fiddle with the message text.

That would look something like this:

const decryptMessage = (m) => ({ ...m, text: 'XXX' + m.text });

const CustomMessageSimple = (props) => {
  const { message } = props;
  const decryptedMessage = useMemo(() => decryptMessage(message), [message]);
  
  return <MessageSimple {...props} message={decryptedMessage} />
}

const ChannelScreen = () => {
/* ...rest of the channel screen implementation */
return <Channel
/* ...rest of the channel props */
MessageSimple={CustomMessageSimple}
}

You can achieve pretty much the same by overriding MessageContent as well (would be preferred for performance reasons) but whatever works for you.

PS: Thanks @MartinCupela for transferring the issue to our repo !

@bigint-zz
Copy link
Author

below my package.json

{
  "dependencies": {
    "@react-native-async-storage/async-storage": "1.23.1",
    "@react-native-community/netinfo": "11.4.1",
    "@react-native-firebase/app": "^21.7.1",
    "@react-native-firebase/messaging": "^21.7.1",
    "@rneui/base": "^4.0.0-rc.7",
    "@rneui/themed": "^4.0.0-rc.8",
    "@stream-io/stream-chat-css": "^5.6.1",
    "@supabase/supabase-js": "^2.48.1",
    "expo": "~52.0.28",
    "expo-av": "~15.0.2",
    "expo-build-properties": "~0.13.2",
    "expo-clipboard": "~7.0.1",
    "expo-constants": "~17.0.5",
    "expo-document-picker": "~13.0.2",
    "expo-haptics": "~14.0.1",
    "expo-image-manipulator": "~13.0.6",
    "expo-image-picker": "~16.0.4",
    "expo-linking": "~7.0.5",
    "expo-media-library": "~17.0.5",
    "expo-router": "~4.0.17",
    "expo-secure-store": "~14.0.1",
    "expo-sharing": "~13.0.1",
    "expo-status-bar": "~2.0.1",
    "react": "18.3.1",
    "react-native": "0.76.6",
    "react-native-elements": "^3.4.3",
    "react-native-encrypted-storage": "^4.0.3",
    "react-native-fast-image": "^8.6.3",
    "react-native-gesture-handler": "~2.20.2",
    "react-native-reanimated": "~3.16.1",
    "react-native-rsa-native": "^2.0.5",
    "react-native-safe-area-context": "4.12.0",
    "react-native-screens": "~4.4.0",
    "react-native-svg": "15.8.0",
    "stream-chat-expo": "^6.3.1",
    "stream-chat-react-native": "^6.4.1"
  },
  "devDependencies": {
    "@babel/core": "^7.25.2",
    "@react-native-community/cli": "latest",
    "@types/react": "~18.3.12",
    "@types/react-native": "^0.72.8",
    "supabase": "^2.9.6",
    "typescript": "^5.3.3"
  },
  "private": true
}

@isekovanic
Copy link
Contributor

That's fine. One thing to point out though, you're using expo and yet you have both stream-chat-expo and stream-chat-react-native installed. It's the same library, but for different platforms - please remove the stream-chat-react-native one and import everything you need from the SDK from stream-chat-expo.

@bigint-zz
Copy link
Author

thanks for the suggestion.

Can you please provide me an example by an override of MessageContent?

@isekovanic
Copy link
Contributor

It's the same deal as doing it for MessageSimple. You can import both components from the SDK directly, i.e:

import { MessageSimple, MessageContent } from 'stream-chat-expo';

@bigint-zz
Copy link
Author

I did like that but doesn't work.

const CustomMessageSimple = (props) => {
        console.log(props)
        //const { message } = props;
        //const decryptedMessage = useMemo(() => decryptMessage(message), [message]);
        //console.log(message)
        //return <MessageSimple {...props} message={message} />
    }

    return (
        <Channel
            channel={channel}
            audioRecordingEnabled
            //doSendMessageRequest={handleSendMessage}
            deletedMessagesVisibilityType="never" // nascondere messaggi cancellati
        >
            <Stack.Screen
                options={{
                    title: 'Channel'
                }}
            />
            <MessageList MessageContent={CustomMessageSimple} />
            <SafeAreaView edges={['bottom']}>
                <MessageInput />
            </SafeAreaView>
        </Channel >
    );

@isekovanic
Copy link
Contributor

Please read my comment above, as well as the docs - you're supposed to pass it to the Channel component; not MessageList.

@bigint-zz
Copy link
Author

I did and I cannot access the text ... do you have any example or share just three rows of codes about using that approach with MessageContent? thanks in advance

@isekovanic
Copy link
Contributor

Ah, deepest apologies - it seems I had a lapse in judgement here.

/* ...rest of your code */
const CustomMessage = (props) => {
  const { message } = props;
  const decryptedMessage = useMemo(() => decryptMessage(message), [message]);

  return <Message {...props} message={decryptedMessage} />;
};

MessageSimple and MessageContent cannot be used for this (the contents of the message) specifically as they rely on the MessageContext inside in order to consume their data. You may pass the message to the HoC as shown above and override the Message prop on the Channel component. For some reason this isn't part of our docs, but the usage would be something like this:

return <Channel
Message={CustomMessage}
{...restProps}
>

Please make sure that if you do this your CustomMessage implementation is either out of the component rendering your Channel or properly memoized.

@bigint-zz
Copy link
Author

how can I use useMemo with an asynchronous function?

@isekovanic
Copy link
Contributor

You can't do that. I used the useMemo as an example of keeping the number of rerenders of the Message component stable. But that's a bit out of scope of our SDK I'm afraid, I'm sure there's literature online which you'll find useful on these topics.

In any case, can you please confirm that the initial issue is resolved so that we can close this ticket ?

Thanks !

@bigint-zz
Copy link
Author

can you suggest me the best approach to use for decrypt the message considering that I need to use an asynchronous func? considering I am using ur SDK

please help me, about this topic I really didn't find nothing and this issue it will be very useful for other people.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants