frontend client for gemstone. decentralised workplace app
at main 5.5 kB view raw
1import type { AtUri } from "@/lib/types/atproto"; 2import type { RequestHistoryMessage, ShardMessage } from "@/lib/types/messages"; 3import { historyMessageSchema, shardMessageSchema } from "@/lib/types/messages"; 4import { atUriEquals, atUriToString, stringToAtUri } from "@/lib/utils/atproto"; 5import { sendShardMessage } from "@/lib/utils/messages"; 6import { 7 validateWsMessageString, 8 validateWsMessageType, 9} from "@/lib/validators"; 10import { useSessions } from "@/providers/authed/SessionsProvider"; 11import { useOAuthSession } from "@/providers/OAuthProvider"; 12import { useEffect, useState } from "react"; 13 14export const useChannel = (channel: AtUri) => { 15 const [messages, setMessages] = useState<Array<ShardMessage>>([]); 16 const [isConnected, setIsConnected] = useState(false); 17 const { findChannelSession } = useSessions(); 18 const oAuthSession = useOAuthSession(); 19 20 const { sessionInfo, socket } = findChannelSession(channel); 21 22 useEffect(() => { 23 if (!sessionInfo) { 24 console.warn( 25 "Channel did not resolve to a valid sessionInfo object.", 26 ); 27 return; 28 } 29 if (!socket) { 30 console.warn( 31 "Session info did not resolve to a valid websocket connection. This should not happen and is likely a bug. Check the sessions map object.", 32 ); 33 return; 34 } 35 36 // attach handlers here 37 38 const handleOpen = () => { 39 console.log("Connected to WebSocket"); 40 setIsConnected(true); 41 const requestHistoryMessage: RequestHistoryMessage = { 42 type: "shard/requestHistory", 43 channel: atUriToString(channel), 44 requestedBy: sessionInfo.clientDid, 45 }; 46 console.log( 47 "requested history from lattice", 48 requestHistoryMessage, 49 ); 50 socket.send(JSON.stringify(requestHistoryMessage)); 51 }; 52 53 socket.addEventListener("message", (event) => { 54 console.log("received message", event); 55 const validateEventResult = validateWsMessageString(event.data); 56 if (!validateEventResult.ok) return; 57 58 const data: unknown = JSON.parse(validateEventResult.data); 59 const validateTypeResult = validateWsMessageType(data); 60 if (!validateTypeResult.ok) return; 61 62 const { type: messageType } = validateTypeResult.data; 63 64 switch (messageType) { 65 case "shard/message": { 66 const { success, data: shardMessage } = 67 shardMessageSchema.safeParse(validateTypeResult.data); 68 if (!success) return; 69 70 const parseChannelResult = stringToAtUri( 71 shardMessage.channel, 72 ); 73 74 if (!parseChannelResult.ok) return; 75 const { data: channelAtUri } = parseChannelResult; 76 77 if (atUriEquals(channelAtUri, channel)) 78 setMessages((prev) => [...prev, shardMessage]); 79 break; 80 } 81 case "shard/history": { 82 console.log( 83 "received history from lattice", 84 validateTypeResult.data, 85 ); 86 const { success, data: historyMessage } = 87 historyMessageSchema.safeParse(validateTypeResult.data); 88 if (!success) return; 89 if (!historyMessage.messages) return; 90 91 const parseChannelResult = stringToAtUri( 92 historyMessage.channel, 93 ); 94 95 if (!parseChannelResult.ok) return; 96 const { data: channelAtUri } = parseChannelResult; 97 98 if (atUriEquals(channelAtUri, channel)) 99 setMessages([...historyMessage.messages]); 100 } 101 } 102 }); 103 104 socket.addEventListener("error", (error) => { 105 console.error("WebSocket error:", error); 106 }); 107 108 socket.addEventListener("close", () => { 109 console.log("Disconnected from WebSocket"); 110 setIsConnected(false); 111 }); 112 113 if (socket.readyState === WebSocket.OPEN) { 114 handleOpen(); 115 } 116 117 socket.addEventListener("open", handleOpen); 118 119 return () => { 120 socket.removeEventListener("open", handleOpen); 121 }; 122 }, [socket, sessionInfo, channel]); 123 124 if (!oAuthSession) {console.warn("No OAuth session"); return } 125 if (!sessionInfo) { 126 console.warn( 127 "Channel did not resolve to a valid sessionInfo object.", 128 ); 129 return; 130 } 131 if (!socket) { 132 console.warn( 133 "Session info did not resolve to a valid websocket connection. This should not happen and is likely a bug. Check the sessions map object.", 134 ); 135 return; 136 } 137 138 const channelStringified = atUriToString(channel); 139 140 const sendMessageToChannel = (content: string) => { 141 sendShardMessage( 142 { 143 content, 144 channel: channelStringified, 145 sentBy: oAuthSession.did, 146 routedThrough: sessionInfo.latticeDid, 147 }, 148 socket, 149 ); 150 }; 151 152 return { sessionInfo, socket, messages, isConnected, sendMessageToChannel }; 153};