Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
## [2.22.1] - 2026-07-14
## [2.22.2] - 2026-07-14
- Added `flush()` to `SSHSocket`, `SSHClient`, and `SSHChannel` to allow force flushing of buffered outgoing data [#183]. Thanks [@vicajilau].

## [2.22.1] - 2026-07-13
- Fixed a keepalive issue where overlapping pings could occur and caught errors during ping execution. Thanks [@vicajilau].

## [2.22.0] - 2026-07-13
## [2.22.0] - 2026-07-03
- Added optional `handshakeTimeout` and `authTimeout` to `SSHClient` to limit connection negotiation and user authentication times [#182]. Thanks [@GT-610].

## [2.21.1] - 2026-07-02
Expand Down
3 changes: 3 additions & 0 deletions lib/src/socket/ssh_socket.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,7 @@ abstract class SSHSocket {
Future<void> close();

void destroy();

/// Force flush any buffered outgoing data.
Future<void> flush() async {}
}
5 changes: 5 additions & 0 deletions lib/src/socket/ssh_socket_io.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ class _SSHNativeSocket implements SSHSocket {
_socket.destroy();
}

@override
Future<void> flush() async {
await _socket.flush();
}

@override
String toString() {
final address = '${_socket.remoteAddress.host}:${_socket.remotePort}';
Expand Down
10 changes: 10 additions & 0 deletions lib/src/ssh_channel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ class SSHChannelController {

SSHChannel get channel => SSHChannel(this);

final Future<void> Function()? onFlush;

SSHChannelController({
required this.localId,
required this.localMaximumPacketSize,
Expand All @@ -39,6 +41,7 @@ class SSHChannelController {
required this.remoteInitialWindowSize,
required this.remoteMaximumPacketSize,
required this.sendMessage,
this.onFlush,
this.printDebug,
}) {
if (remoteInitialWindowSize > 0) {
Expand Down Expand Up @@ -404,6 +407,10 @@ class SSHChannelController {
_remoteWindow -= data.bytes.length;
}
});

Future<void> flush() async {
await onFlush?.call();
}
}

class SSHChannel {
Expand Down Expand Up @@ -434,6 +441,9 @@ class SSHChannel {
sink.add(SSHChannelData(data, type: type));
}

/// Force flush any buffered outgoing data on this channel to the socket.
Future<void> flush() => _controller.flush();

void setRequestHandler(SSHChannelRequestHandler handler) {
_controller._requestHandler = handler;
}
Expand Down
6 changes: 6 additions & 0 deletions lib/src/ssh_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,11 @@ class SSHClient {
_transport.close();
}

/// Force flush any buffered outgoing data to the socket.
Future<void> flush() async {
await _transport.flush();
}

/// Close all channels that are currently open.
void _closeChannels() {
for (final channel in _channels.values) {
Expand Down Expand Up @@ -1376,6 +1381,7 @@ class SSHClient {
remoteInitialWindowSize: remoteInitialWindowSize,
remoteMaximumPacketSize: remoteMaximumPacketSize,
sendMessage: _sendMessage,
onFlush: flush,
printDebug: printDebug,
);

Expand Down
7 changes: 7 additions & 0 deletions lib/src/ssh_forward.dart
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@ class SSHForwardChannel implements SSHSocket {
void destroy() {
_channel.destroy();
}

/// Force flush any buffered outgoing data.
@override
Future<void> flush() async {
await Future.microtask(() {});
await _channel.flush();
}
}

class SSHX11Channel extends SSHForwardChannel {
Expand Down
9 changes: 9 additions & 0 deletions lib/src/ssh_session.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ class SSHSession {
/// be available on the [stdout] and [stderr] streams at this time.
Future<void> get done => _channel.done;

/// The underlying SSH channel.
SSHChannel get channel => _channel;

SSHSession(this._channel) {
_channel.setRequestHandler(_handleRequest);

Expand Down Expand Up @@ -79,6 +82,12 @@ class SSHSession {
stdin.add(data);
}

/// Force flush any buffered stdin data to the remote process.
Future<void> flush() async {
await Future.microtask(() {});
await _channel.flush();
}

/// Inform remote process of the current window size.
void resizeTerminal(
int width,
Expand Down
5 changes: 5 additions & 0 deletions lib/src/ssh_transport.dart
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,11 @@ class SSHTransport {
socket.destroy();
}

/// Force flush any buffered outgoing data to the socket.
Future<void> flush() async {
await socket.flush();
}

/// Subscribes to the underlying socket stream to handle incoming data and status events.
void _initSocket() {
_socketSubscription = socket.stream.listen(
Expand Down
3 changes: 3 additions & 0 deletions test/src/http/http_client_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -300,4 +300,7 @@ class _FakeSocket implements SSHSocket {
}
unawaited(_sinkController.close());
}

@override
Future<void> flush() async {}
}
3 changes: 3 additions & 0 deletions test/src/ssh_auth_abort_error_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ class _FakeSSHSocket implements SSHSocket {
}
unawaited(_inputController.close());
}

@override
Future<void> flush() async {}
}

class _RecordingSink implements StreamSink<List<int>> {
Expand Down
3 changes: 3 additions & 0 deletions test/src/ssh_client_forward_dynamic_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ class _FakeSSHSocket implements SSHSocket {
}
unawaited(_inputController.close());
}

@override
Future<void> flush() async {}
}

class _NoopSink implements StreamSink<List<int>> {
Expand Down
3 changes: 3 additions & 0 deletions test/src/ssh_client_ident_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ class _FakeSSHSocket implements SSHSocket {
}
unawaited(_inputController.close());
}

@override
Future<void> flush() async {}
}

class _RecordingSink implements StreamSink<List<int>> {
Expand Down
3 changes: 3 additions & 0 deletions test/src/ssh_client_run_with_result_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,9 @@ class _FakeSSHSocket implements SSHSocket {
}
unawaited(_inputController.close());
}

@override
Future<void> flush() async {}
}

class _NoopSink implements StreamSink<List<int>> {
Expand Down
29 changes: 29 additions & 0 deletions test/src/ssh_client_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ library;
import 'dart:convert';

import 'package:dartssh2/dartssh2.dart';
import 'package:dartssh2/src/ssh_channel.dart';
import 'package:test/test.dart';

import '../test_utils.dart';
Expand Down Expand Up @@ -264,4 +265,32 @@ void main() {
client.close();
});
});

group('SSHClient.flush', () {
test('can flush client, session, channel, and forward channel', () async {
final client = await getTestClient();
await client.authenticated;

await client.flush();

final session = await client.execute('echo flush_test');
await session.flush();

final controller = SSHChannelController(
localId: 1,
localMaximumPacketSize: 1024,
localInitialWindowSize: 1024,
remoteId: 2,
remoteMaximumPacketSize: 1024,
remoteInitialWindowSize: 1024,
sendMessage: (msg) {},
onFlush: () async {},
);
final forwardChannel = SSHForwardChannel(controller.channel);
await forwardChannel.flush();

await session.done;
client.close();
});
});
}
3 changes: 3 additions & 0 deletions test/src/ssh_client_timeout_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ class _FakeSSHSocket implements SSHSocket {
}
unawaited(_inputController.close());
}

@override
Future<void> flush() async {}
}

class _RecordingSink implements StreamSink<List<int>> {
Expand Down
Loading