import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; import 'package:sxchnge_auth/sxchnge_auth.dart'; import '../mappers/backend.dart'; import '../models/wallet.dart'; import '../transport/backend.dart'; import 'contracts.dart'; import 'http/client.dart'; class BackendWalletService implements WalletService { BackendWalletService({ required this.baseUri, required this.accessToken, http.Client? client, }) : _client = client ?? createClient() { if (!baseUri.hasAuthority || baseUri.hasQuery || baseUri.hasFragment || baseUri.userInfo.isNotEmpty || (baseUri.scheme != 'https' && !(baseUri.scheme == 'http' && ['localhost', '127.0.0.1'].contains(baseUri.host)))) throw ArgumentError('Valid wallet API URL required'); } final Uri baseUri; final String? Function() accessToken; final http.Client _client; @override bool get isSimulated => false; Future send( String path, { Map? body, bool factor = false, }) async { final token = accessToken(); if (token == null) throw const WalletException(WalletError.unavailable); final uri = baseUri.replace( path: '${baseUri.path.replaceFirst(RegExp(r"/$"), "")}/accounts/wallet$path', ); final request = http.Request(body == null ? 'GET' : 'POST', uri) ..followRedirects = false; request.headers.addAll({ 'Authorization': 'Bearer $token', 'Content-Type': 'application/json', if (kIsWeb) 'X-CSRF-Protection': '1', }); if (body != null) request.body = jsonEncode(body); try { final response = await _client .send(request) .then(http.Response.fromStream) .timeout(const Duration(seconds: 40)); if (token != accessToken()) throw const WalletException(WalletError.unavailable); final dto = BackendDTO.decode(response.body); if (response.statusCode < 200 || response.statusCode >= 300 || !dto.accepted) { if (factor || dto.error.startsWith('factor')) throw FactorException(BackendMapper.factorFailure(dto)); throw WalletException(BackendMapper.failure(dto)); } return dto; } on FactorException { rethrow; } on WalletException { rethrow; } catch (_) { throw const WalletException(WalletError.unavailable); } } Future _mapped( Future dto, T Function(BackendDTO) map, ) async { try { return map(await dto); } on FactorException { rethrow; } on WalletException { rethrow; } catch (_) { throw const WalletException(WalletError.unavailable); } } @override Future fetch() => _mapped(send(''), BackendMapper.snapshot); @override Future quote(TransferRequest request) async { if (request.payoutDestination != null) throw const WalletException(WalletError.unsupported); return _mapped( send('/quote', body: BackendMapper.request(request)), BackendMapper.quote, ); } @override Future submit( String quoteId, String idempotencyKey, { String? approval, }) async { final value = await _mapped( send( '/submit', body: {'quoteId': quoteId, 'idempotencyKey': idempotencyKey}, ), BackendMapper.operation, ); if (value == null) throw const WalletException(WalletError.uncertain); return value; } @override Future findSubmission(String idempotencyKey) => _mapped( send('/submission', body: {'idempotencyKey': idempotencyKey}), BackendMapper.operation, ); @override Future profile() => _mapped(send('/profile'), BackendMapper.profile); @override Future saveLocale(String locale) async => throw const WalletException(WalletError.unsupported); void close() => _client.close(); }