update
This commit is contained in:
@@ -25,31 +25,171 @@ class ApiClient {
|
||||
String path,
|
||||
Map<String, dynamic> body,
|
||||
) async {
|
||||
final response = await http.post(
|
||||
_uri(path),
|
||||
headers: _headers,
|
||||
body: jsonEncode(body),
|
||||
);
|
||||
return _decode(response);
|
||||
final uri = _uri(path);
|
||||
final client = _clientFor(uri);
|
||||
try {
|
||||
final response = await client
|
||||
.post(uri, headers: _headers, body: jsonEncode(body))
|
||||
.timeout(const Duration(seconds: 15));
|
||||
return _decode(response, uri);
|
||||
} catch (error, stackTrace) {
|
||||
throw _connectionException(uri, error, stackTrace);
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> get(
|
||||
String path, [
|
||||
Map<String, String>? query,
|
||||
]) async {
|
||||
final response = await http.get(_uri(path, query), headers: _headers);
|
||||
return _decode(response);
|
||||
final uri = _uri(path, query);
|
||||
final client = _clientFor(uri);
|
||||
try {
|
||||
final response = await client
|
||||
.get(uri, headers: _headers)
|
||||
.timeout(const Duration(seconds: 15));
|
||||
return _decode(response, uri);
|
||||
} catch (error, stackTrace) {
|
||||
throw _connectionException(uri, error, stackTrace);
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> _decode(http.Response response) {
|
||||
final decoded = response.body.isEmpty
|
||||
? <String, dynamic>{}
|
||||
: jsonDecode(response.body);
|
||||
if (response.statusCode >= 200 && response.statusCode < 300) {
|
||||
return decoded is Map<String, dynamic> ? decoded : {'data': decoded};
|
||||
http.Client _clientFor(Uri uri) {
|
||||
final client = HttpClient()
|
||||
..badCertificateCallback = (certificate, host, port) {
|
||||
debugPrint(
|
||||
'MyLIS menerima sertifikat internal untuk $host:$port '
|
||||
'issuer=${certificate.issuer}',
|
||||
);
|
||||
return true;
|
||||
};
|
||||
return IOClient(client);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _decode(http.Response response, Uri uri) {
|
||||
final bodyPreview = response.body.length > 240
|
||||
? '${response.body.substring(0, 240)}...'
|
||||
: response.body;
|
||||
|
||||
if (response.statusCode < 200 || response.statusCode >= 300) {
|
||||
Object? decoded;
|
||||
if (_looksLikeJson(response)) {
|
||||
try {
|
||||
decoded = response.body.isEmpty
|
||||
? <String, dynamic>{}
|
||||
: jsonDecode(response.body);
|
||||
} catch (_) {
|
||||
decoded = null;
|
||||
}
|
||||
}
|
||||
final message = decoded is Map ? decoded['message'] : null;
|
||||
debugPrint(
|
||||
'MyLIS API gagal: ${response.request?.method ?? '-'} $uri '
|
||||
'HTTP ${response.statusCode}; body=$bodyPreview',
|
||||
);
|
||||
throw ApiException(
|
||||
message?.toString() ??
|
||||
'Server mengembalikan HTTP ${response.statusCode}. '
|
||||
'URL: $uri. Kemungkinan request masuk ke virtual host/proxy yang salah. '
|
||||
'Detail awal respons: $bodyPreview',
|
||||
);
|
||||
}
|
||||
final message = decoded is Map ? decoded['message'] : null;
|
||||
throw ApiException(message?.toString() ?? 'Koneksi ke server gagal.');
|
||||
|
||||
final Object decoded;
|
||||
try {
|
||||
decoded = response.body.isEmpty
|
||||
? <String, dynamic>{}
|
||||
: jsonDecode(response.body);
|
||||
} catch (error, stackTrace) {
|
||||
debugPrint(
|
||||
'MyLIS API decode gagal: ${response.request?.method ?? '-'} $uri '
|
||||
'HTTP ${response.statusCode}; error=$error',
|
||||
);
|
||||
debugPrintStack(stackTrace: stackTrace);
|
||||
throw ApiException(
|
||||
'Respons server tidak bisa dibaca. '
|
||||
'URL: $uri. Status HTTP: ${response.statusCode}.',
|
||||
);
|
||||
}
|
||||
|
||||
if (response.statusCode >= 200 && response.statusCode < 300) {
|
||||
final cleanDecoded = _decodeHtmlEntitiesIn(decoded);
|
||||
return cleanDecoded is Map<String, dynamic>
|
||||
? cleanDecoded
|
||||
: {'data': cleanDecoded};
|
||||
}
|
||||
return {'data': _decodeHtmlEntitiesIn(decoded)};
|
||||
}
|
||||
|
||||
bool _looksLikeJson(http.Response response) {
|
||||
final contentType = response.headers['content-type']?.toLowerCase() ?? '';
|
||||
final body = response.body.trimLeft();
|
||||
return contentType.contains('json') ||
|
||||
body.startsWith('{') ||
|
||||
body.startsWith('[');
|
||||
}
|
||||
|
||||
ApiException _connectionException(
|
||||
Uri uri,
|
||||
Object error,
|
||||
StackTrace stackTrace,
|
||||
) {
|
||||
if (error is ApiException) {
|
||||
return error;
|
||||
}
|
||||
|
||||
final reason = _connectionReason(error);
|
||||
debugPrint('MyLIS koneksi gagal: $uri; $reason; error=$error');
|
||||
debugPrintStack(stackTrace: stackTrace);
|
||||
return ApiException('Koneksi server gagal. URL: $uri. Penyebab: $reason');
|
||||
}
|
||||
|
||||
String _connectionReason(Object error) {
|
||||
if (error is TimeoutException) {
|
||||
return 'timeout lebih dari 15 detik. Periksa jaringan, DNS lokal, atau server Laravel belum aktif.';
|
||||
}
|
||||
if (error is http.ClientException) {
|
||||
final message = error.message.toLowerCase();
|
||||
if (message.contains('certificate') ||
|
||||
message.contains('handshake') ||
|
||||
message.contains('tls')) {
|
||||
return 'sertifikat HTTPS lokal belum dipercaya oleh device/simulator.';
|
||||
}
|
||||
if (message.contains('failed host lookup') ||
|
||||
message.contains('nodename') ||
|
||||
message.contains('name or service')) {
|
||||
return 'host tidak ditemukan. Periksa URL atau DNS/hosts lokal.';
|
||||
}
|
||||
if (message.contains('connection refused')) {
|
||||
return 'server menolak koneksi. Periksa apakah Laravel/web server aktif.';
|
||||
}
|
||||
if (message.contains('connection closed')) {
|
||||
return 'koneksi ditutup oleh server.';
|
||||
}
|
||||
return 'client HTTP gagal: ${error.message}';
|
||||
}
|
||||
if (error is FormatException) {
|
||||
return 'format URL atau respons tidak valid: ${error.message}';
|
||||
}
|
||||
return error.toString();
|
||||
}
|
||||
|
||||
Object? _decodeHtmlEntitiesIn(Object? value) {
|
||||
if (value is String) {
|
||||
return decodeHtmlEntities(value);
|
||||
}
|
||||
if (value is List) {
|
||||
return value.map(_decodeHtmlEntitiesIn).toList();
|
||||
}
|
||||
if (value is Map) {
|
||||
return value.map(
|
||||
(key, item) => MapEntry(key.toString(), _decodeHtmlEntitiesIn(item)),
|
||||
);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user