113: Use waku store to retrieve archived messages in browser app r=D4nte a=D4nte

Resolves #69 

Co-authored-by: Franck Royer <franck@status.im>
This commit is contained in:
bors[bot] 2021-05-03 04:11:23 +00:00 committed by GitHub
commit 2c72c6d388
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 134 additions and 627 deletions

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,6 @@
"homepage": "/js-waku", "homepage": "/js-waku",
"dependencies": { "dependencies": {
"@livechat/ui-kit": "*", "@livechat/ui-kit": "*",
"peer-id": "^0.14.8",
"react": "^16.14.0", "react": "^16.14.0",
"react-dom": "^16.14.0", "react-dom": "^16.14.0",
"server-name-generator": "^1.0.5", "server-name-generator": "^1.0.5",

View File

@ -5,6 +5,7 @@ import './App.css';
import { ChatMessage } from 'waku-chat/chat_message'; import { ChatMessage } from 'waku-chat/chat_message';
import { WakuMessage } from 'waku/waku_message'; import { WakuMessage } from 'waku/waku_message';
import { RelayDefaultTopic } from 'waku/waku_relay'; import { RelayDefaultTopic } from 'waku/waku_relay';
import { StoreCodec } from 'waku/waku_store';
import handleCommand from './command'; import handleCommand from './command';
import Room from './Room'; import Room from './Room';
import Waku from 'waku/waku'; import Waku from 'waku/waku';
@ -23,7 +24,29 @@ export default function App() {
const handleNewMessages = (event: { data: Uint8Array }) => { const handleNewMessages = (event: { data: Uint8Array }) => {
const chatMsg = decodeWakuMessage(event.data); const chatMsg = decodeWakuMessage(event.data);
if (chatMsg) { if (chatMsg) {
copyAndReplace([chatMsg], stateMessages, setMessages); copyAppendReplace([chatMsg], stateMessages, setMessages);
}
};
const handleProtocolChange = async (
waku: Waku,
{ peerId, protocols }: { peerId: PeerId; protocols: string[] }
) => {
if (protocols.includes(StoreCodec)) {
console.log(
`Retrieving archived messages from ${peerId.toB58String()}`
);
const response = await waku.store.queryHistory(peerId, [
ChatContentTopic,
]);
if (response) {
const messages = response
.map((wakuMsg) => wakuMsg.payload)
.filter((payload) => !!payload)
.map((payload) => ChatMessage.decode(payload as Uint8Array));
copyMergeUniqueReplace(messages, stateMessages, setMessages);
}
} }
}; };
@ -34,12 +57,21 @@ export default function App() {
} else { } else {
stateWaku.libp2p.pubsub.on(RelayDefaultTopic, handleNewMessages); stateWaku.libp2p.pubsub.on(RelayDefaultTopic, handleNewMessages);
stateWaku.libp2p.peerStore.once(
'change:protocols',
handleProtocolChange.bind({}, stateWaku)
);
// To clean up listener when component unmounts // To clean up listener when component unmounts
return () => { return () => {
stateWaku?.libp2p.pubsub.removeListener( stateWaku?.libp2p.pubsub.removeListener(
RelayDefaultTopic, RelayDefaultTopic,
handleNewMessages handleNewMessages
); );
stateWaku?.libp2p.peerStore.removeListener(
'change:protocols',
handleProtocolChange.bind({}, stateWaku)
);
}; };
} }
}, [stateWaku, stateMessages]); }, [stateWaku, stateMessages]);
@ -63,7 +95,7 @@ export default function App() {
const commandMessages = response.map((msg) => { const commandMessages = response.map((msg) => {
return new ChatMessage(new Date(), command, msg); return new ChatMessage(new Date(), command, msg);
}); });
copyAndReplace(commandMessages, stateMessages, setMessages); copyAppendReplace(commandMessages, stateMessages, setMessages);
}} }}
/> />
</ThemeProvider> </ThemeProvider>
@ -104,7 +136,7 @@ function decodeWakuMessage(data: Uint8Array): null | ChatMessage {
return ChatMessage.decode(wakuMsg.payload); return ChatMessage.decode(wakuMsg.payload);
} }
function copyAndReplace<T>( function copyAppendReplace<T>(
newValues: Array<T>, newValues: Array<T>,
currentValues: Array<T>, currentValues: Array<T>,
setter: (val: Array<T>) => void setter: (val: Array<T>) => void
@ -112,3 +144,26 @@ function copyAndReplace<T>(
const copy = currentValues.slice(); const copy = currentValues.slice();
setter(copy.concat(newValues)); 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.timestamp.valueOf() - b.timestamp.valueOf());
setter(copy);
}
function isEqual(lhs: ChatMessage, rhs: ChatMessage): boolean {
return (
lhs.nick === rhs.nick &&
lhs.message === rhs.message &&
lhs.timestamp.toString() === rhs.timestamp.toString()
);
}

View File

@ -11,7 +11,8 @@ export default function ChatList(props: Props) {
const listItems = messages.map((currentMessage) => ( const listItems = messages.map((currentMessage) => (
<Message <Message
key={currentMessage.timestamp.toString()} // We assume that the same user is not sending two messages in the same second
key={currentMessage.timestamp.toString() + currentMessage.nick}
authorName={currentMessage.nick} authorName={currentMessage.nick}
date={formatDisplayDate(currentMessage)} date={formatDisplayDate(currentMessage)}
> >

View File

@ -40,7 +40,7 @@ export default function MessageInput(props: Props) {
<TextComposer <TextComposer
onKeyDown={keyPressHandler} onKeyDown={keyPressHandler}
onChange={messageHandler} onChange={messageHandler}
active={waku} active={!!waku}
onButtonClick={sendMessage} onButtonClick={sendMessage}
> >
<Row align="center"> <Row align="center">