creates video voice memos from audio clips; with bluesky integration.
trill.ptr.pet
1import {
2 configureOAuth,
3 defaultIdentityResolver,
4 createAuthorizationUrl,
5 finalizeAuthorization,
6 OAuthUserAgent,
7 getSession,
8 deleteStoredSession,
9} from "@atcute/oauth-browser-client";
10
11import {
12 CompositeDidDocumentResolver,
13 PlcDidDocumentResolver,
14 WebDidDocumentResolver,
15} from "@atcute/identity-resolver";
16import type { ActorIdentifier } from "@atcute/lexicons";
17import type { AtprotoDid } from "@atcute/lexicons/syntax";
18import { handleResolver, login } from "./at";
19import { loggingIn } from "./accounts";
20import { clientId, redirectUri } from "./oauthMetadata";
21
22configureOAuth({
23 metadata: {
24 client_id: clientId,
25 redirect_uri: redirectUri,
26 },
27 identityResolver: defaultIdentityResolver({
28 handleResolver,
29
30 didDocumentResolver: new CompositeDidDocumentResolver({
31 methods: {
32 plc: new PlcDidDocumentResolver(),
33 web: new WebDidDocumentResolver(),
34 },
35 }),
36 }),
37});
38
39export const sessions = {
40 get: async (did: AtprotoDid) => {
41 const session = await getSession(did, { allowStale: true });
42 return new OAuthUserAgent(session);
43 },
44 remove: async (did: AtprotoDid) => {
45 try {
46 const agent = await sessions.get(did);
47 await agent.signOut();
48 } catch {
49 deleteStoredSession(did);
50 }
51 },
52};
53
54export const flow = {
55 start: async (identifier: ActorIdentifier): Promise<void> => {
56 const authUrl = await createAuthorizationUrl({
57 target: { type: "account", identifier },
58 scope: "atproto transition:generic",
59 });
60 // recommended to wait for the browser to persist local storage before proceeding
61 await new Promise((resolve) => setTimeout(resolve, 200));
62 // redirect the user to sign in and authorize the app
63 window.location.assign(authUrl);
64 // if this is on an async function, ideally the function should never ever resolve.
65 // the only way it should resolve at this point is if the user aborted the authorization
66 // by returning back to this page (thanks to back-forward page caching)
67 await new Promise((_resolve, reject) => {
68 const listener = () => {
69 reject(new Error(`user aborted the login request`));
70 };
71 window.addEventListener("pageshow", listener, { once: true });
72 });
73 },
74 finalize: async (url: URL): Promise<OAuthUserAgent | null> => {
75 // createAuthorizationUrl asks server to put the params in the hash
76 const params = new URLSearchParams(url.hash.slice(1));
77 if (!params.has("code")) return null;
78 const { session } = await finalizeAuthorization(params);
79 return new OAuthUserAgent(session);
80 },
81};
82
83export const tryFinalizeLogin = async () => {
84 const did = loggingIn.get();
85 if (!did) return;
86
87 const currentUrl = new URL(window.location.href);
88 // scrub history so auth state cant be replayed
89 try {
90 history.replaceState(null, "", "/");
91 } catch {
92 // if router was unitialized then we probably dont need to scrub anyway
93 // so its fine
94 }
95
96 loggingIn.set(undefined);
97 await sessions.remove(did);
98 const agent = await flow.finalize(currentUrl);
99 if (!agent) throw "no session was logged into?";
100
101 return await login(agent);
102};
103
104export const getSessionClient = async (did: AtprotoDid) => {
105 const session = await sessions.get(did);
106 if (!session) throw `no session found for ${did}`;
107 return await login(session);
108};