import type { Cookie } from 'playwright'; import type { MatrixClient } from './chat/matrix/client.js'; import type { MatrixCredentials } from './chat/matrix/types.js'; import type { AccountAuthContext } from './account-auth-context.js'; import { assertMatchingAccountIdentity, normalizeRedditAccountId, type RedditAccountIdentity, } from './account-auth-context.js'; interface BrowserSessionTarget { importRedditCookies(cookies: Cookie[]): Promise; isSessionValid(): Promise; getAuthenticatedIdentity(): Promise; } interface ChatSessionTarget { setMatrixClient(client: MatrixClient): void; switchAccount?(accountId: string): Promise; } type MatrixClientFactory = (credentials: MatrixCredentials) => MatrixClient; type CredentialStore = (credentials: MatrixCredentials) => void; export interface VerifiedRedditSessionStore { save(input: { accountId: string; redditUsername: string; matrixUserId: string; cookies: Cookie[]; credentials: MatrixCredentials; }): void; } export class RedditSessionImporter { constructor( private readonly browser: BrowserSessionTarget, private readonly chat: ChatSessionTarget, private readonly createMatrixClient: MatrixClientFactory, private readonly saveCredentials: CredentialStore, private readonly accountAuth: AccountAuthContext, private readonly sessionStore?: VerifiedRedditSessionStore, ) {} async import(cookies: Cookie[], credentials: MatrixCredentials): Promise<{ accountId: string; redditUsername: string; matrixUserId: string; }> { const homeserver = new URL(credentials.homeserver); if ( homeserver.protocol !== 'https:' || !/^matrix\.(?:redditspace|reddit)\.com$/i.test(homeserver.hostname) ) { throw new Error('Invalid Reddit Matrix homeserver'); } const matrixClient = this.createMatrixClient(credentials); const userId = await matrixClient.whoami(); if (!userId || userId !== credentials.userId) { throw new Error('Matrix credentials did not verify the exported account'); } await this.browser.importRedditCookies(cookies); let redditIdentity: RedditAccountIdentity; try { if (!(await this.browser.isSessionValid())) { throw new Error('Imported cookies did not authenticate with Reddit'); } const identity = await this.browser.getAuthenticatedIdentity(); if (!identity) throw new Error('Reddit did not return an authenticated account identity'); redditIdentity = identity; assertMatchingAccountIdentity({ accountId: identity.accountId, redditUsername: identity.username, matrixUserId: userId, }); } catch (error) { this.accountAuth.invalidate(error instanceof Error ? error.message : String(error)); throw error; } this.saveCredentials(credentials); this.chat.setMatrixClient(matrixClient); const account = { accountId: userId, redditUsername: redditIdentity.username, matrixUserId: userId, }; this.accountAuth.markAuthenticated({ accountId: normalizeRedditAccountId(redditIdentity.accountId), redditUsername: redditIdentity.username, matrixUserId: userId, }); this.sessionStore?.save({ ...account, cookies, credentials }); await this.chat.switchAccount?.(account.accountId); return account; } }