76 lines
2.7 KiB
TypeScript
76 lines
2.7 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;
|
|
}
|
|
|
|
type MatrixClientFactory = (credentials: MatrixCredentials) => MatrixClient;
|
|
type CredentialStore = (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,
|
|
) {}
|
|
|
|
async import(cookies: Cookie[], credentials: MatrixCredentials): Promise<void> {
|
|
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);
|
|
this.accountAuth.markAuthenticated({
|
|
accountId: normalizeRedditAccountId(redditIdentity.accountId),
|
|
redditUsername: redditIdentity.username,
|
|
matrixUserId: userId,
|
|
});
|
|
}
|
|
}
|