Files
RedditBot/src/services/reddit-session-importer.ts
T
jjbrunton 48492d5238 Merge remote-tracking branch 'origin/main'
# Conflicts:
#	src/browser/client.ts
#	src/services/reddit-session-importer.ts
#	src/web/routes/conversations.ts
2026-08-08 20:17:19 +01:00

99 lines
3.3 KiB
TypeScript

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<void>;
isSessionValid(): Promise<boolean>;
getAuthenticatedIdentity(): Promise<RedditAccountIdentity | null>;
}
interface ChatSessionTarget {
setMatrixClient(client: MatrixClient): void;
switchAccount?(accountId: string): Promise<void>;
}
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;
}
}