This commit is contained in:
rosetta
2026-01-30 05:01:05 +02:00
commit 83f38dc63f
327 changed files with 18725 additions and 0 deletions

View 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>
)
}

View 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
};
}

View 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
};
}

View 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
];
}

View File

@@ -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;
}

View 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;
}

View 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;
}

View 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
]
}