Line data Source code
1 : import 'dart:async';
2 : import 'dart:convert';
3 : import 'dart:io';
4 :
5 : import 'package:walletkit_dart/src/domain/exceptions.dart';
6 :
7 : abstract class JsonRpcClient {
8 : Future<void> connect(String host, int port);
9 :
10 : Future<bool> disconnect();
11 :
12 : Future<dynamic> sendRequest(Map<String, dynamic> procedure);
13 :
14 : Future<dynamic> sendRawRequest(Map<String, dynamic> procedure);
15 : }
16 :
17 : class TcpJsonRpcClient extends JsonRpcClient {
18 : final Completer<bool> _connected = Completer<bool>();
19 :
20 39 : Future<bool> get connected => _connected.future;
21 :
22 : final bool isZeniq;
23 :
24 : // final Random rand = Random();
25 :
26 : Socket? _socket;
27 :
28 13 : Socket get socket {
29 39 : if (_connected.isCompleted) return _socket!;
30 : throw const JsonRpcConnectionNotInitialized();
31 : }
32 :
33 : Stream? stream;
34 :
35 : final String host;
36 : final int port;
37 :
38 13 : TcpJsonRpcClient({
39 : this.isZeniq = false,
40 : required this.host,
41 : required this.port,
42 : }) {
43 39 : connect(host, port);
44 : }
45 :
46 13 : @override
47 : Future<void> connect(String host, int port) async {
48 : try {
49 26 : _socket = await Socket.connect(
50 : host,
51 : port,
52 : timeout: const Duration(seconds: 5),
53 : );
54 :
55 39 : stream = _socket!.asBroadcastStream();
56 :
57 26 : _connected.complete(true);
58 : } catch (e) {
59 24 : _connected.complete(false);
60 : }
61 : }
62 :
63 13 : @override
64 : Future<bool> disconnect() async {
65 : try {
66 13 : if (isZeniq) {
67 16 : await socket.flush();
68 : }
69 26 : await socket.flush();
70 26 : await socket.close();
71 : } catch (e) {
72 0 : print("$e");
73 : return true;
74 : }
75 :
76 : return true;
77 : }
78 :
79 13 : @override
80 : Future<dynamic> sendRequest(Map<String, dynamic> procedure) async {
81 : try {
82 13 : final rawResult = await sendRawRequest(procedure);
83 13 : final json = jsonDecode(rawResult);
84 13 : final result = json['result'];
85 : if (result == null) {
86 0 : final error = json['error'];
87 0 : final message = error['message'];
88 0 : throw Exception(message);
89 : }
90 : return result;
91 : } catch (e, _) {
92 : rethrow;
93 : }
94 : }
95 :
96 13 : @override
97 : Future<dynamic> sendRawRequest(Map<String, dynamic> procedure) async {
98 26 : final id = DateTime.now().millisecondsSinceEpoch;
99 26 : final procedureJson = jsonEncode({
100 13 : "id": id,
101 13 : ...procedure,
102 : });
103 :
104 : try {
105 26 : socket.writeln(procedureJson);
106 : } catch (e) {
107 : return null;
108 : }
109 :
110 13 : if (isZeniq) {
111 16 : await socket.flush();
112 : }
113 26 : await socket.flush();
114 :
115 : var txJson = "";
116 39 : await for (final uIntList in stream!) {
117 26 : txJson += String.fromCharCodes(uIntList);
118 13 : if (!isCompleteJson(txJson)) continue;
119 : return txJson;
120 : }
121 :
122 0 : throw Exception("Could not fetch result");
123 : }
124 : }
125 :
126 13 : bool isCompleteJson(String jsonString) {
127 : int numOpenCurly = 0;
128 : int numClosedCurly = 0;
129 : int numOpenSquare = 0;
130 : int numClosedSquare = 0;
131 :
132 39 : for (int i = 0; i < jsonString.length; i++) {
133 13 : switch (jsonString[i]) {
134 13 : case '{':
135 13 : numOpenCurly++;
136 : break;
137 13 : case '}':
138 13 : numClosedCurly++;
139 : break;
140 13 : case '[':
141 10 : numOpenSquare++;
142 : break;
143 13 : case ']':
144 10 : numClosedSquare++;
145 : break;
146 : }
147 : }
148 :
149 26 : return numOpenCurly == numClosedCurly && numOpenSquare == numClosedSquare;
150 : }
|