+59
-50
src/components/UniversalPostRenderer.tsx
+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
-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
-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
+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
+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
+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
+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
+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 };