'init'
This commit is contained in:
171
app/providers/InformationProvider/InformationProvider.tsx
Normal file
171
app/providers/InformationProvider/InformationProvider.tsx
Normal file
@@ -0,0 +1,171 @@
|
||||
import { useDatabase } from "@/app/providers/DatabaseProvider/useDatabase";
|
||||
import { OnlineState, PacketOnlineState, PublicKeyOnlineState } from "@/app/providers/ProtocolProvider/protocol/packets/packet.onlinestate";
|
||||
import { createContext, useEffect, useState } from "react";
|
||||
import { usePacket } from "../ProtocolProvider/usePacket";
|
||||
import { useConsoleLogger } from "@/app/hooks/useConsoleLogger";
|
||||
import { useSystemAccounts } from "../SystemAccountsProvider/useSystemAccounts";
|
||||
import { usePublicKey } from "../AccountProvider/usePublicKey";
|
||||
|
||||
export const InformationContext = createContext<any>({});
|
||||
|
||||
interface InformationProviderProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export interface UserInformation {
|
||||
publicKey: string;
|
||||
verified: number;
|
||||
title: string;
|
||||
username: string;
|
||||
online: OnlineState;
|
||||
}
|
||||
|
||||
export interface GroupInformation {
|
||||
groupId: string;
|
||||
title: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export function InformationProvider(props: InformationProviderProps) {
|
||||
const [cachedUsers, setCachedUsers] = useState<UserInformation[]>([]);
|
||||
const [cachedGroups, setCachedGroups] = useState<GroupInformation[]>([]);
|
||||
const {allQuery, getQuery, runQuery} = useDatabase();
|
||||
const {info} = useConsoleLogger("InformationProvider");
|
||||
const systemAccounts = useSystemAccounts();
|
||||
const publicKey = usePublicKey();
|
||||
|
||||
useEffect(() => {
|
||||
loadCachedUsers();
|
||||
loadCachedGroups();
|
||||
}, [publicKey]);
|
||||
|
||||
usePacket(0x5, (state: PacketOnlineState) => {
|
||||
const keys = state.getPublicKeysState();
|
||||
keys.map((value : PublicKeyOnlineState) => {
|
||||
const cachedUser = cachedUsers.find((userInfo) => userInfo.publicKey == value.publicKey);
|
||||
if(!cachedUser) {
|
||||
info(`No cached user found for public key: ${value.publicKey}, info not updated`);
|
||||
return;
|
||||
}
|
||||
updateUserInformation({
|
||||
...cachedUser,
|
||||
online: value.state
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
const loadCachedGroups = () => {
|
||||
if(publicKey == ''){
|
||||
return;
|
||||
}
|
||||
const result = allQuery("SELECT * FROM groups WHERE account = ?", [publicKey]);
|
||||
result.then((rows) => {
|
||||
const infos : GroupInformation[] = [];
|
||||
for(let i = 0; i < rows.length; i++) {
|
||||
infos.push({
|
||||
groupId: rows[i].group_id,
|
||||
title: rows[i].title,
|
||||
description: rows[i].description,
|
||||
});
|
||||
}
|
||||
setCachedGroups(infos);
|
||||
});
|
||||
}
|
||||
|
||||
const loadCachedUsers = () => {
|
||||
const result = allQuery("SELECT * FROM cached_users", []);
|
||||
result.then((rows) => {
|
||||
const infos : UserInformation[] = [];
|
||||
for(let i = 0; i < rows.length; i++) {
|
||||
infos.push({
|
||||
publicKey: rows[i].public_key,
|
||||
verified: rows[i].verified,
|
||||
title: rows[i].title,
|
||||
username: rows[i].username,
|
||||
online: publicKey == rows[i].public_key ? OnlineState.ONLINE : OnlineState.OFFLINE
|
||||
});
|
||||
}
|
||||
infos.push(...systemAccounts);
|
||||
setCachedUsers(infos);
|
||||
});
|
||||
}
|
||||
|
||||
const updateGroupInformation = async (groupInfo : GroupInformation) => {
|
||||
const result = await getQuery("SELECT COUNT(*) as count FROM groups WHERE account = ? AND group_id = ?", [publicKey, groupInfo.groupId]);
|
||||
if(result.count > 0){
|
||||
/**
|
||||
* Обрабатываем только событие если строка в базе уже есть,
|
||||
* потому что добавление строки мы отрабатываем в другом месте
|
||||
*/
|
||||
await runQuery(`UPDATE groups SET title = ?, description = ? WHERE account = ? AND group_id = ?`,
|
||||
[groupInfo.title, groupInfo.description, publicKey, groupInfo.groupId]);
|
||||
}
|
||||
|
||||
if(cachedGroups.find((v) => v.groupId == groupInfo.groupId)){
|
||||
setCachedGroups((prev) => prev.map((group) => {
|
||||
if (group.groupId == groupInfo.groupId) {
|
||||
return {
|
||||
...group,
|
||||
title: groupInfo.title,
|
||||
description: groupInfo.description
|
||||
}
|
||||
}
|
||||
return group;
|
||||
}));
|
||||
}else{
|
||||
setCachedGroups((prev) => ([
|
||||
...prev,
|
||||
{
|
||||
groupId: groupInfo.groupId,
|
||||
title: groupInfo.title,
|
||||
description: groupInfo.description
|
||||
}
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
const updateUserInformation = async (userInfo : UserInformation) => {
|
||||
const result = await getQuery("SELECT COUNT(*) as count FROM cached_users WHERE public_key = ?", [userInfo.publicKey]);
|
||||
if (result.count > 0) {
|
||||
await runQuery(`UPDATE cached_users SET title = ?, username = ?, verified = ? WHERE public_key = ?`, [userInfo.title, userInfo.username, userInfo.verified, userInfo.publicKey]);
|
||||
setCachedUsers((prev) => prev.map((user) => {
|
||||
if (user.publicKey == userInfo.publicKey) {
|
||||
return {
|
||||
...user,
|
||||
verified: userInfo.verified,
|
||||
title: userInfo.title,
|
||||
username: userInfo.username,
|
||||
online: userInfo.online,
|
||||
}
|
||||
}
|
||||
return user;
|
||||
}));
|
||||
} else {
|
||||
await runQuery(`INSERT INTO cached_users (public_key, title, username, verified) VALUES (?, ?, ?, ?)`,
|
||||
[userInfo.publicKey, userInfo.title, userInfo.username, userInfo.verified]);
|
||||
setCachedUsers((prev) => ([
|
||||
...prev,
|
||||
{
|
||||
publicKey: userInfo.publicKey,
|
||||
title: userInfo.title,
|
||||
username: userInfo.username,
|
||||
online: OnlineState.OFFLINE,
|
||||
verified: userInfo.verified
|
||||
}
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<InformationContext.Provider value={{
|
||||
cachedUsers,
|
||||
cachedGroups,
|
||||
setCachedUsers,
|
||||
updateUserInformation,
|
||||
updateGroupInformation
|
||||
}}>
|
||||
{props.children}
|
||||
</InformationContext.Provider>
|
||||
)
|
||||
}
|
||||
38
app/providers/InformationProvider/useGroupInformation.ts
Normal file
38
app/providers/InformationProvider/useGroupInformation.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { useContext } from "react";
|
||||
import { GroupInformation, InformationContext } from "./InformationProvider";
|
||||
|
||||
export function useGroupInformation(groupId: string) : {
|
||||
groupInfo: GroupInformation,
|
||||
markAsDeleted: () => void,
|
||||
updateGroupInformation: (groupInfo: GroupInformation) => void
|
||||
} {
|
||||
const context = useContext(InformationContext);
|
||||
const {cachedGroups, updateGroupInformation} = context;
|
||||
const group : GroupInformation = cachedGroups.find((group: GroupInformation) => group.groupId == groupId.replace("#group:", ""));
|
||||
|
||||
if(!context || !context.cachedGroups) {
|
||||
throw new Error("useGroupInformation must be used within a InformationProvider");
|
||||
}
|
||||
|
||||
if(groupId.trim() == ""){
|
||||
throw new Error("Empty string passed to groupId with useGroupInformation hook");
|
||||
}
|
||||
|
||||
const markAsDeleted = () => {
|
||||
updateGroupInformation({
|
||||
groupId: groupId,
|
||||
title: "DELETED",
|
||||
description: "No description available."
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
groupInfo: {
|
||||
groupId: groupId,
|
||||
title: group ? group.title : "DELETED",
|
||||
description: group ? group.description : "No description available."
|
||||
},
|
||||
markAsDeleted,
|
||||
updateGroupInformation
|
||||
};
|
||||
}
|
||||
69
app/providers/InformationProvider/useGroupMembers.ts
Normal file
69
app/providers/InformationProvider/useGroupMembers.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useGroupInformation } from "./useGroupInformation";
|
||||
import { PacketGroupInfo } from "../ProtocolProvider/protocol/packets/packet.group.info";
|
||||
import { usePacket } from "../ProtocolProvider/usePacket";
|
||||
import { useSender } from "../ProtocolProvider/useSender";
|
||||
import { useGroups } from "../DialogProvider/useGroups";
|
||||
import { useMemory } from "../MemoryProvider/useMemory";
|
||||
|
||||
/**
|
||||
* Хук для получения участников группы
|
||||
* @param groupId ид группы
|
||||
* @param force принудительное обновление, если true, то будет
|
||||
* отправлен запрос на сервер и получен актуальный список участников,
|
||||
* если false, то будет возвращено значение из памяти
|
||||
* @returns
|
||||
*/
|
||||
export function useGroupMembers(groupId: string, force?: boolean) : {
|
||||
members: string[];
|
||||
loading: boolean;
|
||||
} {
|
||||
const send = useSender();
|
||||
const {normalize, hasGroup} = useGroups();
|
||||
const {markAsDeleted} = useGroupInformation(normalize(groupId));
|
||||
const [members, setMembers] = useMemory<string[]>("members_group_" + groupId, [], true);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
updateGroupMembers();
|
||||
}, [groupId]);
|
||||
|
||||
const updateGroupMembers = () => {
|
||||
if((!hasGroup(groupId) && groupId.length > 16)
|
||||
|| (members.length > 0 && !force)
|
||||
){
|
||||
/**
|
||||
* Не ID группы, пропускаем. Если ид группы больше 16 символов
|
||||
* и не начинается с #group:, то это не группа.
|
||||
* Однако если ID меньше 16 символов, то это и не
|
||||
* публичный ключ. Значит скорее всего это ID группы.
|
||||
*
|
||||
* Это условие нужно для оптимизации запросов на сервер.
|
||||
*/
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
let packet = new PacketGroupInfo();
|
||||
packet.setGroupId(normalize(groupId));
|
||||
send(packet);
|
||||
}
|
||||
|
||||
usePacket(0x12, (packet: PacketGroupInfo) => {
|
||||
if(packet.getGroupId() != normalize(groupId)){
|
||||
return;
|
||||
}
|
||||
const members = packet.getMembers();
|
||||
if(members.length <= 0){
|
||||
setLoading(false);
|
||||
markAsDeleted();
|
||||
return;
|
||||
}
|
||||
setLoading(false);
|
||||
setMembers(members);
|
||||
}, [groupId]);
|
||||
|
||||
return {
|
||||
members,
|
||||
loading
|
||||
};
|
||||
}
|
||||
31
app/providers/InformationProvider/useSearch.ts
Normal file
31
app/providers/InformationProvider/useSearch.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { useState } from "react";
|
||||
import { useProtocol } from "../ProtocolProvider/useProtocol";
|
||||
import { PacketSearch, PacketSearchUser } from "../ProtocolProvider/protocol/packets/packet.search";
|
||||
import { usePrivateKeyHash } from "../AccountProvider/usePrivateKeyHash";
|
||||
|
||||
export function useSearch() : [
|
||||
PacketSearchUser[],
|
||||
(username : string) => void,
|
||||
React.Dispatch<React.SetStateAction<PacketSearchUser[]>>
|
||||
] {
|
||||
const {protocol} = useProtocol();
|
||||
const [searchResults, setSearchResults] = useState<PacketSearchUser[]>([]);
|
||||
const privateKeyHash = usePrivateKeyHash();
|
||||
|
||||
protocol.waitPacketOnce(0x03, (packet : PacketSearch) => {
|
||||
setSearchResults(packet.getUsers());
|
||||
});
|
||||
|
||||
const search = (username : string) => {
|
||||
let packet = new PacketSearch();
|
||||
packet.setSearch(username);
|
||||
packet.setPrivateKey(privateKeyHash);
|
||||
protocol.sendPacket(packet);
|
||||
}
|
||||
|
||||
return [
|
||||
searchResults,
|
||||
search,
|
||||
setSearchResults
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { useContext } from "react";
|
||||
import { GroupInformation, InformationContext } from "./InformationProvider";
|
||||
|
||||
export function useUpdateGroupInformation() : (groupInfo: GroupInformation) => void {
|
||||
const context = useContext(InformationContext);
|
||||
const {updateGroupInformation} = context;
|
||||
|
||||
if(!context || !context.cachedGroups) {
|
||||
throw new Error("useUpdateGroupInformation must be used within a InformationProvider");
|
||||
}
|
||||
|
||||
return updateGroupInformation;
|
||||
}
|
||||
36
app/providers/InformationProvider/useUserCache.ts
Normal file
36
app/providers/InformationProvider/useUserCache.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { useDatabase } from "@/app/providers/DatabaseProvider/useDatabase";
|
||||
import { useEffect, useState } from "react";
|
||||
import { UserInformation } from "./InformationProvider";
|
||||
import { OnlineState } from "@/app/providers/ProtocolProvider/protocol/packets/packet.onlinestate";
|
||||
|
||||
/**
|
||||
* Информация запрашивается только из кэша, и в отличии от вызова
|
||||
* хука useUserInformation не использует подгрузку с сервера,
|
||||
* то есть если в кэше информации нет то вернет пустое значение
|
||||
* @param publicKey публичный ключ
|
||||
*/
|
||||
export function useUserCache(publicKey : string) {
|
||||
const {getQuery} = useDatabase();
|
||||
const [userInfo, setUserInfo] =
|
||||
useState<UserInformation|undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
loadFromCacheDatabase();
|
||||
}, [publicKey]);
|
||||
|
||||
const loadFromCacheDatabase = async () => {
|
||||
const result = await getQuery("SELECT * FROM `cached_users` WHERE `public_key` = ?", [publicKey]);
|
||||
if(!result){
|
||||
return;
|
||||
}
|
||||
setUserInfo({
|
||||
publicKey: result.public_key,
|
||||
verified: result.verified,
|
||||
title: result.title,
|
||||
username: result.username,
|
||||
online: OnlineState.OFFLINE
|
||||
});
|
||||
}
|
||||
|
||||
return userInfo;
|
||||
}
|
||||
23
app/providers/InformationProvider/useUserCacheFunc.ts
Normal file
23
app/providers/InformationProvider/useUserCacheFunc.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { useDatabase } from "@/app/providers/DatabaseProvider/useDatabase";
|
||||
import { UserInformation } from "./InformationProvider";
|
||||
import { OnlineState } from "../ProtocolProvider/protocol/packets/packet.onlinestate";
|
||||
|
||||
export function useUserCacheFunc() {
|
||||
const {getQuery} = useDatabase();
|
||||
|
||||
const getUserInformation = async (publicKey: string): Promise<UserInformation | null> => {
|
||||
const result = await getQuery("SELECT * FROM `cached_users` WHERE `public_key` = ?", [publicKey]);
|
||||
if(!result){
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
publicKey: result.public_key,
|
||||
verified: result.verified,
|
||||
title: result.title,
|
||||
username: result.username,
|
||||
online: OnlineState.OFFLINE
|
||||
};
|
||||
}
|
||||
|
||||
return getUserInformation;
|
||||
}
|
||||
132
app/providers/InformationProvider/useUserInformation.ts
Normal file
132
app/providers/InformationProvider/useUserInformation.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { InformationContext, UserInformation } from "./InformationProvider";
|
||||
import { PacketOnlineSubscribe } from "@/app/providers/ProtocolProvider/protocol/packets/packet.onlinesubscribe";
|
||||
import { useMemory } from "../MemoryProvider/useMemory";
|
||||
import { OnlineState } from "@/app/providers/ProtocolProvider/protocol/packets/packet.onlinestate";
|
||||
import { PacketSearch } from "@/app/providers/ProtocolProvider/protocol/packets/packet.search";
|
||||
import { usePrivateKeyHash } from "../AccountProvider/usePrivateKeyHash";
|
||||
import { useSender } from "../ProtocolProvider/useSender";
|
||||
import { usePacket } from "../ProtocolProvider/usePacket";
|
||||
import { usePublicKey } from "../AccountProvider/usePublicKey";
|
||||
import { useBlacklist } from "../BlacklistProvider/useBlacklist";
|
||||
import { useConsoleLogger } from "@/app/hooks/useConsoleLogger";
|
||||
import { useSystemAccounts } from "../SystemAccountsProvider/useSystemAccounts";
|
||||
|
||||
export function useUserInformation(publicKey: string) : [
|
||||
UserInformation,
|
||||
(userInfo: UserInformation) => Promise<void>,
|
||||
() => void,
|
||||
boolean
|
||||
] {
|
||||
const context = useContext(InformationContext);
|
||||
const send = useSender();
|
||||
const privateKey = usePrivateKeyHash();
|
||||
const [onlineSubscribes, setOnlineSubscribes] = useMemory<string[]>("online_subscribes", [], true);
|
||||
const {cachedUsers, updateUserInformation} = context;
|
||||
const user : UserInformation = cachedUsers.find((user: UserInformation) => user.publicKey == publicKey);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const ownPublicKey = usePublicKey();
|
||||
const [blocked] = useBlacklist(publicKey);
|
||||
const {info, warn} = useConsoleLogger('useUserInformation');
|
||||
const systemAccounts = useSystemAccounts();
|
||||
|
||||
if(!context || !context.cachedUsers) {
|
||||
throw new Error("useUserInformation must be used within a InformationProvider");
|
||||
}
|
||||
|
||||
const forceUpdateUserInformation = () => {
|
||||
if(publicKey.indexOf("#group:") !== -1){
|
||||
/**
|
||||
* This is group, only users can be force updated
|
||||
*/
|
||||
info("Force update skipped for group " + publicKey);
|
||||
return;
|
||||
}
|
||||
if(systemAccounts.find((acc) => acc.publicKey == publicKey)){
|
||||
/**
|
||||
* System account has no updates, its hardcoded display
|
||||
* name and user name
|
||||
*/
|
||||
info("System account not need force update");
|
||||
return;
|
||||
}
|
||||
if(blocked){
|
||||
warn("User is blocked, no force update " + publicKey);
|
||||
return;
|
||||
}
|
||||
warn("Force update " + publicKey);
|
||||
let packetSearch = new PacketSearch();
|
||||
packetSearch.setSearch(publicKey);
|
||||
packetSearch.setPrivateKey(privateKey);
|
||||
send(packetSearch);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
/**
|
||||
* Подписываемся на статус пользователя онлайн или не онлайн
|
||||
* если еще не подписаны
|
||||
*/
|
||||
if(onlineSubscribes.indexOf(publicKey) !== -1
|
||||
|| publicKey.indexOf("#group:") !== -1
|
||||
|| publicKey == ownPublicKey
|
||||
|| publicKey.trim() == ''
|
||||
|| blocked){
|
||||
/**
|
||||
* Уже подписаны на онлайн статус этого пользователя или это группа
|
||||
*/
|
||||
return;
|
||||
}
|
||||
let subscribePacket = new PacketOnlineSubscribe();
|
||||
subscribePacket.setPrivateKey(privateKey);
|
||||
subscribePacket.addPublicKey(publicKey);
|
||||
send(subscribePacket);
|
||||
setOnlineSubscribes((prev) => [...prev, publicKey]);
|
||||
}, [blocked]);
|
||||
|
||||
useEffect(() => {
|
||||
if(user || publicKey.trim() == ''){
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
let packetSearch = new PacketSearch();
|
||||
packetSearch.setSearch(publicKey);
|
||||
packetSearch.setPrivateKey(privateKey);
|
||||
send(packetSearch);
|
||||
}, [publicKey, privateKey, user]);
|
||||
|
||||
usePacket(0x03, (packet : PacketSearch) => {
|
||||
const users = packet.getUsers();
|
||||
if (users.length > 0 && users[0].publicKey == publicKey) {
|
||||
if( user &&
|
||||
user.username == users[0].username &&
|
||||
user.verified == users[0].verified &&
|
||||
user.title == users[0].title
|
||||
){
|
||||
/**
|
||||
* No update readed from server, stop rerender
|
||||
*/
|
||||
return;
|
||||
}
|
||||
setLoading(false);
|
||||
updateUserInformation({
|
||||
publicKey: users[0].publicKey,
|
||||
avatar: "", // No avatar in search packet
|
||||
username: users[0].username,
|
||||
title: users[0].title,
|
||||
online: users[0].online,
|
||||
verified: users[0].verified
|
||||
});
|
||||
}
|
||||
}, [publicKey, privateKey]);
|
||||
|
||||
return [
|
||||
{
|
||||
title: user ? user.title : "DELETED",
|
||||
username: user ? user.username : "",
|
||||
publicKey: user ? user.publicKey : "",
|
||||
online: user ? user.online : OnlineState.OFFLINE,
|
||||
verified: user ? user.verified : 0
|
||||
}, updateUserInformation, forceUpdateUserInformation, loading
|
||||
]
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user