Skip to content

WebSocketChannelException: Instance of 'WebSocketException' When Connecting to wss:// Endpoint #1780

@RensithUdara

Description

@RensithUdara

I'm encountering a persistent WebSocketChannelException: Instance of 'WebSocketException' when attempting to connect to a WebSocket server at wss://api.cloudcostume.click/ws/driver-location// using the web_socket_channel package in a Flutter app. The error occurs during the WebSocketChannel.connect call, and the stack trace lacks detailed information about the underlying cause (e.g., SSL issues, connection refusal, or server misconfiguration). Despite implementing network checks, location permission checks, and a reconnection mechanism, the issue persists.
The WebSocket is intended to send real-time location updates from a mobile device (using geolocator) to the server. The connection fails consistently, and the error message is generic, making it difficult to diagnose. I suspect it could be related to SSL/TLS issues, server unavailability, or missing headers, but I need assistance to pinpoint the root cause and resolve it.
Steps to Reproduce

Create a Flutter project with the following dependencies in pubspec.yaml:
dependencies:
web_socket_channel: ^3.0.1
geolocator: ^13.0.1

Use the following DriverTripLocationService class to establish a WebSocket connection and send location updates:
import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:geolocator/geolocator.dart';
import 'package:web_socket_channel/web_socket_channel.dart';

class DriverTripLocationService {
final String driverId;
late WebSocketChannel _channel;
Timer? _locationTimer;
bool _isConnected = false;
int _reconnectAttempts = 0;
static const int MAX_RECONNECT_ATTEMPTS = 5;
Timer? _retryTimer;

DriverTripLocationService({required this.driverId});

Future _checkNetworkConnectivity() async {
try {
final result = await InternetAddress.lookup('google.com').timeout(
const Duration(seconds: 5),
onTimeout: () => throw SocketException('Network check timed out'),
);
return result.isNotEmpty && result[0].rawAddress.isNotEmpty;
} catch (e, stackTrace) {
print("⚠️ Network check failed: $e");
print("Stack trace: $stackTrace");
return false;
}
}

Future _checkLocationPermissions() async {
try {
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
print("⚠️ Location services are disabled.");
return false;
}
LocationPermission permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
print("⚠️ Location permission denied.");
return false;
}
}
if (permission == LocationPermission.deniedForever) {
print("⚠️ Location permission permanently denied.");
return false;
}
return true;
} catch (e, stackTrace) {
print("⚠️ Permission check failed: $e");
print("Stack trace: $stackTrace");
return false;
}
}

void connect() async {
if (!(await _checkNetworkConnectivity())) {
print("⚠️ No network connection. Retrying in ${_reconnectAttempts + 1} attempt(s).");
_isConnected = false;
_reconnect();
return;
}

try {
  final uri = Uri.parse('wss://api.cloudcostume.click/ws/driver-location/$driverId/');
  print("🔌 Attempting to connect to $uri with driverId: $driverId");
  // Temporary SSL bypass for debugging (insecure, for testing only)
  final client = HttpClient()..badCertificateCallback = (cert, host, port) => true;
  _channel = WebSocketChannel.connect(uri, customClient: client);
  await _channel.ready;
  _isConnected = true;
  _reconnectAttempts = 0;
  _retryTimer?.cancel();

  _channel.stream.listen(
    (message) {
      print("📥 Received: $message");
    },
    onError: (error, stackTrace) {
      print("⚠️ WebSocket error: $error");
      print("Stack trace: $stackTrace");
      _isConnected = false;
      _reconnect();
    },
  );
  print("✅ WebSocket connected to $uri");
} catch (e, stackTrace) {
  print("⚠️ Connection failed: $e");
  print("Stack trace: $stackTrace");
  _isConnected = false;
  _reconnect();
}

}

void _reconnect() async {
_retryTimer?.cancel();
if (_reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
print("🚫 Max reconnect attempts reached. Scheduling retry in 30 seconds.");
_retryTimer = Timer(const Duration(seconds: 30), () {
_reconnectAttempts = 0;
print("🔄 Starting new connection cycle");
connect();
});
return;
}
_reconnectAttempts++;
await Future.delayed(Duration(seconds: 5 * _reconnectAttempts));
if (!_isConnected) {
print("🔄 Attempting to reconnect (attempt $_reconnectAttempts)");
connect();
}
}

void startSendingLocation() async {
if (!(await _checkLocationPermissions())) {
print("⚠️ Cannot start sending location due to permission issues.");
return;
}

if (!_isConnected) {
  print("⚠️ Cannot send location. Not connected. Initiating connection.");
  connect();
  return;
}

_locationTimer?.cancel();
_locationTimer = Timer.periodic(const Duration(seconds: 1), (timer) async {
  try {
    final position = await Geolocator.getCurrentPosition(
      desiredAccuracy: LocationAccuracy.high,
    ).timeout(
      const Duration(seconds: 5),
      onTimeout: () => throw Exception('Location fetch timed out'),
    );

    final data = jsonEncode({
      'latitude': position.latitude,
      'longitude': position.longitude,
      'timestamp': DateTime.now().toIso8601String(),
    });

    _channel.sink.add(data);
    print("📤 Sent location: $data");
  } catch (e, stackTrace) {
    print("⚠️ Failed to get or send location: $e");
    print("Stack trace: $stackTrace");
  }
});

print("📡 Started sending location every 1 second");

}
}

Instantiate the service with a valid driverId and start sending location updates:
final service = DriverTripLocationService(driverId: "12345");
service.startSendingLocation();

Run the app on a physical device or emulator with internet access and location permissions enabled.

Observe the console output, which shows:
🔌 Attempting to connect to wss://api.cloudcostume.click/ws/driver-location/12345/ with driverId: 12345
⚠️ Connection failed: WebSocketChannelException: Instance of 'WebSocketException'
Stack trace: #0 WebSocketChannel.connect (package:web_socket_channel/web_socket_channel.dart:123)
...
⚠️ Attempting to reconnect (attempt 1)

Expected Behavior
The WebSocket should connect successfully to wss://api.cloudcostume.click/ws/driver-location//, and location updates should be sent every 1 second. The service should handle transient errors by retrying the connection and continue running indefinitely.
Actual Behavior
The connection fails with WebSocketChannelException: Instance of 'WebSocketException', and the stack trace is generic, lacking details about the underlying cause. The service retries up to 5 times, then schedules a new attempt after 30 seconds, but the error persists. No messages are received from the server, and location updates are not sent.
Environment

Flutter Version: [Run flutter doctor -v and paste the output here]
Dart Version: [Included in flutter doctor -v output]
Package Versions:
web_socket_channel: ^3.0.1
geolocator: ^13.0.1

Platform: [e.g., Android 13 (API 33) emulator, iOS 16.2 physical device]
Network: [e.g., Wi-Fi, mobile data]
Server: The WebSocket server is hosted at wss://api.cloudcostume.click/ws/driver-location//. [Add any known details about the server, e.g., whether it requires authentication headers or specific protocols]

Logs
flutter: 🔌 Attempting to connect to wss://api.cloudcostume.click/ws/driver-location/12345/ with driverId: 12345
flutter: ⚠️ Connection failed: WebSocketChannelException: Instance of 'WebSocketException'
flutter: Stack trace: [Paste full stack trace here]
flutter: 🔄 Attempting to reconnect (attempt 1)
...
flutter: 🚫 Max reconnect attempts reached. Scheduling retry in 30 seconds.
flutter: 🔄 Starting new connection cycle

Additional Context

The WebSocket uses the wss:// scheme, suggesting SSL/TLS is required. I suspect the issue might be related to an invalid or self-signed SSL certificate, as similar issues have been reported (e.g., #1561). To test this, I added a temporary SSL bypass (insecure, for debugging only) using HttpClient.badCertificateCallback, but the error persists.
The server might require authentication headers (e.g., Authorization: Bearer ), but I lack documentation on the server’s requirements.
I tested network connectivity using InternetAddress.lookup('google.com'), and the device has a stable connection.
Location permissions are checked using geolocator, and no issues are reported.
I removed the onDone callback to avoid handling server-initiated closures, as per my requirements, but this might prevent reconnection on server restarts. I’m open to re-adding it if needed.
Related issues:
#1561 (Connection not upgraded to WebSocket)
#1619 (Uncatchable errors in IOWebSocketChannel.connect)
https://github.com/dart-lang/web_socket_channel/issues/378 (JWT authentication issues)

Questions

How can I extract more details from WebSocketChannelException to identify the root cause (e.g., SSL handshake failure, connection refusal, or server misconfiguration)?
Is the wss:// URI correctly formatted, or could the driverId be causing issues?
Should I add authentication headers or specific WebSocket protocols? If so, how can I configure them with WebSocketChannel.connect?
Is there a recommended way to handle persistent connection failures without the onDone callback to ensure continuous operation?

Possible Solutions Attempted

Added network and permission checks to rule out device-side issues.
Implemented a reconnection mechanism with exponential backoff and periodic retries after max attempts.
Added await _channel.ready to catch handshake errors early.
Tested with an insecure SSL bypass (temporary, for debugging).
Verified driverId is a valid string (e.g., "12345").

Any guidance on debugging this further or resolving the WebSocketChannelException would be greatly appreciated! Please let me know if you need additional logs or code.

Metadata

Metadata

Assignees

No one assigned

    Labels

    package:httptype-bugIncorrect behavior (everything from a crash to more subtle misbehavior)

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions