From 1791592ecca94e775dc0955268095e5fa0d5c794 Mon Sep 17 00:00:00 2001 From: "xan.lol" Date: Wed, 15 Apr 2026 16:35:05 -0700 Subject: [PATCH] fix: replicate legacy login agent for oauth fixes embeds not getting thumbnails and such! --- src/lib/link-meta/link-meta.ts | 2 +- .../session/__tests__/oauth-agent-test.ts | 81 +++++++++++++++++++ src/state/session/oauth-agent.ts | 37 ++++++++- 3 files changed, 115 insertions(+), 5 deletions(-) create mode 100644 src/state/session/__tests__/oauth-agent-test.ts diff --git a/src/lib/link-meta/link-meta.ts b/src/lib/link-meta/link-meta.ts index 3d9a695f0..145d79fef 100644 --- a/src/lib/link-meta/link-meta.ts +++ b/src/lib/link-meta/link-meta.ts @@ -74,7 +74,7 @@ export async function getLinkMeta( try { const response = await fetch( - `${LINK_META_PROXY(agent.serviceUrl.toString() || '')}${encodeURIComponent( + `${LINK_META_PROXY(agent.serviceUrl?.toString() || '')}${encodeURIComponent( url, )}`, {signal: controller.signal}, diff --git a/src/state/session/__tests__/oauth-agent-test.ts b/src/state/session/__tests__/oauth-agent-test.ts new file mode 100644 index 000000000..890fef29f --- /dev/null +++ b/src/state/session/__tests__/oauth-agent-test.ts @@ -0,0 +1,81 @@ +import {type OAuthSession} from '@atproto/oauth-client-browser' +import {describe, expect, it, jest} from '@jest/globals' + +jest.mock('../agent', () => ({ + sessionAccountToSession(account: {did: string; handle: string}) { + return { + accessJwt: '', + did: account.did, + emailAuthFactor: false, + emailConfirmed: false, + handle: account.handle, + refreshJwt: '', + active: true, + } + }, +})) + +jest.mock('../moderation', () => ({ + configureModerationForAccount() { + return Promise.resolve() + }, +})) + +jest.mock('../oauth-web-client', () => ({ + getWebOAuthClient() { + throw new Error('not used in this test') + }, +})) + +import {OauthBskyAppAgent} from '../oauth-agent' + +function createOAuthSession(): OAuthSession { + return { + did: 'did:plc:alice', + serverMetadata: { + issuer: 'https://bsky.social', + }, + fetchHandler() { + throw new Error('not implemented in test') + }, + } as unknown as OAuthSession +} + +describe('OauthBskyAppAgent', () => { + it('preserves service and dispatch urls when cloned and proxied', async () => { + const agent = new OauthBskyAppAgent(createOAuthSession()) + + expect(agent.serviceUrl.toString()).toBe('https://bsky.social/') + expect(agent.dispatchUrl.toString()).toBe('https://bsky.social/') + + await agent.prepare( + { + service: 'https://bsky.social', + did: 'did:plc:alice', + handle: 'alice.test', + active: true, + emailConfirmed: false, + emailAuthFactor: false, + pdsUrl: 'https://alice.pds.example', + isSelfHosted: false, + isOauthSession: true, + }, + Promise.resolve(), + Promise.resolve(), + ) + + expect(agent.serviceUrl.toString()).toBe('https://bsky.social/') + expect(agent.pdsUrl?.toString()).toBe('https://alice.pds.example/') + expect(agent.dispatchUrl.toString()).toBe('https://alice.pds.example/') + + const proxied = agent.withProxy('bsky_fg', 'did:plc:feed') + expect(proxied).toBeInstanceOf(OauthBskyAppAgent) + expect(proxied.serviceUrl.toString()).toBe('https://bsky.social/') + expect(proxied.dispatchUrl.toString()).toBe('https://alice.pds.example/') + + const pdsAgent = agent.cloneWithoutProxy() + expect(pdsAgent.proxy).toBeUndefined() + expect(pdsAgent.serviceUrl.toString()).toBe('https://bsky.social/') + expect(pdsAgent.dispatchUrl.toString()).toBe('https://alice.pds.example/') + }) +}) diff --git a/src/state/session/oauth-agent.ts b/src/state/session/oauth-agent.ts index e318e39d0..cacc3132c 100644 --- a/src/state/session/oauth-agent.ts +++ b/src/state/session/oauth-agent.ts @@ -108,11 +108,40 @@ export async function oauthAgentAndSessionToSessionAccount( } export class OauthBskyAppAgent extends Agent { + readonly sessionManager: OAuthSession session?: AtpSessionData - dispatchUrl?: string + private _serviceUrl: URL + private _pdsUrl?: URL constructor(session: OAuthSession) { super(session) + this.sessionManager = session + this._serviceUrl = new URL(session.serverMetadata.issuer) + } + + clone(): this { + const cloned = this.copyInto(new OauthBskyAppAgent(this.sessionManager)) + cloned.session = this.session + cloned._serviceUrl = this._serviceUrl + cloned._pdsUrl = this._pdsUrl + return cloned as this + } + + get serviceUrl() { + return this._serviceUrl + } + + get pdsUrl() { + return this._pdsUrl + } + + get dispatchUrl() { + return this.pdsUrl || this.serviceUrl + } + + /** @deprecated use {@link serviceUrl} instead */ + get service() { + return this.serviceUrl } async prepare( @@ -121,7 +150,8 @@ export class OauthBskyAppAgent extends Agent { moderation: Promise, ) { this.session = sessionAccountToSession(account) - this.dispatchUrl = account.pdsUrl + this._serviceUrl = new URL(account.service) + this._pdsUrl = account.pdsUrl ? new URL(account.pdsUrl) : undefined this.configureProxy(BLUESKY_PROXY_HEADER.get()) await Promise.all([gates, moderation]) @@ -132,8 +162,7 @@ export class OauthBskyAppAgent extends Agent { dispose() {} cloneWithoutProxy(): OauthBskyAppAgent { - const cloned = new OauthBskyAppAgent(this.sessionManager as OAuthSession) - cloned.session = this.session + const cloned = this.clone() cloned.configureProxy(null) return cloned } -- 2.51.2