2022-08-26 08:11:35 +00:00
|
|
|
import 'dart:convert';
|
|
|
|
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'package:http/http.dart';
|
|
|
|
import 'package:stackwallet/hive/db.dart';
|
|
|
|
import 'package:stackwallet/models/node_model.dart';
|
|
|
|
import 'package:stackwallet/utilities/default_nodes.dart';
|
|
|
|
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
|
|
|
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
|
|
|
|
import 'package:stackwallet/utilities/logger.dart';
|
|
|
|
|
|
|
|
const kStackCommunityNodesEndpoint = "https://extras.stackwallet.com";
|
|
|
|
|
|
|
|
class NodeService extends ChangeNotifier {
|
2022-11-09 23:48:43 +00:00
|
|
|
final SecureStorageInterface secureStorageInterface;
|
2022-08-26 08:11:35 +00:00
|
|
|
|
|
|
|
/// Exposed [secureStorageInterface] in order to inject mock for tests
|
|
|
|
NodeService({
|
2022-11-09 22:43:26 +00:00
|
|
|
required this.secureStorageInterface,
|
2022-08-26 08:11:35 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
Future<void> updateDefaults() async {
|
|
|
|
for (final defaultNode in DefaultNodes.all) {
|
|
|
|
final savedNode = DB.instance
|
|
|
|
.get<NodeModel>(boxName: DB.boxNameNodeModels, key: defaultNode.id);
|
|
|
|
if (savedNode == null) {
|
2022-11-25 19:50:13 +00:00
|
|
|
// save the default node to hive only if no other nodes for the specific coin exist
|
|
|
|
if (getNodesFor(coinFromPrettyName(defaultNode.coinName)).isEmpty) {
|
|
|
|
await DB.instance.put<NodeModel>(
|
2022-08-26 08:11:35 +00:00
|
|
|
boxName: DB.boxNameNodeModels,
|
|
|
|
key: defaultNode.id,
|
2022-11-25 19:50:13 +00:00
|
|
|
value: defaultNode,
|
|
|
|
);
|
|
|
|
}
|
2022-08-26 08:11:35 +00:00
|
|
|
} else {
|
2023-01-06 22:57:46 +00:00
|
|
|
// update all fields but copy over previously set enabled and trusted states
|
2022-08-26 08:11:35 +00:00
|
|
|
await DB.instance.put<NodeModel>(
|
|
|
|
boxName: DB.boxNameNodeModels,
|
|
|
|
key: savedNode.id,
|
2023-01-06 22:57:46 +00:00
|
|
|
value: defaultNode.copyWith(
|
|
|
|
enabled: savedNode.enabled,
|
2023-01-06 23:10:17 +00:00
|
|
|
isFailover: savedNode.isFailover,
|
2023-01-06 22:57:46 +00:00
|
|
|
trusted: savedNode.trusted,
|
|
|
|
));
|
2022-08-26 08:11:35 +00:00
|
|
|
}
|
2022-10-20 17:01:25 +00:00
|
|
|
|
|
|
|
// check if a default node is the primary node for the crypto currency
|
|
|
|
// and update it if needed
|
|
|
|
final coin = coinFromPrettyName(defaultNode.coinName);
|
|
|
|
final primaryNode = getPrimaryNodeFor(coin: coin);
|
|
|
|
if (primaryNode != null && primaryNode.id == defaultNode.id) {
|
|
|
|
await setPrimaryNodeFor(
|
|
|
|
coin: coin,
|
|
|
|
node: defaultNode.copyWith(
|
|
|
|
enabled: primaryNode.enabled,
|
2023-01-06 23:10:17 +00:00
|
|
|
isFailover: primaryNode.isFailover,
|
2023-01-06 22:57:46 +00:00
|
|
|
trusted: primaryNode.trusted,
|
2022-10-20 17:01:25 +00:00
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
2022-08-26 08:11:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> setPrimaryNodeFor({
|
|
|
|
required Coin coin,
|
|
|
|
required NodeModel node,
|
|
|
|
bool shouldNotifyListeners = false,
|
|
|
|
}) async {
|
|
|
|
await DB.instance.put<NodeModel>(
|
|
|
|
boxName: DB.boxNamePrimaryNodes, key: coin.name, value: node);
|
|
|
|
if (shouldNotifyListeners) {
|
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
NodeModel? getPrimaryNodeFor({required Coin coin}) {
|
|
|
|
return DB.instance
|
|
|
|
.get<NodeModel>(boxName: DB.boxNamePrimaryNodes, key: coin.name);
|
|
|
|
}
|
|
|
|
|
|
|
|
List<NodeModel> get primaryNodes {
|
|
|
|
return DB.instance.values<NodeModel>(boxName: DB.boxNamePrimaryNodes);
|
|
|
|
}
|
|
|
|
|
|
|
|
List<NodeModel> get nodes {
|
|
|
|
return DB.instance.values<NodeModel>(boxName: DB.boxNameNodeModels);
|
|
|
|
}
|
|
|
|
|
|
|
|
List<NodeModel> getNodesFor(Coin coin) {
|
|
|
|
final list = DB.instance
|
|
|
|
.values<NodeModel>(boxName: DB.boxNameNodeModels)
|
|
|
|
.where((e) =>
|
2022-11-25 19:50:13 +00:00
|
|
|
e.coinName == coin.name &&
|
|
|
|
!e.id.startsWith(DefaultNodes.defaultNodeIdPrefix))
|
2022-08-26 08:11:35 +00:00
|
|
|
.toList();
|
|
|
|
|
|
|
|
// add default to end of list
|
|
|
|
list.addAll(DB.instance
|
|
|
|
.values<NodeModel>(boxName: DB.boxNameNodeModels)
|
|
|
|
.where((e) =>
|
2022-11-25 19:50:13 +00:00
|
|
|
e.coinName == coin.name &&
|
|
|
|
e.id.startsWith(DefaultNodes.defaultNodeIdPrefix))
|
2022-08-26 08:11:35 +00:00
|
|
|
.toList());
|
|
|
|
|
|
|
|
// return reversed list so default node appears at beginning
|
|
|
|
return list.reversed.toList();
|
|
|
|
}
|
|
|
|
|
|
|
|
NodeModel? getNodeById({required String id}) {
|
|
|
|
return DB.instance.get<NodeModel>(boxName: DB.boxNameNodeModels, key: id);
|
|
|
|
}
|
|
|
|
|
|
|
|
List<NodeModel> failoverNodesFor({required Coin coin}) {
|
|
|
|
return getNodesFor(coin).where((e) => e.isFailover && !e.isDown).toList();
|
|
|
|
}
|
|
|
|
|
|
|
|
// should probably just combine this and edit into a save() func at some point
|
|
|
|
/// Over write node in hive if a node with existing id already exists.
|
|
|
|
/// Otherwise add node to hive
|
|
|
|
Future<void> add(
|
|
|
|
NodeModel node,
|
|
|
|
String? password,
|
|
|
|
bool shouldNotifyListeners,
|
|
|
|
) async {
|
|
|
|
await DB.instance.put<NodeModel>(
|
|
|
|
boxName: DB.boxNameNodeModels, key: node.id, value: node);
|
|
|
|
|
|
|
|
if (password != null) {
|
|
|
|
await secureStorageInterface.write(
|
|
|
|
key: "${node.id}_nodePW", value: password);
|
|
|
|
}
|
|
|
|
if (shouldNotifyListeners) {
|
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> delete(String id, bool shouldNotifyListeners) async {
|
|
|
|
await DB.instance.delete<NodeModel>(boxName: DB.boxNameNodeModels, key: id);
|
|
|
|
|
|
|
|
await secureStorageInterface.delete(key: "${id}_nodePW");
|
|
|
|
if (shouldNotifyListeners) {
|
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> setEnabledState(
|
|
|
|
String id,
|
|
|
|
bool enabled,
|
|
|
|
bool shouldNotifyListeners,
|
|
|
|
) async {
|
|
|
|
final model = DB.instance.get<NodeModel>(
|
|
|
|
boxName: DB.boxNameNodeModels,
|
|
|
|
key: id,
|
|
|
|
)!;
|
|
|
|
await DB.instance.put<NodeModel>(
|
|
|
|
boxName: DB.boxNameNodeModels,
|
|
|
|
key: model.id,
|
|
|
|
value: model.copyWith(enabled: enabled));
|
|
|
|
if (shouldNotifyListeners) {
|
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// convenience wrapper for add
|
|
|
|
Future<void> edit(
|
|
|
|
NodeModel editedNode,
|
|
|
|
String? password,
|
|
|
|
bool shouldNotifyListeners,
|
|
|
|
) async {
|
2023-01-06 21:49:27 +00:00
|
|
|
// check if the node being edited is the primary one; if it is, setPrimaryNodeFor coin
|
|
|
|
final coin = coinFromPrettyName(editedNode.coinName);
|
|
|
|
var primaryNode = getPrimaryNodeFor(coin: coin);
|
|
|
|
if (primaryNode?.id == editedNode.id) {
|
|
|
|
await setPrimaryNodeFor(
|
|
|
|
coin: coin,
|
|
|
|
node: editedNode,
|
|
|
|
shouldNotifyListeners: true,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-08-26 08:11:35 +00:00
|
|
|
return add(editedNode, password, shouldNotifyListeners);
|
|
|
|
}
|
|
|
|
|
|
|
|
//============================================================================
|
|
|
|
|
|
|
|
Future<void> updateCommunityNodes() async {
|
|
|
|
final Client client = Client();
|
|
|
|
try {
|
|
|
|
final uri = Uri.parse("$kStackCommunityNodesEndpoint/getNodes");
|
|
|
|
final response = await client.post(
|
|
|
|
uri,
|
|
|
|
headers: {'Content-Type': 'application/json'},
|
|
|
|
body: jsonEncode({
|
|
|
|
"jsonrpc": "2.0",
|
|
|
|
"id": "0",
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
|
|
|
|
final json = jsonDecode(response.body) as Map;
|
|
|
|
final result = jsonDecode(json['result'] as String);
|
|
|
|
final map = jsonDecode(result as String);
|
2022-08-26 18:47:47 +00:00
|
|
|
Logging.instance.log(map, level: LogLevel.Info);
|
2022-08-26 08:11:35 +00:00
|
|
|
|
|
|
|
for (final coin in Coin.values) {
|
|
|
|
final nodeList = List<Map<String, dynamic>>.from(
|
|
|
|
map["nodes"][coin.name] as List? ?? []);
|
|
|
|
for (final nodeMap in nodeList) {
|
|
|
|
NodeModel node = NodeModel(
|
|
|
|
host: nodeMap["host"] as String,
|
|
|
|
port: nodeMap["port"] as int,
|
|
|
|
name: nodeMap["name"] as String,
|
|
|
|
id: nodeMap["id"] as String,
|
|
|
|
useSSL: nodeMap["useSSL"] == "true",
|
|
|
|
enabled: true,
|
|
|
|
coinName: coin.name,
|
|
|
|
isFailover: true,
|
|
|
|
isDown: nodeMap["isDown"] == "true",
|
|
|
|
);
|
|
|
|
final currentNode = getNodeById(id: nodeMap["id"] as String);
|
|
|
|
if (currentNode != null) {
|
|
|
|
node = currentNode.copyWith(
|
|
|
|
host: node.host,
|
|
|
|
port: node.port,
|
|
|
|
name: node.name,
|
|
|
|
useSSL: node.useSSL,
|
|
|
|
coinName: node.coinName,
|
|
|
|
isDown: node.isDown,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
await add(node, null, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (e, s) {
|
|
|
|
Logging.instance
|
|
|
|
.log("updateCommunityNodes() failed: $e\n$s", level: LogLevel.Error);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|