1import { Client, simpleFetchHandler } from "@atcute/client";
2import { Did } from "@atcute/lexicons";
3import {
4 finalizeAuthorization,
5 getSession,
6 OAuthUserAgent,
7 type Session,
8} from "@atcute/oauth-browser-client";
9import { resolveDidDoc } from "../utils/api";
10import { Sessions, setAgent, setSessions } from "./state";
11
12export const saveSessionToStorage = (sessions: Sessions) => {
13 localStorage.setItem("sessions", JSON.stringify(sessions));
14};
15
16export const loadSessionsFromStorage = (): Sessions | null => {
17 const localSessions = localStorage.getItem("sessions");
18 return localSessions ? JSON.parse(localSessions) : null;
19};
20
21export const getAvatar = async (did: Did): Promise<string | undefined> => {
22 const rpc = new Client({
23 handler: simpleFetchHandler({ service: "https://public.api.bsky.app" }),
24 });
25 const res = await rpc.get("app.bsky.actor.getProfile", { params: { actor: did } });
26 if (res.ok) {
27 return res.data.avatar;
28 }
29 return undefined;
30};
31
32export const loadHandleForSession = async (did: Did, storedSessions: Sessions) => {
33 const doc = await resolveDidDoc(did);
34 const alias = doc.alsoKnownAs?.find((alias) => alias.startsWith("at://"));
35 if (alias) {
36 setSessions(did, {
37 signedIn: storedSessions[did].signedIn,
38 handle: alias.replace("at://", ""),
39 grantedScopes: storedSessions[did].grantedScopes,
40 });
41 }
42};
43
44export const retrieveSession = async (): Promise<void> => {
45 const init = async (): Promise<Session | undefined> => {
46 const params = new URLSearchParams(location.hash.slice(1));
47
48 if (params.has("state") && (params.has("code") || params.has("error"))) {
49 history.replaceState(null, "", location.pathname + location.search);
50
51 const auth = await finalizeAuthorization(params);
52 const did = auth.session.info.sub;
53
54 localStorage.setItem("lastSignedIn", did);
55
56 const grantedScopes = localStorage.getItem("pendingScopes") || "atproto";
57 localStorage.removeItem("pendingScopes");
58
59 const sessions = loadSessionsFromStorage();
60 const newSessions: Sessions = sessions || {};
61 newSessions[did] = { signedIn: true, grantedScopes };
62 saveSessionToStorage(newSessions);
63 return auth.session;
64 } else {
65 const lastSignedIn = localStorage.getItem("lastSignedIn");
66
67 if (lastSignedIn) {
68 const sessions = loadSessionsFromStorage();
69 const newSessions: Sessions = sessions || {};
70 try {
71 const session = await getSession(lastSignedIn as Did);
72 const rpc = new Client({ handler: new OAuthUserAgent(session) });
73 const res = await rpc.get("com.atproto.server.getSession");
74 newSessions[lastSignedIn].signedIn = true;
75 saveSessionToStorage(newSessions);
76 if (!res.ok) throw res.data.error;
77 return session;
78 } catch (err) {
79 newSessions[lastSignedIn].signedIn = false;
80 saveSessionToStorage(newSessions);
81 throw err;
82 }
83 }
84 }
85 };
86
87 const session = await init();
88
89 if (session) setAgent(new OAuthUserAgent(session));
90};
91
92export const resumeSession = async (did: Did): Promise<void> => {
93 localStorage.setItem("lastSignedIn", did);
94 await retrieveSession();
95};