diff options
Diffstat (limited to 'src/screens/Messages/Temp')
-rw-r--r-- | src/screens/Messages/Temp/query/query.ts | 219 | ||||
-rw-r--r-- | src/screens/Messages/Temp/useDmServiceUrlStorage.tsx | 64 |
2 files changed, 283 insertions, 0 deletions
diff --git a/src/screens/Messages/Temp/query/query.ts b/src/screens/Messages/Temp/query/query.ts new file mode 100644 index 000000000..2477dc569 --- /dev/null +++ b/src/screens/Messages/Temp/query/query.ts @@ -0,0 +1,219 @@ +import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query' + +import {useSession} from 'state/session' +import {useDmServiceUrlStorage} from '#/screens/Messages/Temp/useDmServiceUrlStorage' +import * as TempDmChatDefs from '#/temp/dm/defs' +import * as TempDmChatGetChat from '#/temp/dm/getChat' +import * as TempDmChatGetChatLog from '#/temp/dm/getChatLog' +import * as TempDmChatGetChatMessages from '#/temp/dm/getChatMessages' + +/** + * TEMPORARY, PLEASE DO NOT JUDGE ME REACT QUERY OVERLORDS 🙏 + * (and do not try this at home) + */ + +function createHeaders(did: string) { + return { + Authorization: did, + } +} + +type Chat = { + chatId: string + messages: TempDmChatGetChatMessages.OutputSchema['messages'] + lastCursor?: string + lastRev?: string +} + +export function useChat(chatId: string) { + const queryClient = useQueryClient() + + const {serviceUrl} = useDmServiceUrlStorage() + const {currentAccount} = useSession() + const did = currentAccount?.did ?? '' + + return useQuery({ + queryKey: ['chat', chatId], + queryFn: async () => { + const currentChat = queryClient.getQueryData(['chat', chatId]) + + if (currentChat) { + return currentChat as Chat + } + + const messagesResponse = await fetch( + `${serviceUrl}/xrpc/temp.dm.getChatMessages?chatId=${chatId}`, + { + headers: createHeaders(did), + }, + ) + + if (!messagesResponse.ok) throw new Error('Failed to fetch messages') + + const messagesJson = + (await messagesResponse.json()) as TempDmChatGetChatMessages.OutputSchema + + const chatResponse = await fetch( + `${serviceUrl}/xrpc/temp.dm.getChat?chatId=${chatId}`, + { + headers: createHeaders(did), + }, + ) + + if (!chatResponse.ok) throw new Error('Failed to fetch chat') + + const chatJson = + (await chatResponse.json()) as TempDmChatGetChat.OutputSchema + + const newChat = { + chatId, + messages: messagesJson.messages, + lastCursor: messagesJson.cursor, + lastRev: chatJson.chat.rev, + } satisfies Chat + + queryClient.setQueryData(['chat', chatId], newChat) + + return newChat + }, + }) +} + +interface SendMessageMutationVariables { + message: string + tempId: string +} + +export function createTempId() { + return Math.random().toString(36).substring(7).toString() +} + +export function useSendMessageMutation(chatId: string) { + const queryClient = useQueryClient() + + const {serviceUrl} = useDmServiceUrlStorage() + const {currentAccount} = useSession() + const did = currentAccount?.did ?? '' + + return useMutation< + TempDmChatDefs.Message, + Error, + SendMessageMutationVariables, + unknown + >({ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + mutationFn: async ({message, tempId}) => { + const response = await fetch( + `${serviceUrl}/xrpc/temp.dm.sendMessage?chatId=${chatId}`, + { + method: 'POST', + headers: { + ...createHeaders(did), + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + chatId, + message: { + text: message, + }, + }), + }, + ) + + if (!response.ok) throw new Error('Failed to send message') + + return response.json() + }, + onMutate: async variables => { + queryClient.setQueryData(['chat', chatId], (prev: Chat) => { + return { + ...prev, + messages: [ + { + id: variables.tempId, + text: variables.message, + }, + ...prev.messages, + ], + } + }) + }, + onSuccess: (result, variables) => { + queryClient.setQueryData(['chat', chatId], (prev: Chat) => { + return { + ...prev, + messages: prev.messages.map(m => + m.id === variables.tempId + ? { + ...m, + id: result.id, + } + : m, + ), + } + }) + }, + onError: (_, variables) => { + console.log(_) + queryClient.setQueryData(['chat', chatId], (prev: Chat) => ({ + ...prev, + messages: prev.messages.filter(m => m.id !== variables.tempId), + })) + }, + }) +} + +export function useChatLogQuery() { + const queryClient = useQueryClient() + + const {serviceUrl} = useDmServiceUrlStorage() + const {currentAccount} = useSession() + const did = currentAccount?.did ?? '' + + return useQuery({ + queryKey: ['chatLog'], + queryFn: async () => { + const prevLog = queryClient.getQueryData([ + 'chatLog', + ]) as TempDmChatGetChatLog.OutputSchema + + try { + const response = await fetch( + `${serviceUrl}/xrpc/temp.dm.getChatLog?cursor=${ + prevLog?.cursor ?? '' + }`, + { + headers: createHeaders(did), + }, + ) + + if (!response.ok) throw new Error('Failed to fetch chat log') + + const json = + (await response.json()) as TempDmChatGetChatLog.OutputSchema + + for (const log of json.logs) { + if (TempDmChatDefs.isLogDeleteMessage(log)) { + queryClient.setQueryData(['chat', log.chatId], (prev: Chat) => { + // What to do in this case + if (!prev) return + + // HACK we don't know who the creator of a message is, so just filter by id for now + if (prev.messages.find(m => m.id === log.message.id)) return prev + + return { + ...prev, + messages: [log.message, ...prev.messages], + } + }) + } + } + + return json + } catch (e) { + console.log(e) + } + }, + refetchInterval: 5000, + }) +} diff --git a/src/screens/Messages/Temp/useDmServiceUrlStorage.tsx b/src/screens/Messages/Temp/useDmServiceUrlStorage.tsx new file mode 100644 index 000000000..3679858f4 --- /dev/null +++ b/src/screens/Messages/Temp/useDmServiceUrlStorage.tsx @@ -0,0 +1,64 @@ +import React from 'react' +import {useAsyncStorage} from '@react-native-async-storage/async-storage' + +/** + * TEMP: REMOVE BEFORE RELEASE + * + * Clip clop trivia: + * + * A little known fact about the term "clip clop" is that it may refer to a unit of time. It is unknown what the exact + * length of a clip clop is, but it is generally agreed that it is approximately 9 minutes and 30 seconds, or 570 + * seconds. + * + * The term "clip clop" may also be used in other contexts, although it is unknown what all of these contexts may be. + * Recently, the term has been used among many young adults to refer to a type of social media functionality, although + * the exact nature of this functionality is also unknown. It is believed that the term may have originated from a + * popular video game, but this has not been confirmed. + * + */ + +const DmServiceUrlStorageContext = React.createContext<{ + serviceUrl: string + setServiceUrl: (value: string) => void +}>({ + serviceUrl: '', + setServiceUrl: () => {}, +}) + +export const useDmServiceUrlStorage = () => + React.useContext(DmServiceUrlStorageContext) + +export function DmServiceUrlProvider({children}: {children: React.ReactNode}) { + const [serviceUrl, setServiceUrl] = React.useState<string>('') + const {getItem, setItem: setItemInner} = useAsyncStorage('dmServiceUrl') + + React.useEffect(() => { + ;(async () => { + const v = await getItem() + console.log(v) + setServiceUrl(v ?? '') + })() + }, [getItem]) + + const setItem = React.useCallback( + (v: string) => { + setItemInner(v) + setServiceUrl(v) + }, + [setItemInner], + ) + + const value = React.useMemo( + () => ({ + serviceUrl, + setServiceUrl: setItem, + }), + [serviceUrl, setItem], + ) + + return ( + <DmServiceUrlStorageContext.Provider value={value}> + {children} + </DmServiceUrlStorageContext.Provider> + ) +} |