an appview-less Bluesky client using Constellation and PDS Queries reddwarf.app
frontend spa bluesky reddwarf microcosm

bye bye weird providers

rimar1337 833207ed cf9d7447

+59 -50
src/components/UniversalPostRenderer.tsx
··· 1 1 import { useNavigate } from "@tanstack/react-router"; 2 - import { useAtom } from 'jotai'; 2 + import { useAtom } from "jotai"; 3 3 import * as React from "react"; 4 4 import { type SVGProps } from "react"; 5 5 ··· 146 146 // >(null); 147 147 //const router = useRouter(); 148 148 149 - const parsed = React.useMemo(() => parseAtUri(atUri), [atUri]); 150 - const did = parsed?.did; 149 + //const parsed = React.useMemo(() => parseAtUri(atUri), [atUri]); 150 + const parsed = new AtUri(atUri); 151 + const did = parsed?.host; 151 152 const rkey = parsed?.rkey; 152 153 // /*mass comment*/ console.log("did", did); 153 154 // /*mass comment*/ console.log("rkey", rkey); ··· 387 388 // }; 388 389 if (!postQuery?.value) { 389 390 // deleted post more often than a non-resolvable post 390 - return (<></>) 391 + return <></>; 391 392 } 392 393 393 394 return ( ··· 409 410 ); 410 411 } 411 412 413 + function getAvatarUrl(opProfile: any, did: string) { 414 + const link = opProfile?.value?.avatar?.ref?.["$link"]; 415 + if (!link) return null; 416 + return `https://cdn.bsky.app/img/avatar/plain/${did}/${link}@jpeg`; 417 + } 418 + 412 419 export function UniversalPostRendererRawRecordShim({ 413 420 postRecord, 414 421 profileRecord, ··· 442 449 const navigate = useNavigate(); 443 450 444 451 //const { get, set } = usePersistentStore(); 445 - function getAvatarUrl(opProfile: any) { 446 - const link = opProfile?.value?.avatar?.ref?.["$link"]; 447 - if (!link) return null; 448 - return `https://cdn.bsky.app/img/avatar/plain/${resolved?.did}/${link}@jpeg`; 449 - } 450 - 451 452 // const [hydratedEmbed, setHydratedEmbed] = useState<any>(undefined); 452 453 453 454 // useEffect(() => { ··· 519 520 error: embedError, 520 521 } = useHydratedEmbed(postRecord?.value?.embed, resolved?.did); 521 522 522 - const parsedaturi = parseAtUri(aturi); 523 + const parsedaturi = new AtUri(aturi); //parseAtUri(aturi); 523 524 524 525 const fakepost = React.useMemo<AppBskyFeedDefs.PostView>( 525 526 () => ({ ··· 530 531 did: resolved?.did || "", 531 532 handle: resolved?.handle || "", 532 533 displayName: profileRecord?.value?.displayName || "", 533 - avatar: getAvatarUrl(profileRecord) || "", 534 + avatar: getAvatarUrl(profileRecord, resolved?.did) || "", 534 535 viewer: undefined, 535 536 labels: profileRecord?.labels || undefined, 536 537 verification: undefined, ··· 548 549 }), 549 550 [ 550 551 aturi, 551 - postRecord, 552 + postRecord?.cid, 553 + postRecord?.value, 554 + postRecord?.labels, 555 + resolved?.did, 556 + resolved?.handle, 552 557 profileRecord, 553 558 hydratedEmbed, 554 559 repliesCount, 555 560 repostsCount, 556 561 likesCount, 557 - resolved, 558 562 ] 559 563 ); 560 564 ··· 595 599 ); 596 600 const feedviewpostreplyhandle = replyhookvalue?.data?.handle; 597 601 598 - 599 - const aturirepostbydid = repostedby ? new AtUri(repostedby).host : undefined 602 + const aturirepostbydid = repostedby ? new AtUri(repostedby).host : undefined; 600 603 const repostedbyhookvalue = useQueryIdentity( 601 604 repostedby ? aturirepostbydid : undefined 602 605 ); ··· 612 615 parsedaturi && 613 616 navigate({ 614 617 to: "/profile/$did/post/$rkey", 615 - params: { did: parsedaturi.did, rkey: parsedaturi.rkey }, 618 + params: { did: parsedaturi.host, rkey: parsedaturi.rkey }, 616 619 }) 617 620 } 618 621 // onProfileClick={() => parsedaturi && navigate({to: "/profile/$did", ··· 623 626 if (parsedaturi) { 624 627 navigate({ 625 628 to: "/profile/$did", 626 - params: { did: parsedaturi.did }, 629 + params: { did: parsedaturi.host }, 627 630 }); 628 631 } 629 632 }} ··· 640 643 ); 641 644 } 642 645 643 - export function parseAtUri( 644 - atUri: string 645 - ): { did: string; collection: string; rkey: string } | null { 646 - const PREFIX = "at://"; 647 - if (!atUri.startsWith(PREFIX)) { 648 - return null; 649 - } 646 + // export function parseAtUri( 647 + // atUri: string 648 + // ): { did: string; collection: string; rkey: string } | null { 649 + // const PREFIX = "at://"; 650 + // if (!atUri.startsWith(PREFIX)) { 651 + // return null; 652 + // } 650 653 651 - const parts = atUri.slice(PREFIX.length).split("/"); 654 + // const parts = atUri.slice(PREFIX.length).split("/"); 652 655 653 - if (parts.length !== 3) { 654 - return null; 655 - } 656 + // if (parts.length !== 3) { 657 + // return null; 658 + // } 656 659 657 - const [did, collection, rkey] = parts; 660 + // const [did, collection, rkey] = parts; 658 661 659 - if (!did || !collection || !rkey) { 660 - return null; 661 - } 662 + // if (!did || !collection || !rkey) { 663 + // return null; 664 + // } 662 665 663 - return { did, collection, rkey }; 664 - } 666 + // return { did, collection, rkey }; 667 + // } 665 668 666 669 export function MdiCommentOutline(props: SVGProps<SVGSVGElement>) { 667 670 return ( ··· 1102 1105 post.viewer?.repost ? true : false 1103 1106 ); 1104 1107 const [hasLiked, setHasLiked] = useState<boolean>( 1105 - (post.uri in likedPosts) || post.viewer?.like ? true : false 1108 + post.uri in likedPosts || post.viewer?.like ? true : false 1106 1109 ); 1107 1110 const { agent } = useAuth(); 1108 1111 const [likeUri, setLikeUri] = useState<string | undefined>(post.viewer?.like); ··· 1132 1135 setHasLiked(true); 1133 1136 newLikedPosts[post.uri] = uri; 1134 1137 } 1135 - setLikedPosts(newLikedPosts) 1138 + setLikedPosts(newLikedPosts); 1136 1139 }; 1137 1140 1138 1141 const repostOrUnrepostPost = async () => { ··· 1152 1155 } 1153 1156 }; 1154 1157 1155 - const isRepost = repostedby ? repostedby : extraOptionalItemInfo 1156 - ? AppBskyFeedDefs.isReasonRepost(extraOptionalItemInfo.reason) 1157 - ? extraOptionalItemInfo.reason?.by.displayName 1158 - : undefined 1159 - : undefined; 1158 + const isRepost = repostedby 1159 + ? repostedby 1160 + : extraOptionalItemInfo 1161 + ? AppBskyFeedDefs.isReasonRepost(extraOptionalItemInfo.reason) 1162 + ? extraOptionalItemInfo.reason?.by.displayName 1163 + : undefined 1164 + : undefined; 1160 1165 const isReply = extraOptionalItemInfo 1161 1166 ? extraOptionalItemInfo.reply 1162 1167 : undefined; ··· 1224 1229 {!isQuote && ( 1225 1230 <div 1226 1231 style={{ 1227 - opacity: topReplyLine || (isReply /*&& (true || expanded)*/) ? 0.5 : 0, 1232 + opacity: 1233 + topReplyLine || isReply /*&& (true || expanded)*/ ? 0.5 : 0, 1228 1234 position: "absolute", 1229 1235 top: 0, 1230 1236 left: 36, // why 36 ??? ··· 1441 1447 {renderTextWithFacets({ 1442 1448 text: (post.record as { text?: string }).text ?? "", 1443 1449 facets: (post.record.facets as Facet[]) ?? [], 1444 - navigate: navigate 1450 + navigate: navigate, 1445 1451 })} 1446 1452 {} 1447 1453 </div> ··· 1455 1461 /> 1456 1462 ) : null} 1457 1463 {post.embed && depth > 0 && ( 1464 + /* pretty bad hack imo. its trying to sync up with how the embed shim doesnt 1465 + hydrate embeds this deep but the connection here is implicit 1466 + todo: idk make this a real part of the embed shim so its not implicit */ 1458 1467 <> 1459 1468 <div className="border-gray-300 dark:border-gray-600 p-3 rounded-xl border italic text-gray-400 text-[14px]"> 1460 1469 (there is an embed here thats too deep to render) ··· 1716 1725 salt={salt} 1717 1726 onPostClick={(e) => { 1718 1727 e.stopPropagation(); 1719 - const parsed = parseAtUri(post.uri); 1728 + const parsed = new AtUri(post.uri); //parseAtUri(post.uri); 1720 1729 if (parsed) { 1721 1730 navigate({ 1722 1731 to: "/profile/$did/post/$rkey", 1723 - params: { did: parsed.did, rkey: parsed.rkey }, 1732 + params: { did: parsed.host, rkey: parsed.rkey }, 1724 1733 }); 1725 1734 } 1726 1735 }} ··· 1833 1842 salt={salt} 1834 1843 onPostClick={(e) => { 1835 1844 e.stopPropagation(); 1836 - const parsed = parseAtUri(post.uri); 1845 + const parsed = new AtUri(post.uri); //parseAtUri(post.uri); 1837 1846 if (parsed) { 1838 1847 navigate({ 1839 1848 to: "/profile/$did/post/$rkey", 1840 - params: { did: parsed.did, rkey: parsed.rkey }, 1849 + params: { did: parsed.host, rkey: parsed.rkey }, 1841 1850 }); 1842 1851 } 1843 1852 }} ··· 2296 2305 for (let i = 0; i < bytes.length; i++) { 2297 2306 map[byteIndex++] = charIndex; 2298 2307 } 2299 - charIndex+=char.length; 2308 + charIndex += char.length; 2300 2309 } 2301 2310 2302 2311 return map; ··· 2389 2398 navigate({ 2390 2399 to: "/profile/$did", 2391 2400 // @ts-expect-error i didnt bother with the correct types here sorry. bsky api types are cursed 2392 - params: { did: feature.did}, 2401 + params: { did: feature.did }, 2393 2402 }); 2394 2403 }} 2395 2404 >
-149
src/providers/PassAuthProvider.tsx
··· 1 - import React, { createContext, useState, useEffect, useContext } from "react"; 2 - import { AtpAgent, type AtpSessionData } from "@atproto/api"; 3 - 4 - interface AuthContextValue { 5 - agent: AtpAgent | null; 6 - loginStatus: boolean; 7 - login: (user: string, password: string, service?: string) => Promise<void>; 8 - logout: () => Promise<void>; 9 - loading: boolean; 10 - authed: boolean | undefined; 11 - } 12 - 13 - const AuthContext = createContext<AuthContextValue>({} as AuthContextValue); 14 - 15 - export const AuthProvider = ({ children }: { children: React.ReactNode }) => { 16 - const [agent, setAgent] = useState<AtpAgent | null>(null); 17 - const [loginStatus, setLoginStatus] = useState(false); 18 - const [loading, setLoading] = useState(true); 19 - const [increment, setIncrement] = useState(0); 20 - const [authed, setAuthed] = useState<boolean | undefined>(undefined); 21 - 22 - useEffect(() => { 23 - const initialize = async () => { 24 - try { 25 - const service = localStorage.getItem("service"); 26 - // const user = await AsyncStorage.getItem('user'); 27 - // const password = await AsyncStorage.getItem('password'); 28 - const session = localStorage.getItem("sess"); 29 - 30 - if (service && session) { 31 - // /*mass comment*/ console.log("Auto-login service is:", service); 32 - const apiAgent = new AtpAgent({ service }); 33 - try { 34 - if (!apiAgent) { 35 - // /*mass comment*/ console.log("Agent is null or undefined"); 36 - return; 37 - } 38 - let sess: AtpSessionData = JSON.parse(session); 39 - // /*mass comment*/ console.log("resuming session is:", sess); 40 - const { data } = await apiAgent.resumeSession(sess); 41 - // /*mass comment*/ console.log("!!!8!!! agent resume session"); 42 - setAgent(apiAgent); 43 - setLoginStatus(true); 44 - setLoading(false); 45 - setAuthed(true); 46 - } catch (e) { 47 - // /*mass comment*/ console.log("Failed to resume session" + e); 48 - setLoginStatus(true); 49 - localStorage.removeItem("sess"); 50 - localStorage.removeItem("service"); 51 - const apiAgent = new AtpAgent({ service: "https://api.bsky.app" }); 52 - setAgent(apiAgent); 53 - setLoginStatus(true); 54 - setLoading(false); 55 - setAuthed(false); 56 - return; 57 - } 58 - } else { 59 - const apiAgent = new AtpAgent({ service: "https://api.bsky.app" }); 60 - setAgent(apiAgent); 61 - setLoginStatus(true); 62 - setLoading(false); 63 - setAuthed(false); 64 - } 65 - } catch (e) { 66 - // /*mass comment*/ console.log("Failed to auto-login:", e); 67 - } finally { 68 - setLoading(false); 69 - } 70 - }; 71 - 72 - initialize(); 73 - }, [increment]); 74 - 75 - const login = async ( 76 - user: string, 77 - password: string, 78 - service: string = "https://bsky.social", 79 - ) => { 80 - try { 81 - let sessionthing; 82 - const apiAgent = new AtpAgent({ 83 - service: service, 84 - persistSession: (evt, sess) => { 85 - sessionthing = sess; 86 - }, 87 - }); 88 - await apiAgent.login({ identifier: user, password }); 89 - // /*mass comment*/ console.log("!!!8!!! agent logged on"); 90 - 91 - localStorage.setItem("service", service); 92 - // await AsyncStorage.setItem('user', user); 93 - // await AsyncStorage.setItem('password', password); 94 - if (sessionthing) { 95 - localStorage.setItem("sess", JSON.stringify(sessionthing)); 96 - } else { 97 - localStorage.setItem("sess", "{}"); 98 - } 99 - 100 - setAgent(apiAgent); 101 - setLoginStatus(true); 102 - setAuthed(true); 103 - } catch (e) { 104 - console.error("Login failed:", e); 105 - } 106 - }; 107 - 108 - const logout = async () => { 109 - if (!agent) { 110 - console.error("Agent is null or undefined"); 111 - return; 112 - } 113 - setLoading(true); 114 - try { 115 - // check if its even in async storage before removing 116 - if (localStorage.getItem("service") && localStorage.getItem("sess")) { 117 - localStorage.removeItem("service"); 118 - localStorage.removeItem("sess"); 119 - } 120 - await agent.logout(); 121 - // /*mass comment*/ console.log("!!!8!!! agent logout"); 122 - setLoginStatus(false); 123 - setAuthed(undefined); 124 - await agent.com.atproto.server.deleteSession(); 125 - // /*mass comment*/ console.log("!!!8!!! agent deltesession"); 126 - //setAgent(null); 127 - setIncrement(increment + 1); 128 - } catch (e) { 129 - console.error("Logout failed:", e); 130 - } finally { 131 - setLoading(false); 132 - } 133 - }; 134 - 135 - // why the hell are we doing this 136 - /*if (loading) { 137 - return <div><span>Laoding...ae</span></div>; 138 - }*/ 139 - 140 - return ( 141 - <AuthContext.Provider 142 - value={{ agent, loginStatus, login, logout, loading, authed }} 143 - > 144 - {children} 145 - </AuthContext.Provider> 146 - ); 147 - }; 148 - 149 - export const useAuth = () => useContext(AuthContext);
-61
src/providers/PersistentStoreProvider.tsx
··· 1 - import React, { createContext, useContext, useCallback } from "react"; 2 - import { get as idbGet, set as idbSet, del as idbDel } from "idb-keyval"; 3 - 4 - type PersistentValue = { 5 - value: string; 6 - time: number; 7 - }; 8 - 9 - type PersistentStoreContextType = { 10 - get: (key: string) => Promise<PersistentValue | null>; 11 - set: (key: string, value: string) => Promise<void>; 12 - remove: (key: string) => Promise<void>; 13 - }; 14 - 15 - const PersistentStoreContext = createContext<PersistentStoreContextType | null>( 16 - null, 17 - ); 18 - 19 - export const PersistentStoreProvider: React.FC<{ 20 - children: React.ReactNode; 21 - }> = ({ children }) => { 22 - const get = useCallback( 23 - async (key: string): Promise<PersistentValue | null> => { 24 - if (typeof window === "undefined") return null; 25 - const raw = await idbGet(key); 26 - if (!raw) return null; 27 - try { 28 - return JSON.parse(raw) as PersistentValue; 29 - } catch { 30 - return null; 31 - } 32 - }, 33 - [], 34 - ); 35 - 36 - const set = useCallback(async (key: string, value: string) => { 37 - if (typeof window === "undefined") return; 38 - const entry: PersistentValue = { value, time: Date.now() }; 39 - await idbSet(key, JSON.stringify(entry)); 40 - }, []); 41 - 42 - const remove = useCallback(async (key: string) => { 43 - if (typeof window === "undefined") return; 44 - await idbDel(key); 45 - }, []); 46 - 47 - return ( 48 - <PersistentStoreContext.Provider value={{ get, set, remove }}> 49 - {children} 50 - </PersistentStoreContext.Provider> 51 - ); 52 - }; 53 - 54 - export const usePersistentStore = (): PersistentStoreContextType => { 55 - const context = useContext(PersistentStoreContext); 56 - if (!context) 57 - throw new Error( 58 - "usePersistentStore must be used within a PersistentStoreProvider", 59 - ); 60 - return context; 61 - };
+8 -14
src/routes/__root.tsx
··· 3 3 // dont forget to run this 4 4 // npx @tanstack/router-cli generate 5 5 6 - import { useState, type SVGProps } from "react"; 6 + import type { QueryClient } from "@tanstack/react-query"; 7 7 import { 8 - HeadContent, 8 + createRootRouteWithContext, 9 9 Link, 10 10 Outlet, 11 11 Scripts, 12 - createRootRoute, 13 - createRootRouteWithContext, 14 12 useLocation, 15 13 useNavigate, 16 14 } from "@tanstack/react-router"; 17 15 import { TanStackRouterDevtools } from "@tanstack/react-router-devtools"; 16 + import { type SVGProps,useState } from "react"; 18 17 import * as React from "react"; 18 + 19 19 import { DefaultCatchBoundary } from "~/components/DefaultCatchBoundary"; 20 20 import Login from "~/components/Login"; 21 21 import { NotFound } from "~/components/NotFound"; 22 - import appCss from "~/styles/app.css?url"; 22 + import { UnifiedAuthProvider, useAuth } from "~/providers/UnifiedAuthProvider"; 23 23 import { seo } from "~/utils/seo"; 24 - import { UnifiedAuthProvider, useAuth } from "~/providers/UnifiedAuthProvider"; 25 - import { PersistentStoreProvider } from "~/providers/PersistentStoreProvider"; 26 - import type Agent from "@atproto/api"; 27 - import type { QueryClient } from "@tanstack/react-query"; 28 24 29 25 export const Route = createRootRouteWithContext<{ 30 26 queryClient: QueryClient; ··· 79 75 function RootComponent() { 80 76 return ( 81 77 <UnifiedAuthProvider> 82 - <PersistentStoreProvider> 83 - <RootDocument> 84 - <Outlet /> 85 - </RootDocument> 86 - </PersistentStoreProvider> 78 + <RootDocument> 79 + <Outlet /> 80 + </RootDocument> 87 81 </UnifiedAuthProvider> 88 82 ); 89 83 }
+58 -63
src/routes/index.tsx
··· 1 1 import { createFileRoute } from "@tanstack/react-router"; 2 - import { 3 - CACHE_TIMEOUT, 4 - //cachedGetRecord, 5 - //cachedResolveIdentity, 6 - UniversalPostRendererATURILoader, 7 - } from "~/components/UniversalPostRenderer"; 2 + import { useAtom } from "jotai"; 8 3 import * as React from "react"; 4 + import { useEffect, useLayoutEffect } from "react"; 5 + 6 + import { InfiniteCustomFeed } from "~/components/InfiniteCustomFeed"; 9 7 import { useAuth } from "~/providers/UnifiedAuthProvider"; 8 + import { 9 + agentAtom, 10 + authedAtom, 11 + feedScrollPositionsAtom, 12 + selectedFeedUriAtom, 13 + store, 14 + } from "~/utils/atoms"; 10 15 //import { usePersistentStore } from "~/providers/PersistentStoreProvider"; 11 16 import { 12 - useQueryIdentity, 13 - useQueryPost, 14 - useQueryFeedSkeleton, 15 - useQueryPreferences, 16 - useQueryArbitrary, 17 - constructInfiniteFeedSkeletonQuery, 18 17 constructArbitraryQuery, 19 18 constructIdentityQuery, 19 + constructInfiniteFeedSkeletonQuery, 20 20 constructPostQuery, 21 + useQueryArbitrary, 22 + useQueryIdentity, 23 + useQueryPreferences, 21 24 } from "~/utils/useQuery"; 22 - import { InfiniteCustomFeed } from "~/components/InfiniteCustomFeed"; 23 - import { useAtom, useSetAtom } from "jotai"; 24 - import { 25 - selectedFeedUriAtom, 26 - store, 27 - agentAtom, 28 - authedAtom, 29 - feedScrollPositionsAtom, 30 - } from "~/utils/atoms"; 31 - import { useEffect, useLayoutEffect } from "react"; 32 25 33 26 export const Route = createFileRoute("/")({ 34 27 loader: async ({ context }) => { ··· 116 109 }, [status, agent, authed]); 117 110 useEffect(() => { 118 111 if (agent) { 119 - // is it just me or is the type really weird here it should be Agent not AtpAgent 112 + // eslint-disable-next-line @typescript-eslint/ban-ts-comment 113 + // @ts-ignore is it just me or is the type really weird here it should be Agent not AtpAgent 120 114 store.set(agentAtom, agent); 121 115 } else { 122 116 store.set(agentAtom, null); ··· 330 324 setRestoringScrollPosition(true); 331 325 const savedPosition = scrollPositions[selectedFeed ?? "null"] ?? 0; 332 326 333 - let raf = requestAnimationFrame(() => { 327 + const raf = requestAnimationFrame(() => { 334 328 // setRestoringScrollPosition(true); 335 329 // raf = requestAnimationFrame(() => { 336 330 // window.scrollTo({ top: savedPosition, behavior: "instant" }); ··· 428 422 Select a feed to get started. 429 423 </div> 430 424 )} 431 - {false && restoringScrollPosition && ( 425 + {/* {false && restoringScrollPosition && ( 432 426 <div className="fixed top-1/2 left-1/2 right-1/2"> 433 427 restoringScrollPosition 434 428 </div> 435 - )} 429 + )} */} 436 430 </div> 437 431 ); 438 432 } 433 + // not even used lmaooo 439 434 440 - export async function cachedResolveDIDWEBDOC({ 441 - didweb, 442 - cacheTimeout = CACHE_TIMEOUT, 443 - get, 444 - set, 445 - }: { 446 - didweb: string; 447 - cacheTimeout?: number; 448 - get: (key: string) => any; 449 - set: (key: string, value: string) => void; 450 - }): Promise<any> { 451 - const isDidInput = didweb.startsWith("did:web:"); 452 - const cacheKey = `didwebdoc:${didweb}`; 453 - const now = Date.now(); 454 - const cached = get(cacheKey); 455 - if ( 456 - cached && 457 - cached.value && 458 - cached.time && 459 - now - cached.time < cacheTimeout 460 - ) { 461 - try { 462 - return JSON.parse(cached.value); 463 - } catch {} 464 - } 465 - const url = `https://free-fly-24.deno.dev/resolve-did-web?did=${encodeURIComponent( 466 - didweb 467 - )}`; 468 - const res = await fetch(url); 469 - if (!res.ok) throw new Error("Failed to resolve didwebdoc"); 470 - const data = await res.json(); 471 - set(cacheKey, JSON.stringify(data)); 472 - if (!isDidInput && data.did) { 473 - set(`didwebdoc:${data.did}`, JSON.stringify(data)); 474 - } 475 - return data; 476 - } 435 + // export async function cachedResolveDIDWEBDOC({ 436 + // didweb, 437 + // cacheTimeout = CACHE_TIMEOUT, 438 + // get, 439 + // set, 440 + // }: { 441 + // didweb: string; 442 + // cacheTimeout?: number; 443 + // get: (key: string) => any; 444 + // set: (key: string, value: string) => void; 445 + // }): Promise<any> { 446 + // const isDidInput = didweb.startsWith("did:web:"); 447 + // const cacheKey = `didwebdoc:${didweb}`; 448 + // const now = Date.now(); 449 + // const cached = get(cacheKey); 450 + // if ( 451 + // cached && 452 + // cached.value && 453 + // cached.time && 454 + // now - cached.time < cacheTimeout 455 + // ) { 456 + // try { 457 + // return JSON.parse(cached.value); 458 + // } catch (_e) {/* whatever*/ } 459 + // } 460 + // const url = `https://free-fly-24.deno.dev/resolve-did-web?did=${encodeURIComponent( 461 + // didweb 462 + // )}`; 463 + // const res = await fetch(url); 464 + // if (!res.ok) throw new Error("Failed to resolve didwebdoc"); 465 + // const data = await res.json(); 466 + // set(cacheKey, JSON.stringify(data)); 467 + // if (!isDidInput && data.did) { 468 + // set(`didwebdoc:${data.did}`, JSON.stringify(data)); 469 + // } 470 + // return data; 471 + // } 477 472 478 473 // export async function cachedGetPrefs({ 479 474 // did,
+22 -21
src/routes/notifications.tsx
··· 1 1 import { createFileRoute } from "@tanstack/react-router"; 2 - import React, { useEffect, useState, useRef } from "react"; 3 - import { useAuth } from "~/providers/PassAuthProvider"; 4 - import { usePersistentStore } from "~/providers/PersistentStoreProvider"; 2 + import React, { useEffect, useRef,useState } from "react"; 3 + 4 + import { useAuth } from "~/providers/UnifiedAuthProvider"; 5 5 6 6 const HANDLE_DID_CACHE_TIMEOUT = 60 * 60 * 1000; // 1 hour 7 7 ··· 11 11 12 12 function NotificationsComponent() { 13 13 // /*mass comment*/ console.log("NotificationsComponent render"); 14 - const { agent, authed, loading: authLoading } = useAuth(); 15 - const { get, set } = usePersistentStore(); 14 + const { agent, status } = useAuth(); 15 + const authed = !!agent?.did; 16 + const authLoading = status === "loading"; 16 17 const [did, setDid] = useState<string | null>(null); 17 18 const [resolving, setResolving] = useState(false); 18 19 const [error, setError] = useState<string | null>(null); ··· 41 42 setResolving(true); 42 43 const cacheKey = `handleDid:${value}`; 43 44 const now = Date.now(); 44 - const cached = await get(cacheKey); 45 - if ( 46 - cached && 47 - cached.value && 48 - cached.time && 49 - now - cached.time < HANDLE_DID_CACHE_TIMEOUT 50 - ) { 51 - try { 52 - const data = JSON.parse(cached.value); 53 - setDid(data.did); 54 - setResolving(false); 55 - return; 56 - } catch {} 57 - } 45 + const cached = undefined // await get(cacheKey); 46 + // if ( 47 + // cached && 48 + // cached.value && 49 + // cached.time && 50 + // now - cached.time < HANDLE_DID_CACHE_TIMEOUT 51 + // ) { 52 + // try { 53 + // const data = JSON.parse(cached.value); 54 + // setDid(data.did); 55 + // setResolving(false); 56 + // return; 57 + // } catch {} 58 + // } 58 59 try { 59 60 const url = `https://free-fly-24.deno.dev/?handle=${encodeURIComponent(value)}`; 60 61 const res = await fetch(url); 61 62 if (!res.ok) throw new Error("Failed to resolve handle"); 62 63 const data = await res.json(); 63 - set(cacheKey, JSON.stringify(data)); 64 + //set(cacheKey, JSON.stringify(data)); 64 65 setDid(data.did); 65 66 } catch (e: any) { 66 67 setError("Failed to resolve handle: " + (e?.message || e)); ··· 94 95 } catch (e: any) { 95 96 return { error: e?.message || String(e) }; 96 97 } 97 - }), 98 + }) 98 99 ) 99 100 .then((results) => { 100 101 if (!ignore) setResponses(results);
+3 -5
src/utils/oauthClient.ts
··· 1 - // src/helpers/oauthClient.ts 2 1 import { BrowserOAuthClient, type ClientMetadata } from '@atproto/oauth-client-browser'; 3 2 4 - // This is your app's PDS for resolving handles if not provided. 5 - // You might need to host your own or use a public one. 3 + // i tried making this https://pds-nd.whey.party but cors is annoying as fuck 6 4 const handleResolverPDS = 'https://bsky.social'; 7 5 8 - // This assumes your client-metadata.json is in the /public folder 9 - // and will be served at the root of your domain. 6 + // eslint-disable-next-line @typescript-eslint/ban-ts-comment 7 + // @ts-ignore this should be fine ? the vite plugin should generate this before errors 10 8 import clientMetadata from '../../public/client-metadata.json' with { type: 'json' }; 11 9 12 10 export const oauthClient = new BrowserOAuthClient({
+18 -48
src/utils/useHydrated.ts
··· 9 9 AppBskyFeedPost, 10 10 AtUri, 11 11 } from "@atproto/api"; 12 - import { useEffect, useMemo,useState } from "react"; 12 + import { useMemo } from "react"; 13 13 14 14 import { useQueryIdentity,useQueryPost, useQueryProfile } from "./useQuery"; 15 15 ··· 151 151 postAuthorDid: string | undefined, 152 152 ) { 153 153 const recordInfo = useMemo(() => { 154 - if ( 155 - AppBskyEmbedRecordWithMedia.isMain(embed) 156 - ) { 154 + if (AppBskyEmbedRecordWithMedia.isMain(embed)) { 157 155 const recordUri = embed.record.record.uri; 158 156 const quotedAuthorDid = new AtUri(recordUri).hostname; 159 157 return { recordUri, quotedAuthorDid, isRecordType: true }; 160 - } else 161 - if ( 162 - AppBskyEmbedRecord.isMain(embed) 163 - ) { 158 + } else if (AppBskyEmbedRecord.isMain(embed)) { 164 159 const recordUri = embed.record.uri; 165 160 const quotedAuthorDid = new AtUri(recordUri).hostname; 166 161 return { recordUri, quotedAuthorDid, isRecordType: true }; ··· 171 166 isRecordType: false, 172 167 }; 173 168 }, [embed]); 174 - const { isRecordType, recordUri, quotedAuthorDid } = recordInfo; 175 169 170 + const { isRecordType, recordUri, quotedAuthorDid } = recordInfo; 176 171 177 172 const usequerypostresults = useQueryPost(recordUri); 178 - // const { 179 - // data: quotedPost, 180 - // isLoading: isLoadingPost, 181 - // error: postError, 182 - // } = usequerypostresults 183 173 184 - const profileUri = quotedAuthorDid ? `at://${quotedAuthorDid}/app.bsky.actor.profile/self` : undefined; 174 + const profileUri = quotedAuthorDid 175 + ? `at://${quotedAuthorDid}/app.bsky.actor.profile/self` 176 + : undefined; 185 177 186 178 const { 187 179 data: quotedProfile, ··· 190 182 } = useQueryProfile(profileUri); 191 183 192 184 const queryidentityresult = useQueryIdentity(quotedAuthorDid); 193 - // const { 194 - // data: quotedIdentity, 195 - // isLoading: isLoadingIdentity, 196 - // error: identityError, 197 - // } = queryidentityresult 198 185 199 - const [hydratedEmbed, setHydratedEmbed] = useState< 200 - HydratedEmbedView | undefined 201 - >(undefined); 202 - 203 - useEffect(() => { 204 - if (!embed || !postAuthorDid) { 205 - setHydratedEmbed(undefined); 206 - return; 207 - } 186 + const hydratedEmbed: HydratedEmbedView | undefined = (() => { 187 + if (!embed || !postAuthorDid) return undefined; 208 188 209 189 if (isRecordType && (!usequerypostresults?.data || !quotedProfile || !queryidentityresult?.data)) { 210 - setHydratedEmbed(undefined); 211 - return; 190 + return undefined; 212 191 } 213 192 214 193 try { 215 - let result: HydratedEmbedView | undefined; 216 - 217 194 if (AppBskyEmbedImages.isMain(embed)) { 218 - result = hydrateEmbedImages(embed, postAuthorDid); 195 + return hydrateEmbedImages(embed, postAuthorDid); 219 196 } else if (AppBskyEmbedExternal.isMain(embed)) { 220 - result = hydrateEmbedExternal(embed, postAuthorDid); 197 + return hydrateEmbedExternal(embed, postAuthorDid); 221 198 } else if (AppBskyEmbedVideo.isMain(embed)) { 222 - result = hydrateEmbedVideo(embed, postAuthorDid); 199 + return hydrateEmbedVideo(embed, postAuthorDid); 223 200 } else if (AppBskyEmbedRecord.isMain(embed)) { 224 - result = hydrateEmbedRecord( 201 + return hydrateEmbedRecord( 225 202 embed, 226 203 usequerypostresults?.data, 227 204 quotedProfile, ··· 243 220 } 244 221 245 222 if (hydratedMedia) { 246 - result = hydrateEmbedRecordWithMedia( 223 + return hydrateEmbedRecordWithMedia( 247 224 embed, 248 225 hydratedMedia, 249 226 usequerypostresults?.data, ··· 252 229 ); 253 230 } 254 231 } 255 - setHydratedEmbed(result); 256 232 } catch (e) { 257 233 console.error("Error hydrating embed", e); 258 - setHydratedEmbed(undefined); 234 + return undefined; 259 235 } 260 - }, [ 261 - embed, 262 - postAuthorDid, 263 - isRecordType, 264 - usequerypostresults?.data, 265 - quotedProfile, 266 - queryidentityresult?.data, 267 - ]); 236 + })(); 268 237 269 238 const isLoading = isRecordType 270 239 ? usequerypostresults?.isLoading || isLoadingProfile || queryidentityresult?.isLoading 271 240 : false; 241 + 272 242 const error = usequerypostresults?.error || profileError || queryidentityresult?.error; 273 243 274 244 return { data: hydratedEmbed, isLoading, error };