From 553c0154d91d6470e1198e7abcc887d648a02d34 Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Fri, 28 May 2021 16:31:08 +1000 Subject: [PATCH] Use waku message timestamp as better unique key --- CHANGELOG.md | 1 + examples/web-chat/src/App.tsx | 31 +++++++++++--------- examples/web-chat/src/ChatList.tsx | 46 ++++++++++++++++-------------- examples/web-chat/src/Message.ts | 39 +++++++++++++++++++++++++ examples/web-chat/src/Room.tsx | 11 ++++--- 5 files changed, 89 insertions(+), 39 deletions(-) create mode 100644 examples/web-chat/src/Message.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 45acd7180f..cb3dfaf157 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Enable access to `WakuMessage.timestamp`. +- Examples (web chat): Use `WakuMessage.timestamp` as unique key for list items. ## [0.5.0] - 2021-05-21 diff --git a/examples/web-chat/src/App.tsx b/examples/web-chat/src/App.tsx index 3738fa587e..fc52d8ea3f 100644 --- a/examples/web-chat/src/App.tsx +++ b/examples/web-chat/src/App.tsx @@ -2,7 +2,6 @@ import PeerId from 'peer-id'; import { useEffect, useState } from 'react'; import './App.css'; import { - ChatMessage, getStatusFleetNodes, Environment, StoreCodec, @@ -14,6 +13,7 @@ import Room from './Room'; import { WakuContext } from './WakuContext'; import { ThemeProvider } from '@livechat/ui-kit'; import { generate } from 'server-name-generator'; +import { Message } from './Message'; const themes = { AuthorName: { @@ -49,13 +49,17 @@ export const ChatContentTopic = '/waku/2/huilong/proto'; async function retrieveStoreMessages( waku: Waku, peerId: PeerId, - setArchivedMessages: (value: ChatMessage[]) => void + setArchivedMessages: (value: Message[]) => void ): Promise { const callback = (wakuMessages: WakuMessage[]): void => { - const messages = wakuMessages - .map((wakuMsg) => wakuMsg.payload) - .filter((payload) => !!payload) - .map((payload) => ChatMessage.decode(payload as Uint8Array)); + const messages: Message[] = []; + wakuMessages + .map((wakuMsg) => Message.fromWakuMessage(wakuMsg)) + .forEach((message) => { + if (message) { + messages.push(message); + } + }); setArchivedMessages(messages); }; @@ -70,18 +74,17 @@ async function retrieveStoreMessages( } export default function App() { - let [newMessages, setNewMessages] = useState([]); - let [archivedMessages, setArchivedMessages] = useState([]); + let [newMessages, setNewMessages] = useState([]); + let [archivedMessages, setArchivedMessages] = useState([]); let [stateWaku, setWaku] = useState(undefined); let [nick, setNick] = useState(generate()); useEffect(() => { const handleRelayMessage = (wakuMsg: WakuMessage) => { - if (wakuMsg.payload) { - const chatMsg = ChatMessage.decode(wakuMsg.payload); - if (chatMsg) { - setNewMessages([chatMsg]); - } + console.log('Message received: ', wakuMsg); + const msg = Message.fromWakuMessage(wakuMsg); + if (msg) { + setNewMessages([msg]); } }; @@ -147,7 +150,7 @@ export default function App() { setNick ); const commandMessages = response.map((msg) => { - return ChatMessage.fromUtf8String(new Date(), command, msg); + return Message.fromUtf8String(command, msg); }); setNewMessages(commandMessages); }} diff --git a/examples/web-chat/src/ChatList.tsx b/examples/web-chat/src/ChatList.tsx index 00273671e7..ee29e6f9af 100644 --- a/examples/web-chat/src/ChatList.tsx +++ b/examples/web-chat/src/ChatList.tsx @@ -1,20 +1,20 @@ import { useEffect, useRef, useState } from 'react'; -import { ChatMessage } from 'js-waku'; import { - Message, + Message as LiveMessage, MessageText, MessageGroup, MessageList, } from '@livechat/ui-kit'; +import { Message } from './Message'; interface Props { - archivedMessages: ChatMessage[]; - newMessages: ChatMessage[]; + archivedMessages: Message[]; + newMessages: Message[]; } export default function ChatList(props: Props) { - const [messages, setMessages] = useState([]); - const [groupedMessages, setGroupedMessages] = useState([]); + const [messages, setMessages] = useState([]); + const [groupedMessages, setGroupedMessages] = useState([]); let updatedMessages; if (IsThereNewMessages(props.newMessages, messages)) { @@ -42,17 +42,20 @@ export default function ChatList(props: Props) { const renderedGroupedMessages = groupedMessages.map((currentMessageGroup) => ( {currentMessageGroup.map((currentMessage) => ( - {currentMessage.payloadAsUtf8} - + ))} )); @@ -65,10 +68,10 @@ export default function ChatList(props: Props) { ); } -function groupMessagesBySender(messageArray: ChatMessage[]): ChatMessage[][] { +function groupMessagesBySender(messageArray: Message[]): Message[][] { let currentSender = -1; let lastNick = ''; - let messagesBySender: ChatMessage[][] = []; + let messagesBySender: Message[][] = []; let currentSenderMessage = 0; for (let currentMessage of messageArray) { @@ -83,7 +86,7 @@ function groupMessagesBySender(messageArray: ChatMessage[]): ChatMessage[][] { return messagesBySender; } -function formatDisplayDate(message: ChatMessage): string { +function formatDisplayDate(message: Message): string { return message.timestamp.toLocaleString([], { month: 'short', day: 'numeric', @@ -93,7 +96,7 @@ function formatDisplayDate(message: ChatMessage): string { }); } -const AlwaysScrollToBottom = (props: { newMessages: ChatMessage[] }) => { +const AlwaysScrollToBottom = (props: { newMessages: Message[] }) => { const elementRef = useRef(); useEffect(() => { @@ -106,8 +109,8 @@ const AlwaysScrollToBottom = (props: { newMessages: ChatMessage[] }) => { }; function IsThereNewMessages( - newValues: ChatMessage[], - currentValues: ChatMessage[] + newValues: Message[], + currentValues: Message[] ): boolean { if (newValues.length === 0) return false; if (currentValues.length === 0) return true; @@ -118,8 +121,8 @@ function IsThereNewMessages( } function copyMergeUniqueReplace( - newValues: ChatMessage[], - currentValues: ChatMessage[] + newValues: Message[], + currentValues: Message[] ) { const copy = currentValues.slice(); newValues.forEach((msg) => { @@ -131,10 +134,11 @@ function copyMergeUniqueReplace( return copy; } -function isEqual(lhs: ChatMessage, rhs: ChatMessage): boolean { +function isEqual(lhs: Message, rhs: Message): boolean { return ( lhs.nick === rhs.nick && lhs.payloadAsUtf8 === rhs.payloadAsUtf8 && - lhs.timestamp.toString() === rhs.timestamp.toString() + lhs.timestamp.valueOf() === rhs.timestamp.valueOf() && + lhs.sentTimestamp?.valueOf() === rhs.sentTimestamp?.valueOf() ); } diff --git a/examples/web-chat/src/Message.ts b/examples/web-chat/src/Message.ts new file mode 100644 index 0000000000..cb49a14440 --- /dev/null +++ b/examples/web-chat/src/Message.ts @@ -0,0 +1,39 @@ +import { ChatMessage, WakuMessage } from 'js-waku'; + +export class Message { + public chatMessage: ChatMessage; + // WakuMessage timestamp + public sentTimestamp: Date | undefined; + + constructor(chatMessage: ChatMessage, sentTimestamp: Date | undefined) { + this.chatMessage = chatMessage; + this.sentTimestamp = sentTimestamp; + } + + static fromWakuMessage(wakuMsg: WakuMessage): Message | undefined { + if (wakuMsg.payload) { + const chatMsg = ChatMessage.decode(wakuMsg.payload); + if (chatMsg) { + return new Message(chatMsg, wakuMsg.timestamp); + } + } + return; + } + + static fromUtf8String(nick: string, text: string): Message { + const now = new Date(); + return new Message(ChatMessage.fromUtf8String(now, nick, text), now); + } + + get nick() { + return this.chatMessage.nick; + } + + get timestamp() { + return this.chatMessage.timestamp; + } + + get payloadAsUtf8() { + return this.chatMessage.payloadAsUtf8; + } +} diff --git a/examples/web-chat/src/Room.tsx b/examples/web-chat/src/Room.tsx index 9f7006cd11..55eb0ad5c9 100644 --- a/examples/web-chat/src/Room.tsx +++ b/examples/web-chat/src/Room.tsx @@ -4,10 +4,11 @@ import ChatList from './ChatList'; import MessageInput from './MessageInput'; import { useWaku } from './WakuContext'; import { TitleBar } from '@livechat/ui-kit'; +import { Message } from './Message'; interface Props { - newMessages: ChatMessage[]; - archivedMessages: ChatMessage[]; + newMessages: Message[]; + archivedMessages: Message[]; commandHandler: (cmd: string) => void; nick: string; } @@ -52,10 +53,12 @@ async function handleMessage( if (message.startsWith('/')) { commandHandler(message); } else { - const chatMessage = ChatMessage.fromUtf8String(new Date(), nick, message); + const timestamp = new Date(); + const chatMessage = ChatMessage.fromUtf8String(timestamp, nick, message); const wakuMsg = WakuMessage.fromBytes( chatMessage.encode(), - ChatContentTopic + ChatContentTopic, + timestamp ); return messageSender(wakuMsg); }