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 XrpcHandleResolver
16} from '@atcute/identity-resolver';
17import { slingshotUrl } from './client';
18import type { ActorIdentifier } from '@atcute/lexicons';
19import { err, ok, type Result } from '$lib/result';
20import type { AtprotoDid } from '@atcute/lexicons/syntax';
21import { clientId, redirectUri } from '$lib/oauth';
22
23configureOAuth({
24 metadata: {
25 client_id: clientId,
26 redirect_uri: redirectUri
27 },
28 identityResolver: defaultIdentityResolver({
29 handleResolver: new XrpcHandleResolver({ serviceUrl: slingshotUrl.href }),
30
31 didDocumentResolver: new CompositeDidDocumentResolver({
32 methods: {
33 plc: new PlcDidDocumentResolver(),
34 web: new WebDidDocumentResolver()
35 }
36 })
37 })
38});
39
40export const sessions = {
41 get: async (did: AtprotoDid) => {
42 const session = await getSession(did, { allowStale: true });
43 return new OAuthUserAgent(session);
44 },
45 remove: async (did: AtprotoDid) => {
46 try {
47 const agent = await sessions.get(did);
48 await agent.signOut();
49 } catch {
50 deleteStoredSession(did);
51 }
52 }
53};
54
55export const flow = {
56 start: async (identifier: ActorIdentifier): Promise<Result<null, string>> => {
57 try {
58 const authUrl = await createAuthorizationUrl({
59 target: { type: 'account', identifier },
60 scope: 'atproto transition:generic'
61 });
62 // recommended to wait for the browser to persist local storage before proceeding
63 await new Promise((resolve) => setTimeout(resolve, 200));
64 // redirect the user to sign in and authorize the app
65 window.location.assign(authUrl);
66 // if this is on an async function, ideally the function should never ever resolve.
67 // the only way it should resolve at this point is if the user aborted the authorization
68 // by returning back to this page (thanks to back-forward page caching)
69 await new Promise((_resolve, reject) => {
70 const listener = () => {
71 reject(new Error(`user aborted the login request`));
72 };
73 window.addEventListener('pageshow', listener, { once: true });
74 });
75 return ok(null);
76 } catch (error) {
77 return err(`login error: ${error}`);
78 }
79 },
80 finalize: async (url: URL): Promise<Result<OAuthUserAgent | null, string>> => {
81 try {
82 // createAuthorizationUrl asks server to put the params in the hash
83 const params = new URLSearchParams(url.hash.slice(1));
84 if (!params.has('code')) return ok(null);
85 const { session } = await finalizeAuthorization(params);
86 return ok(new OAuthUserAgent(session));
87 } catch (error) {
88 return err(`login error: ${error}`);
89 }
90 }
91};