an independent Bluesky client using Constellation, PDS Queries, and other services reddwarf.app
frontend spa bluesky reddwarf microcosm client app
93
fork

Configure Feed

Select the types of activity you want to include in your feed.

bye bye weird providers

rimar1337 833207ed cf9d7447

+168 -411
+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 };