mirror of
https://github.com/block/buzz.git
synced 2026-10-27 04:30:52 +00:00
## Summary Enter a desktop-only, random six-digit code on mobile to pair without a desktop confirmation click. Verification uses the encrypted session, limits guesses to five, and never treats the QR-derived transcript as approval. Older clients retain desktop confirmation. Adds digit-only error shakes and native haptics, a separate biometrics choice, and a direct community-icon arrival animation. Fixes dropped early offers and resets completed pairing so reopening or signing out cannot get stuck. ### Related issue Related: #8060; no duplicate code-entry PR found. <img width="780" height="1688" alt="01-enter-code" src="https://github.com/user-attachments/assets/a99ced3e-4305-4ac6-a6cc-d598ced423e7" /> <img width="780" height="1688" alt="02-incorrect-code" src="https://github.com/user-attachments/assets/19cdb109-1f10-40a9-a661-1d7ba89c81c1" /> <img width="780" height="1688" alt="03-biometrics" src="https://github.com/user-attachments/assets/af1b2924-9ab6-4d1a-b4b1-fccfdf446404" /> <img width="780" height="1688" alt="04-community-loading" src="https://github.com/user-attachments/assets/84145d82-843a-4c25-aef7-6492fc241d4b" /> <img width="897" height="388" alt="05-desktop-code" src="https://github.com/user-attachments/assets/d8a2e469-7e42-466a-988f-bab6b1df85fc" /> ### Release order Deploy the updated pairing relay first, then desktop, then mobile. The relay now allows eight events per connection (seven for a successful fifth guess plus room for cancellation), with a corresponding bounded delivery budget. Existing clients remain supported; older client combinations keep desktop confirmation. ### Testing - 103 mobile pairing tests, 270 core tests, 51 relay integration tests, and the new real-relay fifth-guess flow passed. - The real-relay regression verifies proof, decrypted identity import, and completion after four wrong guesses. Restoring the old six-event cap makes it fail. - A paused-clock desktop regression confirms slow readiness cannot extend the visible QR beyond protocol expiry. Mobile regression covers typing before negotiation completes. - Core/pair-relay Clippy and full native desktop Clippy/workspace tests passed. Mobile analysis passed; broad mobile runs each hit an unrelated failure (a gateway-script timeout and a native-menu missing-plugin error), and both affected test files passed separately. - Repository-wide Rust testing exhausted local disk space while compiling unrelated packages. The final push omitted that resource-blocked hook and avoided repeating the desktop/mobile runs above; hosted CI remains required. --------- Signed-off-by: kenny lopez <klopez4212@gmail.com>
75 lines
2.4 KiB
Dart
75 lines
2.4 KiB
Dart
part of 'pairing_provider.dart';
|
|
|
|
String _friendlyErrorMessage(Object error) {
|
|
final message = error.toString();
|
|
if (message.contains('SocketException') ||
|
|
message.contains('Connection refused') ||
|
|
message.contains('Network is unreachable') ||
|
|
message.contains('No route to host') ||
|
|
message.contains('Failed to connect')) {
|
|
return 'Could not reach the pairing relay. Check your internet '
|
|
'connection and VPN, then try again.';
|
|
}
|
|
if (error is PairingAuthException) {
|
|
return 'The pairing relay rejected authentication. Try creating a new '
|
|
'pairing code.';
|
|
}
|
|
if (error is StateError ||
|
|
message.contains('Null check operator used on a null value')) {
|
|
return 'Pairing stopped because of an internal error. Please try again.';
|
|
}
|
|
if (message.contains('HandshakeException') ||
|
|
message.contains('CERTIFICATE_VERIFY_FAILED')) {
|
|
return 'Secure connection failed. Check your network settings '
|
|
'and try again.';
|
|
}
|
|
if (message.contains('TimeoutException') || message.contains('timed out')) {
|
|
return 'Connection timed out. Check your internet connection and '
|
|
'try again.';
|
|
}
|
|
return 'Connection failed. Please check your internet connection '
|
|
'and try again.';
|
|
}
|
|
|
|
void _validateRelayUrl(String url) {
|
|
final uri = Uri.parse(url);
|
|
|
|
if (!kDebugMode && uri.scheme != 'https') {
|
|
throw const FormatException('Relay URL must use HTTPS');
|
|
}
|
|
if (uri.scheme != 'http' && uri.scheme != 'https') {
|
|
throw FormatException('Invalid URL scheme: ${uri.scheme}');
|
|
}
|
|
|
|
final host = uri.host.toLowerCase();
|
|
if (host == 'localhost' || host == '127.0.0.1' || host == '::1') {
|
|
if (!kDebugMode) {
|
|
throw const FormatException('Relay URL cannot target localhost');
|
|
}
|
|
return;
|
|
}
|
|
|
|
final ip = Uri.tryParse('http://$host')?.host ?? host;
|
|
if (_isPrivateHost(ip)) {
|
|
throw const FormatException(
|
|
'Relay URL cannot target private network addresses',
|
|
);
|
|
}
|
|
}
|
|
|
|
bool _isPrivateHost(String host) {
|
|
final parts = host.split('.');
|
|
if (parts.length != 4) return false;
|
|
final octets = parts.map(int.tryParse).toList();
|
|
if (octets.any((o) => o == null)) return false;
|
|
|
|
final a = octets[0]!;
|
|
final b = octets[1]!;
|
|
|
|
if (a == 10) return true;
|
|
if (a == 172 && b >= 16 && b <= 31) return true;
|
|
if (a == 192 && b == 168) return true;
|
|
if (a == 169 && b == 254) return true;
|
|
return false;
|
|
}
|