From 51be21da7449f8160cb5a0dd94cd5e39f14b3585 Mon Sep 17 00:00:00 2001 From: Max Lampert Date: Tue, 28 Jul 2026 16:02:43 -0700 Subject: [PATCH] mobile: don't downgrade wss:// relay URLs to plaintext ws:// RelayConfig.wsUrl derived the WebSocket scheme with `uri.scheme == 'https' ? 'wss' : 'ws'`, mapping every non-https scheme (including wss) to plaintext ws, so a base URL already using wss:// was silently downgraded to ws://. Invite-joined communities store their relay as wss://, so invited members' connections lost transport encryption and failed outright against TLS-only relays, making the community unusable for them. Map the scheme explicitly (https/wss -> wss, http/ws -> ws, other schemes pass through unchanged); only the wss -> ws downgrade changes. Co-Authored-By: Claude Opus 4.8 Signed-off-by: Max Lampert --- mobile/lib/shared/relay/relay_provider.dart | 12 +++++-- .../shared/relay/relay_provider_test.dart | 31 +++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 mobile/test/shared/relay/relay_provider_test.dart 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', + ); + }); + }); +}