mirror of https://github.com/waku-org/js-waku.git
Use waku message timestamp as better unique key
This commit is contained in:
parent
9e64eec2a6
commit
553c0154d9
|
@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Enable access to `WakuMessage.timestamp`.
|
- Enable access to `WakuMessage.timestamp`.
|
||||||
|
- Examples (web chat): Use `WakuMessage.timestamp` as unique key for list items.
|
||||||
|
|
||||||
## [0.5.0] - 2021-05-21
|
## [0.5.0] - 2021-05-21
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@ import PeerId from 'peer-id';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
import {
|
import {
|
||||||
ChatMessage,
|
|
||||||
getStatusFleetNodes,
|
getStatusFleetNodes,
|
||||||
Environment,
|
Environment,
|
||||||
StoreCodec,
|
StoreCodec,
|
||||||
|
@ -14,6 +13,7 @@ import Room from './Room';
|
||||||
import { WakuContext } from './WakuContext';
|
import { WakuContext } from './WakuContext';
|
||||||
import { ThemeProvider } from '@livechat/ui-kit';
|
import { ThemeProvider } from '@livechat/ui-kit';
|
||||||
import { generate } from 'server-name-generator';
|
import { generate } from 'server-name-generator';
|
||||||
|
import { Message } from './Message';
|
||||||
|
|
||||||
const themes = {
|
const themes = {
|
||||||
AuthorName: {
|
AuthorName: {
|
||||||
|
@ -49,13 +49,17 @@ export const ChatContentTopic = '/waku/2/huilong/proto';
|
||||||
async function retrieveStoreMessages(
|
async function retrieveStoreMessages(
|
||||||
waku: Waku,
|
waku: Waku,
|
||||||
peerId: PeerId,
|
peerId: PeerId,
|
||||||
setArchivedMessages: (value: ChatMessage[]) => void
|
setArchivedMessages: (value: Message[]) => void
|
||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
const callback = (wakuMessages: WakuMessage[]): void => {
|
const callback = (wakuMessages: WakuMessage[]): void => {
|
||||||
const messages = wakuMessages
|
const messages: Message[] = [];
|
||||||
.map((wakuMsg) => wakuMsg.payload)
|
wakuMessages
|
||||||
.filter((payload) => !!payload)
|
.map((wakuMsg) => Message.fromWakuMessage(wakuMsg))
|
||||||
.map((payload) => ChatMessage.decode(payload as Uint8Array));
|
.forEach((message) => {
|
||||||
|
if (message) {
|
||||||
|
messages.push(message);
|
||||||
|
}
|
||||||
|
});
|
||||||
setArchivedMessages(messages);
|
setArchivedMessages(messages);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -70,18 +74,17 @@ async function retrieveStoreMessages(
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
let [newMessages, setNewMessages] = useState<ChatMessage[]>([]);
|
let [newMessages, setNewMessages] = useState<Message[]>([]);
|
||||||
let [archivedMessages, setArchivedMessages] = useState<ChatMessage[]>([]);
|
let [archivedMessages, setArchivedMessages] = useState<Message[]>([]);
|
||||||
let [stateWaku, setWaku] = useState<Waku | undefined>(undefined);
|
let [stateWaku, setWaku] = useState<Waku | undefined>(undefined);
|
||||||
let [nick, setNick] = useState<string>(generate());
|
let [nick, setNick] = useState<string>(generate());
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleRelayMessage = (wakuMsg: WakuMessage) => {
|
const handleRelayMessage = (wakuMsg: WakuMessage) => {
|
||||||
if (wakuMsg.payload) {
|
console.log('Message received: ', wakuMsg);
|
||||||
const chatMsg = ChatMessage.decode(wakuMsg.payload);
|
const msg = Message.fromWakuMessage(wakuMsg);
|
||||||
if (chatMsg) {
|
if (msg) {
|
||||||
setNewMessages([chatMsg]);
|
setNewMessages([msg]);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -147,7 +150,7 @@ export default function App() {
|
||||||
setNick
|
setNick
|
||||||
);
|
);
|
||||||
const commandMessages = response.map((msg) => {
|
const commandMessages = response.map((msg) => {
|
||||||
return ChatMessage.fromUtf8String(new Date(), command, msg);
|
return Message.fromUtf8String(command, msg);
|
||||||
});
|
});
|
||||||
setNewMessages(commandMessages);
|
setNewMessages(commandMessages);
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { ChatMessage } from 'js-waku';
|
|
||||||
import {
|
import {
|
||||||
Message,
|
Message as LiveMessage,
|
||||||
MessageText,
|
MessageText,
|
||||||
MessageGroup,
|
MessageGroup,
|
||||||
MessageList,
|
MessageList,
|
||||||
} from '@livechat/ui-kit';
|
} from '@livechat/ui-kit';
|
||||||
|
import { Message } from './Message';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
archivedMessages: ChatMessage[];
|
archivedMessages: Message[];
|
||||||
newMessages: ChatMessage[];
|
newMessages: Message[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ChatList(props: Props) {
|
export default function ChatList(props: Props) {
|
||||||
const [messages, setMessages] = useState<ChatMessage[]>([]);
|
const [messages, setMessages] = useState<Message[]>([]);
|
||||||
const [groupedMessages, setGroupedMessages] = useState<ChatMessage[][]>([]);
|
const [groupedMessages, setGroupedMessages] = useState<Message[][]>([]);
|
||||||
let updatedMessages;
|
let updatedMessages;
|
||||||
|
|
||||||
if (IsThereNewMessages(props.newMessages, messages)) {
|
if (IsThereNewMessages(props.newMessages, messages)) {
|
||||||
|
@ -42,17 +42,20 @@ export default function ChatList(props: Props) {
|
||||||
const renderedGroupedMessages = groupedMessages.map((currentMessageGroup) => (
|
const renderedGroupedMessages = groupedMessages.map((currentMessageGroup) => (
|
||||||
<MessageGroup onlyFirstWithMeta>
|
<MessageGroup onlyFirstWithMeta>
|
||||||
{currentMessageGroup.map((currentMessage) => (
|
{currentMessageGroup.map((currentMessage) => (
|
||||||
<Message
|
<LiveMessage
|
||||||
key={
|
key={
|
||||||
currentMessage.timestamp.valueOf() +
|
currentMessage.sentTimestamp
|
||||||
currentMessage.nick +
|
? currentMessage.sentTimestamp.valueOf()
|
||||||
currentMessage.payloadAsUtf8
|
: '' +
|
||||||
|
currentMessage.timestamp.valueOf() +
|
||||||
|
currentMessage.nick +
|
||||||
|
currentMessage.payloadAsUtf8
|
||||||
}
|
}
|
||||||
authorName={currentMessage.nick}
|
authorName={currentMessage.nick}
|
||||||
date={formatDisplayDate(currentMessage)}
|
date={formatDisplayDate(currentMessage)}
|
||||||
>
|
>
|
||||||
<MessageText>{currentMessage.payloadAsUtf8}</MessageText>
|
<MessageText>{currentMessage.payloadAsUtf8}</MessageText>
|
||||||
</Message>
|
</LiveMessage>
|
||||||
))}
|
))}
|
||||||
</MessageGroup>
|
</MessageGroup>
|
||||||
));
|
));
|
||||||
|
@ -65,10 +68,10 @@ export default function ChatList(props: Props) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function groupMessagesBySender(messageArray: ChatMessage[]): ChatMessage[][] {
|
function groupMessagesBySender(messageArray: Message[]): Message[][] {
|
||||||
let currentSender = -1;
|
let currentSender = -1;
|
||||||
let lastNick = '';
|
let lastNick = '';
|
||||||
let messagesBySender: ChatMessage[][] = [];
|
let messagesBySender: Message[][] = [];
|
||||||
let currentSenderMessage = 0;
|
let currentSenderMessage = 0;
|
||||||
|
|
||||||
for (let currentMessage of messageArray) {
|
for (let currentMessage of messageArray) {
|
||||||
|
@ -83,7 +86,7 @@ function groupMessagesBySender(messageArray: ChatMessage[]): ChatMessage[][] {
|
||||||
return messagesBySender;
|
return messagesBySender;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatDisplayDate(message: ChatMessage): string {
|
function formatDisplayDate(message: Message): string {
|
||||||
return message.timestamp.toLocaleString([], {
|
return message.timestamp.toLocaleString([], {
|
||||||
month: 'short',
|
month: 'short',
|
||||||
day: 'numeric',
|
day: 'numeric',
|
||||||
|
@ -93,7 +96,7 @@ function formatDisplayDate(message: ChatMessage): string {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const AlwaysScrollToBottom = (props: { newMessages: ChatMessage[] }) => {
|
const AlwaysScrollToBottom = (props: { newMessages: Message[] }) => {
|
||||||
const elementRef = useRef<HTMLDivElement>();
|
const elementRef = useRef<HTMLDivElement>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -106,8 +109,8 @@ const AlwaysScrollToBottom = (props: { newMessages: ChatMessage[] }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
function IsThereNewMessages(
|
function IsThereNewMessages(
|
||||||
newValues: ChatMessage[],
|
newValues: Message[],
|
||||||
currentValues: ChatMessage[]
|
currentValues: Message[]
|
||||||
): boolean {
|
): boolean {
|
||||||
if (newValues.length === 0) return false;
|
if (newValues.length === 0) return false;
|
||||||
if (currentValues.length === 0) return true;
|
if (currentValues.length === 0) return true;
|
||||||
|
@ -118,8 +121,8 @@ function IsThereNewMessages(
|
||||||
}
|
}
|
||||||
|
|
||||||
function copyMergeUniqueReplace(
|
function copyMergeUniqueReplace(
|
||||||
newValues: ChatMessage[],
|
newValues: Message[],
|
||||||
currentValues: ChatMessage[]
|
currentValues: Message[]
|
||||||
) {
|
) {
|
||||||
const copy = currentValues.slice();
|
const copy = currentValues.slice();
|
||||||
newValues.forEach((msg) => {
|
newValues.forEach((msg) => {
|
||||||
|
@ -131,10 +134,11 @@ function copyMergeUniqueReplace(
|
||||||
return copy;
|
return copy;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isEqual(lhs: ChatMessage, rhs: ChatMessage): boolean {
|
function isEqual(lhs: Message, rhs: Message): boolean {
|
||||||
return (
|
return (
|
||||||
lhs.nick === rhs.nick &&
|
lhs.nick === rhs.nick &&
|
||||||
lhs.payloadAsUtf8 === rhs.payloadAsUtf8 &&
|
lhs.payloadAsUtf8 === rhs.payloadAsUtf8 &&
|
||||||
lhs.timestamp.toString() === rhs.timestamp.toString()
|
lhs.timestamp.valueOf() === rhs.timestamp.valueOf() &&
|
||||||
|
lhs.sentTimestamp?.valueOf() === rhs.sentTimestamp?.valueOf()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,10 +4,11 @@ import ChatList from './ChatList';
|
||||||
import MessageInput from './MessageInput';
|
import MessageInput from './MessageInput';
|
||||||
import { useWaku } from './WakuContext';
|
import { useWaku } from './WakuContext';
|
||||||
import { TitleBar } from '@livechat/ui-kit';
|
import { TitleBar } from '@livechat/ui-kit';
|
||||||
|
import { Message } from './Message';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
newMessages: ChatMessage[];
|
newMessages: Message[];
|
||||||
archivedMessages: ChatMessage[];
|
archivedMessages: Message[];
|
||||||
commandHandler: (cmd: string) => void;
|
commandHandler: (cmd: string) => void;
|
||||||
nick: string;
|
nick: string;
|
||||||
}
|
}
|
||||||
|
@ -52,10 +53,12 @@ async function handleMessage(
|
||||||
if (message.startsWith('/')) {
|
if (message.startsWith('/')) {
|
||||||
commandHandler(message);
|
commandHandler(message);
|
||||||
} else {
|
} else {
|
||||||
const chatMessage = ChatMessage.fromUtf8String(new Date(), nick, message);
|
const timestamp = new Date();
|
||||||
|
const chatMessage = ChatMessage.fromUtf8String(timestamp, nick, message);
|
||||||
const wakuMsg = WakuMessage.fromBytes(
|
const wakuMsg = WakuMessage.fromBytes(
|
||||||
chatMessage.encode(),
|
chatMessage.encode(),
|
||||||
ChatContentTopic
|
ChatContentTopic,
|
||||||
|
timestamp
|
||||||
);
|
);
|
||||||
return messageSender(wakuMsg);
|
return messageSender(wakuMsg);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue