import 'dart:async'; import 'dart:convert'; import 'package:web_socket_channel/web_socket_channel.dart'; import '../config/api_config.dart'; enum TerminalConnectionState { disconnected, connecting, connected, error, } class TerminalService { WebSocketChannel? _channel; String? _token; String? _sessionName; final _outputController = StreamController.broadcast(); final _stateController = StreamController.broadcast(); Stream get output => _outputController.stream; Stream get connectionState => _stateController.stream; TerminalConnectionState _currentState = TerminalConnectionState.disconnected; TerminalConnectionState get currentState => _currentState; void setToken(String? token) { _token = token; } Future connect(String sessionName) async { if (_token == null) { _updateState(TerminalConnectionState.error); return; } _sessionName = sessionName; _updateState(TerminalConnectionState.connecting); try { final uri = Uri.parse('${ApiConfig.wsUrl}${ApiConfig.wsTerminal(sessionName)}'); _channel = WebSocketChannel.connect(uri); await _channel!.ready; // Send auth token _channel!.sink.add(jsonEncode({'token': _token})); _channel!.stream.listen( (data) { try { final message = jsonDecode(data); if (message['type'] == 'connected') { _updateState(TerminalConnectionState.connected); } else if (message['type'] == 'output') { _outputController.add(message['data'] ?? ''); } } catch (e) { // Raw data _outputController.add(data.toString()); } }, onError: (error) { _updateState(TerminalConnectionState.error); }, onDone: () { _updateState(TerminalConnectionState.disconnected); }, ); } catch (e) { _updateState(TerminalConnectionState.error); } } void sendInput(String input) { if (_channel == null || _currentState != TerminalConnectionState.connected) { return; } _channel!.sink.add(jsonEncode({ 'type': 'input', 'data': input, })); } void resize(int rows, int cols) { if (_channel == null || _currentState != TerminalConnectionState.connected) { return; } _channel!.sink.add(jsonEncode({ 'type': 'resize', 'rows': rows, 'cols': cols, })); } void _updateState(TerminalConnectionState state) { _currentState = state; _stateController.add(state); } void disconnect() { _channel?.sink.close(); _channel = null; _sessionName = null; _updateState(TerminalConnectionState.disconnected); } void dispose() { disconnect(); _outputController.close(); _stateController.close(); } }