132 lines
No EOL
3.7 KiB
TypeScript
132 lines
No EOL
3.7 KiB
TypeScript
import * as React from "react";
|
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
import { useRouterState } from "@tanstack/react-router";
|
|
import type { RawMessage, RawMessages } from "./values";
|
|
import { useCrypto } from "@tensamin/crypto/context";
|
|
import { useUser } from "@tensamin/user/context";
|
|
import { useStorage } from "@tensamin/storage/context";
|
|
import { useSocket } from "@tensamin/ttp/context";
|
|
|
|
export const context = React.createContext<contextType | undefined>(undefined);
|
|
|
|
const queryClient = new QueryClient();
|
|
|
|
export default function Provider(
|
|
props: { children: React.ReactNode },
|
|
) {
|
|
const { get_shared_secret } = useCrypto();
|
|
const { get } = useUser();
|
|
const { load } = useStorage();
|
|
const { send } = useSocket();
|
|
|
|
const [liveMessagesState, setLiveMessagesState] = React.useState<RawMessages>([]);
|
|
const [currentSharedSecret, setCurrentSharedSecret] = React.useState("");
|
|
|
|
const locationSearch = useRouterState({
|
|
select: (state) => state.location.search,
|
|
});
|
|
|
|
const userIdValue = React.useMemo(() => {
|
|
const rawId = (locationSearch as unknown as { id?: unknown })?.id;
|
|
return Number(rawId ?? 0);
|
|
}, [locationSearch]);
|
|
|
|
React.useEffect(() => {
|
|
const recipientId = userIdValue;
|
|
|
|
if (!recipientId) {
|
|
setCurrentSharedSecret("");
|
|
return;
|
|
}
|
|
|
|
let active = true;
|
|
|
|
void (async () => {
|
|
try {
|
|
const recipientData = await get(recipientId);
|
|
const ownId = await load("user_id");
|
|
const privateKey = await load("private_key");
|
|
const ownData = await get(ownId);
|
|
const sharedSecret = await get_shared_secret(
|
|
privateKey,
|
|
ownData.public_key,
|
|
recipientData.public_key,
|
|
);
|
|
|
|
if (active) {
|
|
setCurrentSharedSecret(sharedSecret);
|
|
}
|
|
} catch {
|
|
if (active) {
|
|
setCurrentSharedSecret("");
|
|
}
|
|
}
|
|
})();
|
|
|
|
return () => {
|
|
active = false;
|
|
};
|
|
}, [get, get_shared_secret, load, userIdValue]);
|
|
|
|
const customGetMessages = React.useCallback(
|
|
async (amount: number, offset: number) => {
|
|
const messages = await send("messages_get", {
|
|
amount,
|
|
offset,
|
|
user_id: userIdValue,
|
|
});
|
|
|
|
if (messages.type.startsWith("error")) {
|
|
throw new Error(messages.type);
|
|
}
|
|
|
|
const rawMessages = messages.data.messages;
|
|
const sorted = [...rawMessages].sort((a, b) => a.timestamp - b.timestamp);
|
|
return sorted;
|
|
},
|
|
[send, userIdValue],
|
|
);
|
|
|
|
const addLiveMessage = React.useCallback((message: RawMessage) => {
|
|
setLiveMessagesState((prev) => [...prev, message]);
|
|
}, []);
|
|
|
|
const clearLiveMessages = React.useCallback(() => {
|
|
setLiveMessagesState([]);
|
|
}, []);
|
|
|
|
const value = React.useMemo<contextType>(
|
|
() => ({
|
|
getMessages: customGetMessages,
|
|
liveMessages: () => liveMessagesState,
|
|
addLiveMessage,
|
|
clearLiveMessages,
|
|
sharedSecret: () => currentSharedSecret,
|
|
userId: () => userIdValue,
|
|
}),
|
|
[addLiveMessage, clearLiveMessages, currentSharedSecret, customGetMessages, liveMessagesState, userIdValue],
|
|
);
|
|
|
|
return (
|
|
<QueryClientProvider client={queryClient}>
|
|
<context.Provider value={value}>{props.children}</context.Provider>
|
|
</QueryClientProvider>
|
|
);
|
|
}
|
|
|
|
type contextType = {
|
|
getMessages: (amount: number, offset: number) => Promise<RawMessages>;
|
|
liveMessages: () => RawMessages;
|
|
addLiveMessage: (message: RawMessage) => void;
|
|
clearLiveMessages: () => void;
|
|
sharedSecret: () => string;
|
|
userId: () => number;
|
|
};
|
|
|
|
export function useChat(): contextType {
|
|
const ctx = React.useContext(context);
|
|
if (!ctx) {
|
|
throw new Error("useChat must be used within a ChatProvider");
|
|
}
|
|
return ctx;
|
|
} |