136 lines
3.9 KiB
Dart
136 lines
3.9 KiB
Dart
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<BackendDTO> send(
|
|
String path, {
|
|
Map<String, Object?>? 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<T> _mapped<T>(
|
|
Future<BackendDTO> 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<WalletSnapshot> fetch() => _mapped(send(''), BackendMapper.snapshot);
|
|
@override
|
|
Future<Quote> 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<WalletOperation> 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<WalletOperation?> findSubmission(String idempotencyKey) => _mapped(
|
|
send('/submission', body: {'idempotencyKey': idempotencyKey}),
|
|
BackendMapper.operation,
|
|
);
|
|
@override
|
|
Future<AccountProfile> profile() =>
|
|
_mapped(send('/profile'), BackendMapper.profile);
|
|
@override
|
|
Future<AccountProfile> saveLocale(String locale) async =>
|
|
throw const WalletException(WalletError.unsupported);
|
|
void close() => _client.close();
|
|
}
|