Refactor useMessenger (#89)
This commit is contained in:
parent
255d1d30f9
commit
819b89562c
|
@ -2,7 +2,7 @@ import React, { useEffect, useMemo, useState } from "react";
|
|||
import styled from "styled-components";
|
||||
|
||||
import { useNarrow } from "../contexts/narrowProvider";
|
||||
import { useMessenger } from "../hooks/useMessenger";
|
||||
import { useMessenger } from "../hooks/messenger/useMessenger";
|
||||
import { ChannelData } from "../models/ChannelData";
|
||||
import { Metadata } from "../models/Metadata";
|
||||
import { Theme } from "../styles/themes";
|
||||
|
@ -37,7 +37,7 @@ export function Chat({ theme, communityKey, fetchMetadata }: ChatProps) {
|
|||
sendMessage,
|
||||
notifications,
|
||||
clearNotifications,
|
||||
loadNextDay,
|
||||
loadPrevDay,
|
||||
loadingMessages,
|
||||
community,
|
||||
} = useMessenger(activeChannel?.id ?? "", communityKey);
|
||||
|
@ -120,7 +120,7 @@ export function Chat({ theme, communityKey, fetchMetadata }: ChatProps) {
|
|||
showMembers={showMembers}
|
||||
community={communityData}
|
||||
showCommunity={!showChannels}
|
||||
loadNextDay={() => loadNextDay(activeChannel.name)}
|
||||
loadPrevDay={() => loadPrevDay(activeChannel.name)}
|
||||
onCommunityClick={showModal}
|
||||
fetchMetadata={fetchMetadata}
|
||||
loadingMessages={loadingMessages}
|
||||
|
|
|
@ -33,7 +33,7 @@ interface ChatBodyProps {
|
|||
notifications: { [id: string]: number };
|
||||
setActiveChannel: (val: ChannelData) => void;
|
||||
activeChannelId: string;
|
||||
loadNextDay: () => void;
|
||||
loadPrevDay: () => void;
|
||||
onCommunityClick: () => void;
|
||||
fetchMetadata?: (url: string) => Promise<Metadata | undefined>;
|
||||
loadingMessages: boolean;
|
||||
|
@ -54,7 +54,7 @@ export function ChatBody({
|
|||
notifications,
|
||||
setActiveChannel,
|
||||
activeChannelId,
|
||||
loadNextDay,
|
||||
loadPrevDay,
|
||||
onCommunityClick,
|
||||
fetchMetadata,
|
||||
loadingMessages,
|
||||
|
@ -131,7 +131,7 @@ export function ChatBody({
|
|||
messenger ? (
|
||||
<ChatMessages
|
||||
messages={messages}
|
||||
loadNextDay={loadNextDay}
|
||||
loadPrevDay={loadPrevDay}
|
||||
fetchMetadata={fetchMetadata}
|
||||
loadingMessages={loadingMessages}
|
||||
/>
|
||||
|
|
|
@ -13,14 +13,14 @@ import { ChatMessageContent } from "./ChatMessageContent";
|
|||
|
||||
type ChatMessagesProps = {
|
||||
messages: ChatMessage[];
|
||||
loadNextDay: () => void;
|
||||
loadPrevDay: () => void;
|
||||
fetchMetadata?: (url: string) => Promise<Metadata | undefined>;
|
||||
loadingMessages: boolean;
|
||||
};
|
||||
|
||||
export function ChatMessages({
|
||||
messages,
|
||||
loadNextDay,
|
||||
loadPrevDay,
|
||||
fetchMetadata,
|
||||
loadingMessages,
|
||||
}: ChatMessagesProps) {
|
||||
|
@ -38,7 +38,7 @@ export function ChatMessages({
|
|||
if (
|
||||
(ref?.current?.clientHeight ?? 0) >= (ref?.current?.scrollHeight ?? 0)
|
||||
) {
|
||||
loadNextDay();
|
||||
loadPrevDay();
|
||||
}
|
||||
}
|
||||
}, [messages, messages.length, loadingMessages]);
|
||||
|
@ -47,7 +47,7 @@ export function ChatMessages({
|
|||
const setScroll = () => {
|
||||
if (ref && ref.current) {
|
||||
if (ref.current.scrollTop <= 0) {
|
||||
loadNextDay();
|
||||
loadPrevDay();
|
||||
}
|
||||
if (
|
||||
ref.current.scrollTop + ref.current.clientHeight ==
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { Messenger } from "status-communities/dist/cjs";
|
||||
|
||||
const _MS_PER_DAY = 1000 * 60 * 60 * 24;
|
||||
|
||||
export function useLoadPrevDay(
|
||||
chatId: string,
|
||||
messenger: Messenger | undefined
|
||||
) {
|
||||
const loadingPreviousMessages = useRef<{
|
||||
[chatId: string]: boolean;
|
||||
}>({});
|
||||
const lastLoadTime = useRef<{
|
||||
[chatId: string]: Date;
|
||||
}>({});
|
||||
const [loadingMessages, setLoadingMessages] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setLoadingMessages(loadingPreviousMessages.current[chatId]);
|
||||
}, [chatId]);
|
||||
|
||||
const loadPrevDay = useCallback(
|
||||
async (id: string) => {
|
||||
if (messenger) {
|
||||
const endTime = lastLoadTime.current[id] ?? new Date();
|
||||
const startTime = new Date(endTime.getTime() - _MS_PER_DAY);
|
||||
const timeDiff = Math.floor(
|
||||
(new Date().getTime() - endTime.getTime()) / _MS_PER_DAY
|
||||
);
|
||||
if (timeDiff < 30) {
|
||||
if (!loadingPreviousMessages.current[id]) {
|
||||
loadingPreviousMessages.current[id] = true;
|
||||
setLoadingMessages(true);
|
||||
const amountOfMessages = await messenger.retrievePreviousMessages(
|
||||
id,
|
||||
startTime,
|
||||
endTime
|
||||
);
|
||||
lastLoadTime.current[id] = startTime;
|
||||
loadingPreviousMessages.current[id] = false;
|
||||
setLoadingMessages(false);
|
||||
if (amountOfMessages === 0) {
|
||||
loadPrevDay(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
[lastLoadTime, messenger]
|
||||
);
|
||||
return { loadingMessages, loadPrevDay };
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
import { useCallback, useMemo, useState } from "react";
|
||||
import { ApplicationMetadataMessage } from "status-communities/dist/cjs";
|
||||
|
||||
import { ChatMessage } from "../../models/ChatMessage";
|
||||
import { binarySetInsert } from "../../utils";
|
||||
|
||||
import { useNotifications } from "./useNotifications";
|
||||
|
||||
export function useMessages(chatId: string) {
|
||||
const [messages, setMessages] = useState<{ [chatId: string]: ChatMessage[] }>(
|
||||
{}
|
||||
);
|
||||
const { notifications, incNotification, clearNotifications } =
|
||||
useNotifications();
|
||||
|
||||
const addMessage = useCallback(
|
||||
(msg: ApplicationMetadataMessage, id: string, date: Date) => {
|
||||
const newMessage = ChatMessage.fromMetadataMessage(msg, date);
|
||||
if (newMessage) {
|
||||
setMessages((prev) => {
|
||||
return {
|
||||
...prev,
|
||||
[id]: binarySetInsert(
|
||||
prev?.[id] ?? [],
|
||||
newMessage,
|
||||
(a, b) => a.date < b.date,
|
||||
(a, b) => a.date.getTime() === b.date.getTime()
|
||||
),
|
||||
};
|
||||
});
|
||||
incNotification(id);
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const activeMessages = useMemo(
|
||||
() => messages?.[chatId] ?? [],
|
||||
[messages, chatId]
|
||||
);
|
||||
|
||||
return {
|
||||
messages: activeMessages,
|
||||
addMessage,
|
||||
notifications,
|
||||
clearNotifications,
|
||||
};
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
// import { StoreCodec } from "js-waku";
|
||||
import { StoreCodec } from "js-waku";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { Community, Identity, Messenger } from "status-communities/dist/cjs";
|
||||
|
||||
import { loadIdentity, saveIdentity } from "../../utils";
|
||||
|
||||
import { useLoadPrevDay } from "./useLoadPrevDay";
|
||||
import { useMessages } from "./useMessages";
|
||||
|
||||
export function useMessenger(chatId: string, communityKey: string) {
|
||||
const [messenger, setMessenger] = useState<Messenger | undefined>(undefined);
|
||||
const { addMessage, clearNotifications, notifications, messages } =
|
||||
useMessages(chatId);
|
||||
const [community, setCommunity] = useState<Community | undefined>(undefined);
|
||||
const { loadPrevDay, loadingMessages } = useLoadPrevDay(chatId, messenger);
|
||||
|
||||
useEffect(() => {
|
||||
const createMessenger = async () => {
|
||||
// Test password for now
|
||||
// Need design for password input
|
||||
|
||||
let identity = await loadIdentity("test");
|
||||
if (!identity) {
|
||||
identity = Identity.generate();
|
||||
await saveIdentity(identity, "test");
|
||||
}
|
||||
const messenger = await Messenger.create(identity, {
|
||||
libp2p: {
|
||||
config: {
|
||||
pubsub: {
|
||||
enabled: true,
|
||||
emitSelf: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
await new Promise((resolve) => {
|
||||
messenger.waku.libp2p.peerStore.on(
|
||||
"change:protocols",
|
||||
({ protocols }) => {
|
||||
if (protocols.includes(StoreCodec)) {
|
||||
resolve("");
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
const community = await Community.instantiateCommunity(
|
||||
communityKey,
|
||||
messenger.waku
|
||||
);
|
||||
setCommunity(community);
|
||||
await Promise.all(
|
||||
Array.from(community.chats.values()).map(async (chat) => {
|
||||
await messenger.joinChat(chat);
|
||||
messenger.addObserver(
|
||||
(msg, date) => addMessage(msg, chat.id, date),
|
||||
chat.id
|
||||
);
|
||||
clearNotifications(chat.id);
|
||||
})
|
||||
);
|
||||
setMessenger(messenger);
|
||||
};
|
||||
createMessenger();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (messenger && community?.chats) {
|
||||
Array.from(community?.chats.values()).forEach(({ id }) =>
|
||||
loadPrevDay(id)
|
||||
);
|
||||
}
|
||||
}, [messenger]);
|
||||
|
||||
const sendMessage = useCallback(
|
||||
async (messageText?: string, image?: Uint8Array) => {
|
||||
if (messageText) {
|
||||
await messenger?.sendMessage(chatId, {
|
||||
text: messageText,
|
||||
contentType: 0,
|
||||
});
|
||||
}
|
||||
if (image) {
|
||||
await messenger?.sendMessage(chatId, {
|
||||
image,
|
||||
imageType: 1,
|
||||
contentType: 2,
|
||||
});
|
||||
}
|
||||
},
|
||||
[chatId, messenger]
|
||||
);
|
||||
|
||||
return {
|
||||
messenger,
|
||||
messages,
|
||||
sendMessage,
|
||||
notifications,
|
||||
clearNotifications,
|
||||
loadPrevDay,
|
||||
loadingMessages,
|
||||
community,
|
||||
};
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
import { useCallback, useState } from "react";
|
||||
|
||||
export function useNotifications() {
|
||||
const [notifications, setNotifications] = useState<{
|
||||
[chatId: string]: number;
|
||||
}>({});
|
||||
const incNotification = useCallback((id: string) => {
|
||||
setNotifications((prevNotifications) => {
|
||||
return {
|
||||
...prevNotifications,
|
||||
[id]: prevNotifications[id] + 1,
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
const clearNotifications = useCallback((id: string) => {
|
||||
setNotifications((prevNotifications) => {
|
||||
return {
|
||||
...prevNotifications,
|
||||
[id]: 0,
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
return { notifications, incNotification, clearNotifications };
|
||||
}
|
|
@ -1,237 +0,0 @@
|
|||
// import { StoreCodec } from "js-waku";
|
||||
import { StoreCodec } from "js-waku";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { Community, Identity, Messenger } from "status-communities/dist/cjs";
|
||||
import { ApplicationMetadataMessage } from "status-communities/dist/cjs";
|
||||
|
||||
import { ChatMessage } from "../models/ChatMessage";
|
||||
import { loadIdentity, saveIdentity } from "../utils/identityStorage";
|
||||
import { uintToImgUrl } from "../utils/uintToImgUrl";
|
||||
|
||||
const _MS_PER_DAY = 1000 * 60 * 60 * 24;
|
||||
|
||||
function binarySetInsert<T>(
|
||||
arr: T[],
|
||||
val: T,
|
||||
compFunc: (a: T, b: T) => boolean,
|
||||
eqFunc: (a: T, b: T) => boolean
|
||||
) {
|
||||
let low = 0;
|
||||
let high = arr.length;
|
||||
while (low < high) {
|
||||
const mid = (low + high) >> 1;
|
||||
if (compFunc(arr[mid], val)) {
|
||||
low = mid + 1;
|
||||
} else {
|
||||
high = mid;
|
||||
}
|
||||
}
|
||||
if (arr.length === low || !eqFunc(arr[low], val)) {
|
||||
arr.splice(low, 0, val);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
export function useMessenger(chatId: string, communityKey: string) {
|
||||
const [messenger, setMessenger] = useState<Messenger | undefined>(undefined);
|
||||
const [messages, setMessages] = useState<{ [chatId: string]: ChatMessage[] }>(
|
||||
{}
|
||||
);
|
||||
const [community, setCommunity] = useState<Community | undefined>(undefined);
|
||||
const [notifications, setNotifications] = useState<{
|
||||
[chatId: string]: number;
|
||||
}>({});
|
||||
const loadingPreviousMessages = useRef<{
|
||||
[chatId: string]: boolean;
|
||||
}>({});
|
||||
const lastLoadTime = useRef<{
|
||||
[chatId: string]: Date;
|
||||
}>({});
|
||||
const [lastMessage, setLastMessage] = useState(new Date());
|
||||
const [loadingMessages, setLoadingMessages] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (lastLoadTime.current?.[chatId]) {
|
||||
setLastMessage(lastLoadTime.current?.[chatId]);
|
||||
}
|
||||
}, [chatId]);
|
||||
const clearNotifications = useCallback((id: string) => {
|
||||
setNotifications((prevNotifications) => {
|
||||
return {
|
||||
...prevNotifications,
|
||||
[id]: 0,
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
|
||||
const addNewMessage = useCallback(
|
||||
(msg: ApplicationMetadataMessage, id: string, date: Date) => {
|
||||
if (
|
||||
msg.signer &&
|
||||
(msg.chatMessage?.text || msg.chatMessage?.image) &&
|
||||
msg.chatMessage.clock
|
||||
) {
|
||||
const content = msg.chatMessage.text ?? "";
|
||||
let image: string | undefined = undefined;
|
||||
if (msg.chatMessage?.image) {
|
||||
image = uintToImgUrl(msg.chatMessage?.image.payload);
|
||||
}
|
||||
const sender = msg.signer.reduce(
|
||||
(p: string, c: number): string => p + c.toString(16),
|
||||
"0x"
|
||||
);
|
||||
const newMessage = { sender, content, date, image };
|
||||
setMessages((prev) => {
|
||||
return {
|
||||
...prev,
|
||||
[id]: binarySetInsert(
|
||||
prev?.[id] ?? [],
|
||||
newMessage,
|
||||
(a, b) => a.date < b.date,
|
||||
(a, b) => a.date.getTime() === b.date.getTime()
|
||||
),
|
||||
};
|
||||
});
|
||||
setNotifications((prev) => {
|
||||
return {
|
||||
...prev,
|
||||
[id]: prev[id] + 1,
|
||||
};
|
||||
});
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const loadNextDay = useCallback(
|
||||
async (id: string) => {
|
||||
if (messenger) {
|
||||
const endTime = lastLoadTime.current[id] ?? new Date();
|
||||
const startTime = new Date(endTime.getTime() - _MS_PER_DAY);
|
||||
const timeDiff = Math.floor(
|
||||
(new Date().getTime() - endTime.getTime()) / _MS_PER_DAY
|
||||
);
|
||||
if (timeDiff < 30) {
|
||||
if (!loadingPreviousMessages.current[id]) {
|
||||
loadingPreviousMessages.current[id] = true;
|
||||
setLoadingMessages(true);
|
||||
const amountOfMessages = await messenger.retrievePreviousMessages(
|
||||
id,
|
||||
startTime,
|
||||
endTime
|
||||
);
|
||||
lastLoadTime.current[id] = startTime;
|
||||
if (id === chatId) {
|
||||
setLastMessage(startTime);
|
||||
}
|
||||
loadingPreviousMessages.current[id] = false;
|
||||
setLoadingMessages(false);
|
||||
if (amountOfMessages === 0) {
|
||||
loadNextDay(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
[lastLoadTime, messenger, chatId]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const createMessenger = async () => {
|
||||
// Test password for now
|
||||
// Need design for password input
|
||||
|
||||
let identity = await loadIdentity("test");
|
||||
if (!identity) {
|
||||
identity = Identity.generate();
|
||||
await saveIdentity(identity, "test");
|
||||
}
|
||||
const messenger = await Messenger.create(identity, {
|
||||
libp2p: {
|
||||
config: {
|
||||
pubsub: {
|
||||
enabled: true,
|
||||
emitSelf: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
await new Promise((resolve) => {
|
||||
messenger.waku.libp2p.peerStore.on(
|
||||
"change:protocols",
|
||||
({ protocols }) => {
|
||||
if (protocols.includes(StoreCodec)) {
|
||||
resolve("");
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
const community = await Community.instantiateCommunity(
|
||||
communityKey,
|
||||
messenger.waku
|
||||
);
|
||||
setCommunity(community);
|
||||
await Promise.all(
|
||||
Array.from(community.chats.values()).map(async (chat) => {
|
||||
await messenger.joinChat(chat);
|
||||
lastLoadTime.current[chat.id] = new Date();
|
||||
messenger.addObserver(
|
||||
(msg, date) => addNewMessage(msg, chat.id, date),
|
||||
chat.id
|
||||
);
|
||||
clearNotifications(chat.id);
|
||||
})
|
||||
);
|
||||
setMessenger(messenger);
|
||||
};
|
||||
createMessenger();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (messenger && community?.chats) {
|
||||
Array.from(community?.chats.values()).forEach(({ id }) =>
|
||||
loadNextDay(id)
|
||||
);
|
||||
}
|
||||
}, [messenger]);
|
||||
|
||||
const sendMessage = useCallback(
|
||||
async (messageText?: string, image?: Uint8Array) => {
|
||||
if (messageText) {
|
||||
await messenger?.sendMessage(chatId, {
|
||||
text: messageText,
|
||||
contentType: 0,
|
||||
});
|
||||
}
|
||||
if (image) {
|
||||
await messenger?.sendMessage(chatId, {
|
||||
image,
|
||||
imageType: 1,
|
||||
contentType: 2,
|
||||
});
|
||||
}
|
||||
},
|
||||
[chatId, messenger]
|
||||
);
|
||||
|
||||
const activeMessages = useMemo(
|
||||
() => messages?.[chatId] ?? [],
|
||||
[messages, chatId]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setLoadingMessages(loadingPreviousMessages.current[chatId]);
|
||||
}, [chatId]);
|
||||
|
||||
return {
|
||||
messenger,
|
||||
messages: activeMessages,
|
||||
sendMessage,
|
||||
notifications,
|
||||
clearNotifications,
|
||||
loadNextDay,
|
||||
lastMessage,
|
||||
loadingMessages,
|
||||
community,
|
||||
};
|
||||
}
|
|
@ -1,6 +1,41 @@
|
|||
export type ChatMessage = {
|
||||
import { ApplicationMetadataMessage } from "status-communities/dist/cjs";
|
||||
|
||||
import { uintToImgUrl } from "../utils";
|
||||
|
||||
export class ChatMessage {
|
||||
content: string;
|
||||
date: Date;
|
||||
sender: string;
|
||||
image?: string;
|
||||
};
|
||||
|
||||
constructor(content: string, date: Date, sender: string, image?: string) {
|
||||
this.content = content;
|
||||
this.date = date;
|
||||
this.sender = sender;
|
||||
this.image = image;
|
||||
}
|
||||
|
||||
public static fromMetadataMessage(
|
||||
msg: ApplicationMetadataMessage,
|
||||
date: Date
|
||||
) {
|
||||
if (
|
||||
msg.signer &&
|
||||
(msg.chatMessage?.text || msg.chatMessage?.image) &&
|
||||
msg.chatMessage.clock
|
||||
) {
|
||||
const content = msg.chatMessage.text ?? "";
|
||||
let image: string | undefined = undefined;
|
||||
if (msg.chatMessage?.image) {
|
||||
image = uintToImgUrl(msg.chatMessage?.image.payload);
|
||||
}
|
||||
const sender = msg.signer.reduce(
|
||||
(p: string, c: number): string => p + c.toString(16),
|
||||
"0x"
|
||||
);
|
||||
return new ChatMessage(content, date, sender, image);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
export function binarySetInsert<T>(
|
||||
arr: T[],
|
||||
val: T,
|
||||
compFunc: (a: T, b: T) => boolean,
|
||||
eqFunc: (a: T, b: T) => boolean
|
||||
) {
|
||||
let low = 0;
|
||||
let high = arr.length;
|
||||
while (low < high) {
|
||||
const mid = (low + high) >> 1;
|
||||
if (compFunc(arr[mid], val)) {
|
||||
low = mid + 1;
|
||||
} else {
|
||||
high = mid;
|
||||
}
|
||||
}
|
||||
if (arr.length === low || !eqFunc(arr[low], val)) {
|
||||
arr.splice(low, 0, val);
|
||||
}
|
||||
return arr;
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
export { binarySetInsert } from "./binarySetInsert";
|
||||
export { copy } from "./copy";
|
||||
export { copyImg } from "./copyImg";
|
||||
export { downloadImg } from "./downloadImg";
|
||||
export { saveIdentity, loadIdentity } from "./identityStorage";
|
||||
export { reduceString } from "./reduceString";
|
||||
export { uintToImgUrl } from "./uintToImgUrl";
|
Loading…
Reference in New Issue