A social knowledge tool for researchers built on ATProto
1import { err, ok, Result } from 'src/shared/core/Result';
2import { IAppPasswordSessionRepository } from '../repositories/IAppPasswordSessionRepository';
3import { AtpAgent, AtpSessionData } from '@atproto/api';
4import { IAppPasswordSessionService } from '../../application/IAppPasswordSessionService';
5import { ATPROTO_SERVICE_ENDPOINTS } from './ServiceEndpoints';
6
7export class AppPasswordSessionService implements IAppPasswordSessionService {
8 constructor(
9 private readonly appPasswordSessionRepository: IAppPasswordSessionRepository,
10 ) {}
11 async getSession(did: string): Promise<Result<AtpSessionData>> {
12 const sessionResult =
13 await this.appPasswordSessionRepository.getSession(did);
14 if (sessionResult.isErr()) {
15 return err(sessionResult.error);
16 }
17 const agent = new AtpAgent({
18 service: ATPROTO_SERVICE_ENDPOINTS.AUTHENTICATED_BSKY_SERVICE,
19 });
20
21 const sessionWithAppPassword = sessionResult.value;
22 if (!sessionWithAppPassword) {
23 return err(new Error(`No session found for DID: ${did}`));
24 }
25 try {
26 await agent.resumeSession(sessionResult.value.session);
27 const session = agent.session;
28 if (!session) {
29 return err(new Error(`Failed to resume session for DID: ${did}`));
30 }
31 return ok(session);
32 } catch (error) {
33 try {
34 await agent.login({
35 identifier: sessionWithAppPassword.session.did,
36 password: sessionWithAppPassword.appPassword,
37 });
38 const updatedSession = agent.session;
39 if (!updatedSession) {
40 return err(
41 new Error(`Failed to login with app password for DID: ${did}`),
42 );
43 }
44 const saveResult = await this.appPasswordSessionRepository.saveSession(
45 did,
46 {
47 session: updatedSession,
48 appPassword: sessionWithAppPassword.appPassword,
49 },
50 );
51 if (saveResult.isErr()) {
52 return err(
53 new Error(
54 `Failed to save session after login for DID: ${did}, error: ${saveResult.error.message}`,
55 ),
56 );
57 }
58 return ok(updatedSession);
59 } catch (error) {
60 return err(
61 new Error(
62 `Failed to login with app password for DID: ${did}, error: ${
63 error instanceof Error ? error.message : String(error)
64 }`,
65 ),
66 );
67 }
68 }
69 }
70 async createSession(
71 identifier: string,
72 appPassword: string,
73 ): Promise<Result<AtpSessionData>> {
74 const agent = new AtpAgent({
75 service: ATPROTO_SERVICE_ENDPOINTS.AUTHENTICATED_BSKY_SERVICE,
76 });
77
78 try {
79 await agent.login({
80 identifier,
81 password: appPassword,
82 });
83 const session = agent.session;
84 if (!session) {
85 return err(
86 new Error(`Failed to create session for identifier: ${identifier}`),
87 );
88 }
89 const saveResult = await this.appPasswordSessionRepository.saveSession(
90 session.did,
91 { session, appPassword },
92 );
93 if (saveResult.isErr()) {
94 return err(
95 new Error(
96 `Failed to save session after login for identifier: ${identifier}, error: ${saveResult.error.message}`,
97 ),
98 );
99 }
100 return ok(session);
101 } catch (error) {
102 return err(
103 new Error(
104 `Failed to create session for identifier: ${identifier}, error: ${
105 error instanceof Error ? error.message : String(error)
106 }`,
107 ),
108 );
109 }
110 }
111}