2022-08-26 08:11:35 +00:00
|
|
|
import 'dart:async';
|
|
|
|
import 'dart:convert';
|
|
|
|
import 'dart:io';
|
2023-05-24 18:27:19 +00:00
|
|
|
import 'dart:typed_data';
|
2023-05-24 21:06:56 +00:00
|
|
|
import 'package:mutex/mutex.dart';
|
2022-08-26 08:11:35 +00:00
|
|
|
|
|
|
|
import 'package:stackwallet/utilities/logger.dart';
|
|
|
|
|
|
|
|
// hacky fix to receive large jsonrpc responses
|
|
|
|
class JsonRPC {
|
|
|
|
JsonRPC({
|
|
|
|
required this.host,
|
|
|
|
required this.port,
|
|
|
|
this.useSSL = false,
|
|
|
|
this.connectionTimeout = const Duration(seconds: 60),
|
|
|
|
});
|
2023-05-24 18:12:54 +00:00
|
|
|
final bool useSSL;
|
|
|
|
final String host;
|
|
|
|
final int port;
|
|
|
|
final Duration connectionTimeout;
|
2022-08-26 08:11:35 +00:00
|
|
|
|
2023-05-24 18:39:21 +00:00
|
|
|
Socket? socket;
|
2023-05-24 18:27:19 +00:00
|
|
|
StreamSubscription<Uint8List>? _subscription;
|
|
|
|
|
2023-05-24 21:06:56 +00:00
|
|
|
final m = Mutex();
|
2023-05-24 20:55:24 +00:00
|
|
|
|
2023-05-24 18:39:21 +00:00
|
|
|
void Function(List<int>)? _onData;
|
|
|
|
void Function(Object, StackTrace)? _onError;
|
|
|
|
|
2022-08-26 08:11:35 +00:00
|
|
|
Future<dynamic> request(String jsonRpcRequest) async {
|
|
|
|
final completer = Completer<dynamic>();
|
|
|
|
final List<int> responseData = [];
|
|
|
|
|
|
|
|
void dataHandler(List<int> data) {
|
|
|
|
responseData.addAll(data);
|
|
|
|
|
|
|
|
// 0x0A is newline
|
|
|
|
// https://electrumx-spesmilo.readthedocs.io/en/latest/protocol-basics.html
|
|
|
|
if (data.last == 0x0A) {
|
|
|
|
try {
|
|
|
|
final response = json.decode(String.fromCharCodes(responseData));
|
2023-05-24 20:55:36 +00:00
|
|
|
completer.complete(response); // TODO only complete on last chunk?
|
2022-08-26 08:11:35 +00:00
|
|
|
} catch (e, s) {
|
|
|
|
Logging.instance
|
|
|
|
.log("JsonRPC json.decode: $e\n$s", level: LogLevel.Error);
|
|
|
|
completer.completeError(e, s);
|
|
|
|
} finally {
|
2023-05-24 18:27:19 +00:00
|
|
|
Logging.instance.log(
|
|
|
|
"JsonRPC dataHandler: not destroying socket ${socket?.address}:${socket?.port}",
|
|
|
|
level: LogLevel.Info,
|
|
|
|
);
|
2023-05-24 18:15:19 +00:00
|
|
|
// socket?.destroy();
|
|
|
|
// TODO is this all we need to do?
|
2022-08-26 08:11:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-24 18:39:21 +00:00
|
|
|
_onData = dataHandler;
|
|
|
|
|
2022-08-26 08:11:35 +00:00
|
|
|
void errorHandler(Object error, StackTrace trace) {
|
|
|
|
Logging.instance
|
|
|
|
.log("JsonRPC errorHandler: $error\n$trace", level: LogLevel.Error);
|
|
|
|
completer.completeError(error, trace);
|
2023-05-24 18:27:19 +00:00
|
|
|
Logging.instance.log(
|
|
|
|
"JsonRPC errorHandler: not destroying socket ${socket?.address}:${socket?.port}",
|
|
|
|
level: LogLevel.Info,
|
|
|
|
);
|
2023-05-24 18:15:19 +00:00
|
|
|
// socket?.destroy();
|
|
|
|
// TODO do we need to recreate the socket?
|
2022-08-26 08:11:35 +00:00
|
|
|
}
|
|
|
|
|
2023-05-24 18:39:21 +00:00
|
|
|
_onError = errorHandler;
|
|
|
|
|
2022-08-26 08:11:35 +00:00
|
|
|
void doneHandler() {
|
2023-05-24 18:27:19 +00:00
|
|
|
Logging.instance.log(
|
|
|
|
"JsonRPC doneHandler: not destroying socket ${socket?.address}:${socket?.port}",
|
|
|
|
level: LogLevel.Info,
|
|
|
|
);
|
2023-05-24 18:15:19 +00:00
|
|
|
// socket?.destroy();
|
2023-05-24 21:06:56 +00:00
|
|
|
m.release();
|
2023-05-24 18:15:19 +00:00
|
|
|
// TODO is this all we need?
|
2022-08-26 08:11:35 +00:00
|
|
|
}
|
|
|
|
|
2023-05-24 18:15:10 +00:00
|
|
|
if (socket != null) {
|
|
|
|
// TODO check if the socket is valid, alive, connected, etc
|
2023-05-24 21:06:56 +00:00
|
|
|
} else {
|
|
|
|
Logging.instance.log(
|
|
|
|
"JsonRPC request: opening socket $host:$port",
|
|
|
|
level: LogLevel.Info,
|
|
|
|
);
|
2023-05-24 18:15:10 +00:00
|
|
|
}
|
2023-05-24 20:55:24 +00:00
|
|
|
// Do we need to check the subscription, too?
|
|
|
|
|
2023-05-24 21:06:56 +00:00
|
|
|
await m.acquire();
|
2023-05-24 18:15:10 +00:00
|
|
|
|
2022-08-26 08:11:35 +00:00
|
|
|
if (useSSL) {
|
2023-05-24 18:14:56 +00:00
|
|
|
socket ??= await SecureSocket.connect(host, port,
|
2023-05-24 20:55:36 +00:00
|
|
|
timeout: connectionTimeout, onBadCertificate: (_) => true); // TODO do not automatically trust bad certificates
|
2023-05-24 18:27:19 +00:00
|
|
|
_subscription ??= socket!.listen(
|
2023-05-24 18:39:21 +00:00
|
|
|
_onData,
|
|
|
|
onError: _onError,
|
2023-05-24 18:27:19 +00:00
|
|
|
onDone: doneHandler,
|
|
|
|
cancelOnError: true,
|
|
|
|
);
|
2022-08-26 08:11:35 +00:00
|
|
|
} else {
|
2023-05-24 18:27:19 +00:00
|
|
|
socket ??= await Socket.connect(
|
|
|
|
host,
|
|
|
|
port,
|
|
|
|
timeout: connectionTimeout,
|
|
|
|
);
|
|
|
|
_subscription ??= socket!.listen(
|
2023-05-24 18:39:21 +00:00
|
|
|
_onData,
|
|
|
|
onError: _onError,
|
2023-05-24 18:27:19 +00:00
|
|
|
onDone: doneHandler,
|
|
|
|
cancelOnError: true,
|
|
|
|
);
|
2022-08-26 08:11:35 +00:00
|
|
|
}
|
|
|
|
|
2023-05-24 20:56:08 +00:00
|
|
|
socket!.write('$jsonRpcRequest\r\n');
|
2022-08-26 08:11:35 +00:00
|
|
|
|
2023-05-24 20:55:55 +00:00
|
|
|
Logging.instance.log(
|
2023-05-24 21:06:56 +00:00
|
|
|
"JsonRPC request: wrote request $jsonRpcRequest to socket ${socket?.address}:${socket?.port}",
|
2023-05-24 20:55:55 +00:00
|
|
|
level: LogLevel.Info,
|
|
|
|
);
|
|
|
|
|
2022-08-26 08:11:35 +00:00
|
|
|
return completer.future;
|
|
|
|
}
|
|
|
|
}
|