diff --git a/fluxer_api/src/api/instance/InstanceConfigRepository.test.ts b/fluxer_api/src/api/instance/InstanceConfigRepository.test.ts index eebe45ff9..1a57cf90a 100644 --- a/fluxer_api/src/api/instance/InstanceConfigRepository.test.ts +++ b/fluxer_api/src/api/instance/InstanceConfigRepository.test.ts @@ -81,6 +81,40 @@ describe('InstanceConfigRepository', () => { }); }); + it('reuses the memoized effective bluesky config until the integrations blob changes', async () => { + const executor = new CountingInMemoryCassandraQueryExecutor(); + setCassandraQueryExecutorForTesting(executor); + const kvProvider = new MockKVProvider(); + const repository = createRepository(kvProvider); + + const first = await repository.getEffectiveBlueskyConfig(); + expect(await repository.getEffectiveBlueskyConfig()).toBe(first); + + await repository.setInstanceIntegrationsConfig({bluesky: {client_name: 'Memoized Instance'}}); + + const updated = await repository.getEffectiveBlueskyConfig(); + expect(updated).not.toBe(first); + expect(updated.client_name).toBe('Memoized Instance'); + expect(await repository.getEffectiveBlueskyConfig()).toBe(updated); + }); + + it('recomputes the effective bluesky config after another repository publishes an integrations update', async () => { + const executor = new CountingInMemoryCassandraQueryExecutor(); + setCassandraQueryExecutorForTesting(executor); + const kvProvider = new MockKVProvider(); + const reader = createRepository(kvProvider); + const writer = createRepository(kvProvider); + + const before = await reader.getEffectiveBlueskyConfig(); + expect(await reader.getEffectiveBlueskyConfig()).toBe(before); + + await writer.setInstanceIntegrationsConfig({bluesky: {client_name: 'Refreshed Instance'}}); + + await vi.waitFor(async () => { + expect((await reader.getEffectiveBlueskyConfig()).client_name).toBe('Refreshed Instance'); + }); + }); + it('uses the registration URL id as the admin-visible registration code', async () => { const executor = new CountingInMemoryCassandraQueryExecutor(); setCassandraQueryExecutorForTesting(executor); diff --git a/fluxer_api/src/api/instance/InstanceConfigRepository.ts b/fluxer_api/src/api/instance/InstanceConfigRepository.ts index 7826b9abb..7fda95978 100644 --- a/fluxer_api/src/api/instance/InstanceConfigRepository.ts +++ b/fluxer_api/src/api/instance/InstanceConfigRepository.ts @@ -800,6 +800,8 @@ export class InstanceConfigRepository { private subscriberInitialized = false; private subscriberInitializationPromise: Promise | null = null; private messageHandler: ((channel: string, message: string) => void) | null = null; + private effectiveBlueskyConfig: BlueskyOAuthConfig | null = null; + private effectiveBlueskyConfigSource: string | null = null; constructor(kvClient: IKVProvider | null = null) { this.kvClient = kvClient; @@ -1303,14 +1305,19 @@ export class InstanceConfigRepository { } async getEffectiveBlueskyConfig(): Promise { - const integrations = await this.getInstanceIntegrationsConfig(); + const raw = await this.getConfig(INSTANCE_INTEGRATIONS_CONFIG_KEY); + const memoized = this.effectiveBlueskyConfig; + if (memoized && this.effectiveBlueskyConfigSource === raw) { + return memoized; + } + const integrations = normalizeInstanceIntegrationsConfig(raw ? parseJsonRecord(raw) : null); const runtimeKeys = integrations.bluesky.keys.flatMap((key): Array => { if (!key.private_key) return []; return [{kid: key.kid, private_key: key.private_key}]; }); const keys = runtimeKeys.length > 0 ? runtimeKeys : Config.auth.bluesky.keys; const enabled = (integrations.bluesky.enabled ?? Config.auth.bluesky.enabled) && keys.length > 0; - return { + const effective: BlueskyOAuthConfig = { ...Config.auth.bluesky, enabled, client_name: integrations.bluesky.client_name ?? Config.auth.bluesky.client_name, @@ -1320,6 +1327,9 @@ export class InstanceConfigRepository { policy_uri: integrations.bluesky.policy_uri ?? Config.auth.bluesky.policy_uri, keys, }; + this.effectiveBlueskyConfigSource = raw; + this.effectiveBlueskyConfig = effective; + return effective; } async getInstanceIntegrationsAdminConfig(): Promise { diff --git a/fluxer_api/src/api/middleware/ServiceRegistry.ts b/fluxer_api/src/api/middleware/ServiceRegistry.ts index 14d7722c0..5de842309 100644 --- a/fluxer_api/src/api/middleware/ServiceRegistry.ts +++ b/fluxer_api/src/api/middleware/ServiceRegistry.ts @@ -181,6 +181,8 @@ let _blueskyOAuthService: IBlueskyOAuthService | undefined; let _blueskyOAuthInitializationPromise: Promise | null = null; let _blueskyOAuthConfigSignature: string | null = null; let _blueskyOAuthInitializationSignature: string | null = null; +let _blueskyOAuthSignatureSource: BlueskyOAuthConfig | null = null; +let _blueskyOAuthSignatureValue: string | null = null; let _disabledBlueskyOAuthService: DisabledBlueskyOAuthService | undefined; export function setInjectedBlueskyOAuthService(service: IBlueskyOAuthService | undefined): void { @@ -195,7 +197,13 @@ function getDisabledBlueskyOAuthService(): DisabledBlueskyOAuthService { } function getBlueskyOAuthConfigSignature(config: BlueskyOAuthConfig): string { - return JSON.stringify(config); + if (_blueskyOAuthSignatureSource === config && _blueskyOAuthSignatureValue !== null) { + return _blueskyOAuthSignatureValue; + } + const signature = JSON.stringify(config); + _blueskyOAuthSignatureSource = config; + _blueskyOAuthSignatureValue = signature; + return signature; } export async function resolveBlueskyOAuthService( @@ -315,6 +323,8 @@ export function resetServiceRegistryForTesting(): void { _blueskyOAuthInitializationPromise = null; _blueskyOAuthConfigSignature = null; _blueskyOAuthInitializationSignature = null; + _blueskyOAuthSignatureSource = null; + _blueskyOAuthSignatureValue = null; _disabledBlueskyOAuthService = undefined; voiceTopology = null; voiceAvailabilityService = null;