From 65404c769a346a8370643db65aaf05672c30db96 Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Tue, 4 May 2021 16:38:38 +1000 Subject: [PATCH 1/5] Add /connections command --- web-chat/src/command.ts | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/web-chat/src/command.ts b/web-chat/src/command.ts index 1da11566d4..7c1ae620ac 100644 --- a/web-chat/src/command.ts +++ b/web-chat/src/command.ts @@ -78,6 +78,31 @@ function peers(waku: Waku | undefined): string[] { return response; } +function connections(waku: Waku | undefined): string[] { + if (!waku) { + return ['Waku node is starting']; + } + let response: string[] = []; + waku.libp2p.connections.forEach( + ( + connections: import('libp2p-interfaces/src/connection/connection')[], + peerId + ) => { + response.push(peerId + ':'); + let strConnections = ' connections: ['; + connections.forEach((connection) => { + strConnections += JSON.stringify(connection.stat); + }); + strConnections += ']'; + response.push(strConnections); + } + ); + if (response.length === 0) { + response.push('Not connected to any peer.'); + } + return response; +} + export default function handleCommand( input: string, waku: Waku | undefined, @@ -102,6 +127,9 @@ export default function handleCommand( case '/peers': peers(waku).map((str) => response.push(str)); break; + case '/connections': + connections(waku).map((str) => response.push(str)); + break; default: response.push(`Unknown Command '${command}'`); } From 42553202a3d4b38aa94907eb207c08c154ee2d74 Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Tue, 4 May 2021 16:54:32 +1000 Subject: [PATCH 2/5] Reduce occurrences of dupe keys --- web-chat/src/App.tsx | 18 ++++++++++++------ web-chat/src/ChatList.tsx | 10 +++++++--- web-chat/src/ChatMessage.ts | 19 +++++++++++++++++++ web-chat/src/Room.tsx | 5 +++-- 4 files changed, 41 insertions(+), 11 deletions(-) create mode 100644 web-chat/src/ChatMessage.ts diff --git a/web-chat/src/App.tsx b/web-chat/src/App.tsx index 1a68481fef..ecd98e9026 100644 --- a/web-chat/src/App.tsx +++ b/web-chat/src/App.tsx @@ -2,7 +2,8 @@ import { multiaddr } from 'multiaddr'; import PeerId from 'peer-id'; import { useEffect, useState } from 'react'; import './App.css'; -import { ChatMessage } from 'waku/chat_message'; +import { ChatMessage } from './ChatMessage'; +import { ChatMessage as WakuChatMessage } from 'waku/chat_message'; import { WakuMessage } from 'waku/waku_message'; import { RelayDefaultTopic } from 'waku/waku_relay'; import { StoreCodec } from 'waku/waku_store'; @@ -73,7 +74,10 @@ export default function App() { const messages = response .map((wakuMsg) => wakuMsg.payload) .filter((payload) => !!payload) - .map((payload) => ChatMessage.decode(payload as Uint8Array)); + .map((payload) => WakuChatMessage.decode(payload as Uint8Array)) + .map((wakuChatMessage) => + ChatMessage.fromWakuChatMessage(wakuChatMessage) + ); copyMergeUniqueReplace(messages, stateMessages, setMessages); } } @@ -122,7 +126,7 @@ export default function App() { setNick ); const commandMessages = response.map((msg) => { - return new ChatMessage(new Date(), command, msg); + return new ChatMessage(new Date(), new Date(), command, msg); }); copyAppendReplace(commandMessages, stateMessages, setMessages); }} @@ -162,7 +166,9 @@ function decodeWakuMessage(data: Uint8Array): null | ChatMessage { if (!wakuMsg.payload) { return null; } - return ChatMessage.decode(wakuMsg.payload); + return ChatMessage.fromWakuChatMessage( + WakuChatMessage.decode(wakuMsg.payload) + ); } function copyAppendReplace( @@ -185,7 +191,7 @@ function copyMergeUniqueReplace( copy.push(msg); } }); - copy.sort((a, b) => a.timestamp.valueOf() - b.timestamp.valueOf()); + copy.sort((a, b) => a.sentTimestamp.valueOf() - b.sentTimestamp.valueOf()); setter(copy); } @@ -193,6 +199,6 @@ function isEqual(lhs: ChatMessage, rhs: ChatMessage): boolean { return ( lhs.nick === rhs.nick && lhs.message === rhs.message && - lhs.timestamp.toString() === rhs.timestamp.toString() + lhs.sentTimestamp.toString() === rhs.sentTimestamp.toString() ); } diff --git a/web-chat/src/ChatList.tsx b/web-chat/src/ChatList.tsx index 71af3597d8..96beaf919c 100644 --- a/web-chat/src/ChatList.tsx +++ b/web-chat/src/ChatList.tsx @@ -1,5 +1,5 @@ import { useEffect, useRef } from 'react'; -import { ChatMessage } from 'waku/chat_message'; +import { ChatMessage } from './ChatMessage'; import { Message, MessageText, @@ -20,7 +20,11 @@ export default function ChatList(props: Props) { {currentMessageGroup.map((currentMessage) => ( @@ -58,7 +62,7 @@ function groupMessagesBySender(messageArray: ChatMessage[]): ChatMessage[][] { } function formatDisplayDate(message: ChatMessage): string { - return message.timestamp.toLocaleString([], { + return message.sentTimestamp.toLocaleString([], { month: 'short', day: 'numeric', hour: 'numeric', diff --git a/web-chat/src/ChatMessage.ts b/web-chat/src/ChatMessage.ts new file mode 100644 index 0000000000..8b8f696572 --- /dev/null +++ b/web-chat/src/ChatMessage.ts @@ -0,0 +1,19 @@ +import { ChatMessage as WakuChatMessage } from 'waku/chat_message'; + +export class ChatMessage { + constructor( + public receivedTimestampMs: Date, + public sentTimestamp: Date, + public nick: string, + public message: string + ) {} + + static fromWakuChatMessage(wakuChatMessage: WakuChatMessage): ChatMessage { + return new ChatMessage( + new Date(), + wakuChatMessage.timestamp, + wakuChatMessage.nick, + wakuChatMessage.message + ); + } +} diff --git a/web-chat/src/Room.tsx b/web-chat/src/Room.tsx index 1353819039..3d1b8b3862 100644 --- a/web-chat/src/Room.tsx +++ b/web-chat/src/Room.tsx @@ -1,5 +1,6 @@ import { useState } from 'react'; -import { ChatMessage } from 'waku/chat_message'; +import { ChatMessage } from './ChatMessage'; +import { ChatMessage as WakuChatMessage } from 'waku/chat_message'; import { WakuMessage } from 'waku/waku_message'; import { ChatContentTopic } from './App'; import ChatList from './ChatList'; @@ -52,7 +53,7 @@ async function handleMessage( if (message.startsWith('/')) { commandHandler(message); } else { - const chatMessage = new ChatMessage(new Date(), nick, message); + const chatMessage = new WakuChatMessage(new Date(), nick, message); const wakuMsg = WakuMessage.fromBytes( chatMessage.encode(), ChatContentTopic From 9c65e590c44b7bbacf842d87f7c552f02410e75a Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Wed, 5 May 2021 09:54:12 +1000 Subject: [PATCH 3/5] Add streams to /connections response --- web-chat/src/command.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/web-chat/src/command.ts b/web-chat/src/command.ts index 7c1ae620ac..1ce72adece 100644 --- a/web-chat/src/command.ts +++ b/web-chat/src/command.ts @@ -92,6 +92,7 @@ function connections(waku: Waku | undefined): string[] { let strConnections = ' connections: ['; connections.forEach((connection) => { strConnections += JSON.stringify(connection.stat); + strConnections += "; " + JSON.stringify(connection.streams); }); strConnections += ']'; response.push(strConnections); From f4663ab24738b9e93d9c15405c2efb10f942b29e Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Wed, 5 May 2021 14:29:00 +1000 Subject: [PATCH 4/5] Only add new listeners when waku is initialised --- web-chat/src/App.tsx | 52 ++++++------------------- web-chat/src/ChatList.tsx | 72 +++++++++++++++++++++++++++++++---- web-chat/src/MessageInput.tsx | 6 +-- web-chat/src/Room.tsx | 13 ++++--- 4 files changed, 84 insertions(+), 59 deletions(-) diff --git a/web-chat/src/App.tsx b/web-chat/src/App.tsx index ecd98e9026..bd197bf3ea 100644 --- a/web-chat/src/App.tsx +++ b/web-chat/src/App.tsx @@ -46,15 +46,16 @@ const themes = { export const ChatContentTopic = 'dingpu'; export default function App() { - let [stateMessages, setMessages] = useState([]); + let [newMessages, setNewMessages] = useState([]); + let [archivedMessages, setArchivedMessages] = useState([]); let [stateWaku, setWaku] = useState(undefined); let [nick, setNick] = useState(generate()); useEffect(() => { - const handleNewMessages = (event: { data: Uint8Array }) => { + const handleRelayMessage = (event: { data: Uint8Array }) => { const chatMsg = decodeWakuMessage(event.data); if (chatMsg) { - copyAppendReplace([chatMsg], stateMessages, setMessages); + setNewMessages([chatMsg]); } }; @@ -78,7 +79,7 @@ export default function App() { .map((wakuChatMessage) => ChatMessage.fromWakuChatMessage(wakuChatMessage) ); - copyMergeUniqueReplace(messages, stateMessages, setMessages); + setArchivedMessages(messages); } } }; @@ -88,7 +89,7 @@ export default function App() { .then(() => console.log('Waku init done')) .catch((e) => console.log('Waku init failed ', e)); } else { - stateWaku.libp2p.pubsub.on(RelayDefaultTopic, handleNewMessages); + stateWaku.libp2p.pubsub.on(RelayDefaultTopic, handleRelayMessage); stateWaku.libp2p.peerStore.on( 'change:protocols', @@ -99,7 +100,7 @@ export default function App() { return () => { stateWaku?.libp2p.pubsub.removeListener( RelayDefaultTopic, - handleNewMessages + handleRelayMessage ); stateWaku?.libp2p.peerStore.removeListener( 'change:protocols', @@ -107,7 +108,7 @@ export default function App() { ); }; } - }, [stateWaku, stateMessages]); + }, [stateWaku]); return (
{ const { command, response } = handleCommand( input, @@ -128,7 +130,7 @@ export default function App() { const commandMessages = response.map((msg) => { return new ChatMessage(new Date(), new Date(), command, msg); }); - copyAppendReplace(commandMessages, stateMessages, setMessages); + setNewMessages(commandMessages); }} /> @@ -170,35 +172,3 @@ function decodeWakuMessage(data: Uint8Array): null | ChatMessage { WakuChatMessage.decode(wakuMsg.payload) ); } - -function copyAppendReplace( - newValues: Array, - currentValues: Array, - setter: (val: Array) => void -) { - const copy = currentValues.slice(); - setter(copy.concat(newValues)); -} - -function copyMergeUniqueReplace( - newValues: ChatMessage[], - currentValues: ChatMessage[], - setter: (val: ChatMessage[]) => void -) { - const copy = currentValues.slice(); - newValues.forEach((msg) => { - if (!copy.find(isEqual.bind({}, msg))) { - copy.push(msg); - } - }); - copy.sort((a, b) => a.sentTimestamp.valueOf() - b.sentTimestamp.valueOf()); - setter(copy); -} - -function isEqual(lhs: ChatMessage, rhs: ChatMessage): boolean { - return ( - lhs.nick === rhs.nick && - lhs.message === rhs.message && - lhs.sentTimestamp.toString() === rhs.sentTimestamp.toString() - ); -} diff --git a/web-chat/src/ChatList.tsx b/web-chat/src/ChatList.tsx index 96beaf919c..8a6c076062 100644 --- a/web-chat/src/ChatList.tsx +++ b/web-chat/src/ChatList.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { ChatMessage } from './ChatMessage'; import { Message, @@ -8,18 +8,40 @@ import { } from '@livechat/ui-kit'; interface Props { - messages: ChatMessage[]; + archivedMessages: ChatMessage[]; + newMessages: ChatMessage[]; } export default function ChatList(props: Props) { - const messages = props.messages; + const [messages, setMessages] = useState([]); + let updatedMessages; - const messagesGroupedBySender = groupMessagesBySender(props.messages).map( + if (IsThereNewMessages(props.newMessages, messages)) { + updatedMessages = messages.slice().concat(props.newMessages); + if (IsThereNewMessages(props.archivedMessages, updatedMessages)) { + updatedMessages = copyMergeUniqueReplace( + props.archivedMessages, + updatedMessages + ); + } + } else { + if (IsThereNewMessages(props.archivedMessages, messages)) { + updatedMessages = copyMergeUniqueReplace( + props.archivedMessages, + messages + ); + } + } + + if (updatedMessages) { + setMessages(updatedMessages); + } + + const messagesGroupedBySender = groupMessagesBySender(messages).map( (currentMessageGroup) => ( {currentMessageGroup.map((currentMessage) => ( {messagesGroupedBySender} - + ); } @@ -71,14 +93,48 @@ function formatDisplayDate(message: ChatMessage): string { }); } -const AlwaysScrollToBottom = (props: Props) => { +const AlwaysScrollToBottom = (props: { newMessages: ChatMessage[] }) => { const elementRef = useRef(); useEffect(() => { // @ts-ignore elementRef.current.scrollIntoView(); - }, [props.messages]); + }, [props.newMessages]); // @ts-ignore return
; }; + +function IsThereNewMessages( + newValues: ChatMessage[], + currentValues: ChatMessage[] +): boolean { + if (newValues.length === 0) return false; + if (currentValues.length === 0) return true; + + return !newValues.find((newMsg) => + currentValues.find(isEqual.bind({}, newMsg)) + ); +} + +function copyMergeUniqueReplace( + newValues: ChatMessage[], + currentValues: ChatMessage[] +) { + const copy = currentValues.slice(); + newValues.forEach((msg) => { + if (!copy.find(isEqual.bind({}, msg))) { + copy.push(msg); + } + }); + copy.sort((a, b) => a.sentTimestamp.valueOf() - b.sentTimestamp.valueOf()); + return copy; +} + +function isEqual(lhs: ChatMessage, rhs: ChatMessage): boolean { + return ( + lhs.nick === rhs.nick && + lhs.message === rhs.message && + lhs.sentTimestamp.toString() === rhs.sentTimestamp.toString() + ); +} diff --git a/web-chat/src/MessageInput.tsx b/web-chat/src/MessageInput.tsx index bdd2f4424a..d8a8bfe208 100644 --- a/web-chat/src/MessageInput.tsx +++ b/web-chat/src/MessageInput.tsx @@ -10,8 +10,7 @@ import { } from '@livechat/ui-kit'; interface Props { - messageHandler: (msg: string) => void; - sendMessage: (() => Promise) | undefined; + sendMessage: ((msg: string) => Promise) | undefined; } export default function MessageInput(props: Props) { @@ -20,14 +19,13 @@ export default function MessageInput(props: Props) { const sendMessage = async () => { if (props.sendMessage) { - await props.sendMessage(); + await props.sendMessage(inputText); setInputText(''); } }; const messageHandler = (event: ChangeEvent) => { setInputText(event.target.value); - props.messageHandler(event.target.value); }; const keyPressHandler = async (event: KeyboardEvent) => { diff --git a/web-chat/src/Room.tsx b/web-chat/src/Room.tsx index 3d1b8b3862..76ccc5cc4b 100644 --- a/web-chat/src/Room.tsx +++ b/web-chat/src/Room.tsx @@ -1,4 +1,3 @@ -import { useState } from 'react'; import { ChatMessage } from './ChatMessage'; import { ChatMessage as WakuChatMessage } from 'waku/chat_message'; import { WakuMessage } from 'waku/waku_message'; @@ -9,13 +8,13 @@ import { useWaku } from './WakuContext'; import { TitleBar } from '@livechat/ui-kit'; interface Props { - lines: ChatMessage[]; + newMessages: ChatMessage[]; + archivedMessages: ChatMessage[]; commandHandler: (cmd: string) => void; nick: string; } export default function Room(props: Props) { - let [messageToSend, setMessageToSend] = useState(''); const { waku } = useWaku(); return ( @@ -24,12 +23,14 @@ export default function Room(props: Props) { style={{ height: '98vh', display: 'flex', flexDirection: 'column' }} > - + { + ? async (messageToSend) => { return handleMessage( messageToSend, props.nick, From 10aafc6cbcae17a33494aca63110a70089806ebf Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Wed, 5 May 2021 14:29:10 +1000 Subject: [PATCH 5/5] fixup! Connection --- web-chat/src/command.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web-chat/src/command.ts b/web-chat/src/command.ts index 1ce72adece..e6ee0e4835 100644 --- a/web-chat/src/command.ts +++ b/web-chat/src/command.ts @@ -92,7 +92,7 @@ function connections(waku: Waku | undefined): string[] { let strConnections = ' connections: ['; connections.forEach((connection) => { strConnections += JSON.stringify(connection.stat); - strConnections += "; " + JSON.stringify(connection.streams); + strConnections += '; ' + JSON.stringify(connection.streams); }); strConnections += ']'; response.push(strConnections);