socket_io_client_new
A fixed fork of socket_io_client that resolves the port 0 issue affecting WebSocket connections.
Version Info
| socket_io_client_new | Socket.io Server |
|---|---|
| v0.9.* ~ v1.* | v2.* |
| v2.* | v3.* ~ v4.6.* |
| v3.* | v4.7.* ~ v4.* |
Note: If connecting to a Socket.IO v2.x server, you need to add
allowEIO3: truein your server configuration:const io = require('socket.io')(server, { allowEIO3: true // Required for older clients });
The Problem (Original Package Bug)
The original socket_io_client package on pub.flutter-io.cn has a critical bug:
When Dart's Uri.parse() parses a URL without an explicit port (like https://api.example.com), it returns port = 0 instead of the default port (443 for HTTPS, 80 for HTTP).
The original package uses this port directly, generating invalid WebSocket URLs:
// What you expect:
wss://api.example.com/socket.io/?EIO=4&transport=websocket
// What original package generates (BROKEN):
wss://api.example.com:0/socket.io/?EIO=4&transport=websocket
^^
This :0 breaks the connection!
This causes WebSocket connection failed errors in production when connecting to servers without explicit ports.
The Fix
This package fixes the issue by:
- Detecting when
Uri.parse()returnsport = 0 - Setting the correct default port (443 for HTTPS/WSS, 80 for HTTP/WS)
- Always including a valid port in the WebSocket URL
// This package generates correctly:
wss://api.example.com:443/socket.io/?EIO=4&transport=websocket
Installation
dependencies:
socket_io_client_new: ^1.0.0
Usage
Dart Client
import 'package:socket_io_client_new/socket_io_client_new.dart' as IO;
void main() {
IO.Socket socket = IO.io('http://localhost:3000');
socket.onConnect((_) {
print('connect');
socket.emit('msg', 'test');
});
socket.on('event', (data) => print(data));
socket.onDisconnect((_) => print('disconnect'));
socket.on('fromServer', (_) => print(_));
}
Connect Manually
To connect the socket manually, set disableAutoConnect() and call .connect().
IO.Socket socket = IO.io(
'http://localhost:3000',
IO.OptionBuilder()
.setTransports(['websocket']) // for Flutter or Dart VM
.disableAutoConnect() // disable auto-connection
.setExtraHeaders({'foo': 'bar'}) // optional
.build()
);
socket.connect();
Note: .connect() should not be called if autoConnect is enabled (default), as this will cause all event handlers to get registered/fired twice. See Issue #33.
Update Extra Headers
IO.Socket socket = ... // Create socket.
socket.io.options['extraHeaders'] = {'foo': 'bar'}; // Update extra headers.
socket.io..disconnect()..connect(); // Reconnect the socket manually.
Emit with Acknowledgement
socket.onConnect((_) {
print('connect');
socket.emitWithAck('msg', 'init', ack: (data) {
print('ack $data');
if (data != null) {
print('from server $data');
} else {
print('Null');
}
});
});
Socket Connection Events
These events can be listened on:
socket.onConnect((_) => print('connect'));
socket.onConnectError((err) => print('connect_error: $err'));
socket.onDisconnect((_) => print('disconnect'));
socket.onError((err) => print('error: $err'));
socket.onReconnect((_) => print('reconnect'));
socket.onReconnectAttempt((attempt) => print('reconnect_attempt: $attempt'));
socket.onReconnectFailed((_) => print('reconnect_failed'));
socket.onReconnectError((err) => print('reconnect_error: $err'));
socket.onPing((_) => print('ping'));
socket.onPong((_) => print('pong'));
Acknowledge with the Server
socket.on('eventName', (data) {
final dataList = data as List;
final ack = dataList.last as Function;
ack(null);
});
Usage (Flutter)
In Flutter (not Flutter Web), it only works with dart:io websocket, not with dart:html websocket or Ajax (XHR). So you must use setTransports(['websocket']):
IO.Socket socket = IO.io(
'http://localhost:3000',
IO.OptionBuilder()
.setTransports(['websocket']) // for Flutter or Dart VM
.setExtraHeaders({'foo': 'bar'}) // optional
.build()
);
Usage with Stream and StreamBuilder
import 'dart:async';
import 'package:socket_io_client_new/socket_io_client_new.dart' as IO;
// STEP 1: Stream setup
class StreamSocket {
final _socketResponse = StreamController<String>();
void Function(String) get addResponse => _socketResponse.sink.add;
Stream<String> get getResponse => _socketResponse.stream;
void dispose() {
_socketResponse.close();
}
}
StreamSocket streamSocket = StreamSocket();
// STEP 2: Connect and listen
void connectAndListen() {
IO.Socket socket = IO.io(
'http://localhost:3000',
IO.OptionBuilder().setTransports(['websocket']).build()
);
socket.onConnect((_) {
print('connect');
socket.emit('msg', 'test');
});
// When event received, add data to stream
socket.on('event', (data) => streamSocket.addResponse(data));
socket.onDisconnect((_) => print('disconnect'));
}
// STEP 3: Build widgets with StreamBuilder
class BuildWithSocketStream extends StatelessWidget {
@override
Widget build(BuildContext context) {
return StreamBuilder(
stream: streamSocket.getResponse,
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
return Text(snapshot.data ?? '');
},
);
}
}
Important: Socket Cache and Subsequent Lookups
There is a design decision related to caching of socket instances.
Lookup Protocol: We reuse the existing instance based on same scheme/port/host.
Both dispose() and destroy() do NOT remove a host from the cache. This can cause subsequent connections to ignore parts of your configuration.
Example
_socket = IO.io(
host,
IO.OptionBuilder()
.setTransports(['websocket'])
.setExtraHeaders({'id': userId, 'name': username})
.disableAutoConnect()
.enableReconnection()
.build()
);
If you try to create the same connection with updated extraHeaders, the old connection would be returned with old headers.
Solutions:
- Use
enableForceNew()ordisableMultiplex()in OptionBuilder - Or update extra headers manually (see "Update Extra Headers" section above)
Socket Service Pattern
A recommended pattern for managing socket connections:
import 'dart:async';
import 'package:socket_io_client_new/socket_io_client_new.dart' as IO;
class SocketService {
static SocketService? _instance;
IO.Socket? _socket;
bool _isConnected = false;
final _connectionController = StreamController<bool>.broadcast();
Stream<bool> get connectionStream => _connectionController.stream;
bool get isConnected => _isConnected;
factory SocketService() {
_instance ??= SocketService._internal();
return _instance!;
}
SocketService._internal();
void connect(String url) {
if (_isConnected) return;
_socket = IO.io(
url,
IO.OptionBuilder()
.setTransports(['websocket', 'polling'])
.enableAutoConnect()
.enableReconnection()
.setReconnectionAttempts(5)
.build(),
);
_socket!.onConnect((_) {
_isConnected = true;
_connectionController.add(true);
print('Connected');
});
_socket!.onDisconnect((_) {
_isConnected = false;
_connectionController.add(false);
print('Disconnected');
});
_socket!.onConnectError((error) {
print('Connection error: $error');
});
}
void emit(String event, dynamic data) {
_socket?.emit(event, data);
}
void on(String event, Function(dynamic) handler) {
_socket?.on(event, handler);
}
void off(String event) {
_socket?.off(event);
}
void disconnect() {
_socket?.disconnect();
_socket?.dispose();
_socket = null;
_isConnected = false;
}
void dispose() {
disconnect();
_connectionController.close();
}
}
Migration from socket_io_client
Simply change your import:
// Before
import 'package:socket_io_client/socket_io_client.dart' as IO;
// After
import 'package:socket_io_client_new/socket_io_client_new.dart' as IO;
No other code changes required - the API is identical!
Troubleshooting
Cannot connect to "https" server or self-signed certificate
Refer to dart-lang/sdk#34284. Workaround:
class MyHttpOverrides extends HttpOverrides {
@override
HttpClient createHttpClient(SecurityContext? context) {
return super.createHttpClient(context)
..badCertificateCallback =
(X509Certificate cert, String host, int port) => true;
}
}
void main() {
HttpOverrides.global = MyHttpOverrides();
runApp(MyApp());
}
Memory leak issues in iOS when closing socket
Refer to rikulo/socket.io-client-dart#108.
Use socket.dispose() instead of socket.close() or socket.disconnect() to solve the memory leak issue on iOS.
Connect_error on MacOS with SocketException: Connection failed
Refer to flutter/flutter#47606.
Add to macos/Runner/*.entitlements:
<key>com.apple.security.network.client</key>
<true/>
For more details: Flutter Desktop Entitlements
Can't connect on Flutter with Insecure HTTP
HTTP connections are disabled by default on iOS and Android. See Flutter network policy for workarounds.
Changes from Original Package
- Fixed: Port 0 being incorrectly appended to URLs without explicit ports
License
MIT License - see LICENSE file.
Credits
This package is a fork of socket_io_client originally created by Potix Corporation.
Contributors
- Original package: rikulo/socket.io-client-dart
- Port 0 fix: Rugved
Libraries
- socket_io_client_new
- socket_io_client_new.dart