import 'dart:convert'; import 'package:anyhow/anyhow.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:http/http.dart' as http; import 'package:latlong2/latlong.dart'; import 'package:rust_core/option.dart'; import 'package:ws/ws.dart'; String _responseNiceErrorString(http.Response response) { return 'Request failed: ${response.statusCode}: ${response.body.length > 500 ? response.body.substring(0, 500) : response.body}'; } const API_PREFIX = '/api/0'; class OwntracksApi { OwntracksApi({ required this.baseUrl, required this.username, required this.pass, }); final String baseUrl; final String username; final String pass; static Map _createBasicAuthHeader(String username, String password, [Map? additionalHeaders]) { final credentials = base64Encode(utf8.encode('$username:$password')); final headers = { 'Authorization': 'Basic $credentials', }; // If there are additional headers, add them to the headers map if (additionalHeaders != null) { headers.addAll(additionalHeaders); } return headers; } Future> fetchLastPoint({ required String user, required String device, }) async { final queryParams = { 'user': user, 'device': device, 'fields': 'lat,lon,isotst', }; final uri = Uri.parse('$baseUrl$API_PREFIX/last').replace(queryParameters: queryParams); var auth_header = _createBasicAuthHeader(username, pass); final response = await http.get( uri, headers: auth_header, ); // print('${response.statusCode}: ${response.body.length > 500 ? response.body.substring(0, 500) : response.body}'); if (response.statusCode == 200) { final point = Point.fromJson((json.decode(response.body) as List)[0]); // print(point); return Ok(point); } else { return bail("couldn't get point data for device '$user:$device': ${_responseNiceErrorString(response)}"); } } // Method to fetch points from a device within a specified date range Future>> fetchPointsForDevice({ required String user, required String device, required DateTime from, DateTime? to, }) async { // Constructing the URL with query parameters final queryParams = { 'user': user, 'device': device, 'from': from.toIso8601String(), 'to': (to ?? DateTime.now()).toIso8601String(), 'fields': 'lat,lon,isotst', }; final uri = Uri.parse('$baseUrl$API_PREFIX/locations').replace(queryParameters: queryParams); var auth_header = _createBasicAuthHeader(username, pass); final response = await http.get( uri, headers: auth_header, ); print('${response.statusCode}: ${response.body.length > 500 ? response.body.substring(0, 500) : response.body}'); if (response.statusCode == 200) { final jsonData = (json.decode(response.body) as Map)['data'] as List; return Ok(jsonData.map((point) => Point.fromJson(point)).toList()); } else { return bail("couldn't get point data for device '$user:$device': ${_responseNiceErrorString(response)}"); } } Future>>> getDevices() async { final uri = Uri.parse('$baseUrl$API_PREFIX/list'); print(uri); final authHeader = _createBasicAuthHeader(username, pass); final response = await http.get(uri, headers: authHeader); if (response.statusCode != 200) { return bail("couldn't get user list from server: ${_responseNiceErrorString(response)}"); } final users = List.from(json.decode(response.body)['results']); Map> map = {}; for (String user in users) { var response = await http.get(uri.replace(queryParameters: {'user': user}), headers: authHeader); if (response.statusCode != 200) { return bail("couldn't get devices list for user '$user': ${_responseNiceErrorString(response)}"); } map[user] = List.from(jsonDecode(response.body)['results']); } return Ok(map); } // Method to create and return a WebSocket connection Future> createWebSocketConnection({ required String wsPath, required void Function(Object message) onMessage, required void Function(WebSocketClientState stateChange) onStateChange, Option<(String, String)> onlyDeviceId = None, }) async { const retryInterval = ( min: Duration(milliseconds: 500), max: Duration(seconds: 15), ); Map headers = {}; if (onlyDeviceId case Some(:final v)) { headers.putIfAbsent('X-Limit-User', () => v.$1); headers.putIfAbsent('X-Limit-Device', () => v.$2); } final client = WebSocketClient(kIsWeb ? WebSocketOptions.common(connectionRetryInterval: retryInterval) : WebSocketOptions.vm( connectionRetryInterval: retryInterval, headers: _createBasicAuthHeader(username, pass)..addAll(headers))); // Listen to messages client.stream.listen(onMessage); // Listen to state changes client.stateChanges.listen(onStateChange); // Connect to the WebSocket server try { await client.connect("${baseUrl.replaceFirst('http', 'ws')}/ws/$wsPath"); } catch (e) { await client.disconnect(); return bail("WebSocket connection to path $wsPath was unsuccessful: $e"); } // Return the connected client return Ok(client); } } class Point { final double lat; final double lon; final DateTime timestamp; Point({required this.lat, required this.lon, required this.timestamp}); @override String toString() { return 'Point{lat: $lat, lon: $lon, timestamp: $timestamp}'; } factory Point.fromJson(Map json) { return Point( lat: json['lat'], lon: json['lon'], timestamp: DateTime.parse(json['isotst']), ); } LatLng get asLatLng => LatLng(lat, lon); } class Device { final String id; final String name; Device({required this.id, required this.name}); factory Device.fromJson(Map json) { return Device( id: json['id'], name: json['name'], ); } } /* Future> fetchPointsForDevice(String deviceId) async { final response = await http.get(Uri.parse('$_baseUrl/devices/$deviceId/points'), headers: { 'authorization': base64Encode(utf8.encode('$username:$pass')) } ); print('${response.statusCode}: ${response.body}'); if (response.statusCode == 200) { final jsonData = json.decode(response.body); return (jsonData as List).map((point) => Point.fromJson(point)).toList(); } else { throw Exception('Failed to load points'); } } Future> listAllDevices() async { final response = await http.get(Uri.parse('$_baseUrl/devices')); if (response.statusCode == 200) { final jsonData = json.decode(response.body); return (jsonData as List).map((device) => Device.fromJson(device)).toList(); } else { throw Exception('Failed to load devices'); } } // Add more API calls as needed } */