diff --git a/mobile/lib/shared/relay/relay_provider.dart b/mobile/lib/shared/relay/relay_provider.dart index 97b88dd3d..1799f8c2b 100644 --- a/mobile/lib/shared/relay/relay_provider.dart +++ b/mobile/lib/shared/relay/relay_provider.dart @@ -17,10 +17,18 @@ class RelayConfig { const RelayConfig({required this.baseUrl, this.nsec}); - /// Derive the websocket URL from the HTTP base URL. + /// Derive the websocket URL from the base URL. + /// + /// A base that is already `ws`/`wss` (e.g. an invite-joined community's + /// `wss://` relay) passes through unchanged — mapping it to `ws` would be a + /// silent TLS downgrade that also fails against TLS-only relays. String get wsUrl { final uri = Uri.parse(baseUrl); - final scheme = uri.scheme == 'https' ? 'wss' : 'ws'; + final scheme = switch (uri.scheme) { + 'https' || 'wss' => 'wss', + 'http' || 'ws' => 'ws', + _ => uri.scheme, + }; return uri.replace(scheme: scheme).toString(); } } diff --git a/mobile/test/shared/relay/relay_provider_test.dart b/mobile/test/shared/relay/relay_provider_test.dart new file mode 100644 index 000000000..06b8e4260 --- /dev/null +++ b/mobile/test/shared/relay/relay_provider_test.dart @@ -0,0 +1,31 @@ +import 'package:flutter_test/flutter_test.dart'; + +import 'package:buzz/shared/relay/relay_provider.dart'; + +void main() { + group('RelayConfig.wsUrl', () { + test('maps http-family schemes to their websocket equivalent', () { + expect( + const RelayConfig(baseUrl: 'https://relay.example').wsUrl, + 'wss://relay.example', + ); + expect( + const RelayConfig(baseUrl: 'http://localhost:3000').wsUrl, + 'ws://localhost:3000', + ); + }); + + test('passes ws-family schemes through without downgrading', () { + // An invite-joined community stores relayUrl as wss://…; downgrading it to + // plaintext ws:// is a TLS downgrade and fails against TLS-only relays. + expect( + const RelayConfig(baseUrl: 'wss://relay.example').wsUrl, + 'wss://relay.example', + ); + expect( + const RelayConfig(baseUrl: 'ws://localhost:3000').wsUrl, + 'ws://localhost:3000', + ); + }); + }); +}