part of spiceapi; class Connection { // settings static const _TIMEOUT = Duration(seconds: 2); static const _BUFFER_SIZE = 1024 * 8; // state final String host, pass; final int port; var resource; List _dataBuffer; StreamController _responses; StreamController _connections; WebSocket _socket; RC4 _cipher; bool _disposed = false; Connection(this.host, this.port, this.pass, {this.resource, bool refreshSession=true}) { // initialize _dataBuffer = List(); _responses = StreamController.broadcast(); _connections = StreamController.broadcast(); if (pass.length > 0) _cipher = RC4(utf8.encode(pass)); // initialize socket this._socket = WebSocket("ws://$host:${port + 1}"); this._socket.binaryType = "arraybuffer"; // listen to events this._socket.onOpen.listen((e) async { // refresh session bool error = false; if (refreshSession) { try { await controlRefreshSession(this); } on Error { error = true; } on TimeoutException { error = true; } } // mark as connected if (!this._connections.isClosed) this._connections.add(this); if (error) this.dispose(); }); this._socket.onMessage.listen((e) { // get data var data = e.data; if (data is ByteBuffer) data = data.asUint8List(); // check type if (data is List) { // cipher if (_cipher != null) _cipher.crypt(data); // add data to buffer _dataBuffer.addAll(data); // check buffer size if (_dataBuffer.length > _BUFFER_SIZE) { this.dispose(); return; } // check for completed message for (int i = 0; i < _dataBuffer.length; i++) { if (_dataBuffer[i] == 0) { // get message data and remove from buffer var msgData = List.from(_dataBuffer.getRange(0, i)); _dataBuffer.removeRange(0, i + 1); // check data length if (msgData.length > 0) { // convert to JSON var msgStr = utf8.decode(msgData, allowMalformed: false); // build response var res = Response.fromJson(msgStr); this._responses.add(res); } } } } }); this._socket.onClose.listen((e) { this.dispose(); }); this._socket.onError.listen((e) { this.dispose(); }); } void changePass(String pass) { if (pass.length > 0) _cipher = RC4(utf8.encode(pass)); else _cipher = null; } void dispose() { if (_socket != null) _socket.close(); _socket = null; if (_responses != null) _responses.close(); if (_connections != null) _connections.close(); this._disposed = true; this.free(); } bool isDisposed() { return this._disposed; } void free() { // release optional resource if (this.resource != null) { this.resource.release(); this.resource = null; } } bool isFree() { return this.resource == null; } Future onConnect() { return _connections.stream.first; } bool isValid() { return this._socket != null && !this._disposed; } Future request(Request req) { // add response listener var res = _awaitResponse(req._id); // write request _writeRequest(req); // return future response return res.then((res) { // validate first res.validate(); // return it return res; }); } void _writeRequest(Request req) async { // convert to JSON var json = req.toJson() + "\x00"; var jsonEncoded = utf8.encode(json); // cipher if (_cipher != null) _cipher.crypt(jsonEncoded); // write to socket this._socket.sendByteBuffer(Int8List.fromList(jsonEncoded).buffer); } Future _awaitResponse(int id) { return _responses.stream.timeout(_TIMEOUT).firstWhere( (res) => res._id == id, orElse: null); } }