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};