import 'package:dio/dio.dart'; import '../config.dart'; import '../models/api_exception.dart'; import '../models/login_result.dart'; import '../models/support_message.dart'; import '../models/support_ticket.dart'; class SupportApi { SupportApi({Dio? dio, String? origin, this.onUnauthorized}) : dio = dio ?? Dio( BaseOptions( baseUrl: origin ?? baseUrl, connectTimeout: const Duration(seconds: 20), receiveTimeout: const Duration(seconds: 20), headers: const { 'Accept': 'application/json', 'Content-Type': 'application/json', }, ), ) { this.dio.interceptors.add( InterceptorsWrapper( onRequest: (options, handler) { final token = authToken; if (token != null && token.isNotEmpty) { options.headers['Authorization'] = 'Bearer $token'; } handler.next(options); }, onError: (error, handler) { final apiError = _exceptionFor(error); final sentAuth = error.requestOptions.headers['Authorization'] != null; if (apiError.isUnauthorized && sentAuth) { onUnauthorized?.call(); } handler.reject( DioException( requestOptions: error.requestOptions, response: error.response, type: error.type, error: apiError, message: apiError.message, ), ); }, ), ); } final Dio dio; String? authToken; void Function()? onUnauthorized; Future login(String username, String password) { return _send( dio.post( '/api/auth/login', data: {'username': username, 'password': password}, ), (data) { if (data is! Map) { throw const FormatException('Unexpected login payload'); } return LoginResult.fromJson(Map.from(data)); }, ); } Future> listTickets({String? status, String? category}) { return _send( dio.get( '/api/support/tickets', queryParameters: ticketListQuery(status: status, category: category), ), (data) { if (data is! Map || data['tickets'] is! List) { throw const FormatException('Unexpected ticket list'); } return [ for (final item in data['tickets'] as List) if (item is Map) SupportTicket.fromJson(Map.from(item)), ]; }, ); } Future getThread(String ticketId) { return _send(dio.get('/api/support/tickets/$ticketId'), _thread); } Future reply(String ticketId, String body) { return _send( dio.post( '/api/support/tickets/$ticketId/messages', data: {'body': body}, ), _thread, ); } Future setStatus(String ticketId, String status) { return _send( dio.post( '/api/support/tickets/$ticketId/status', data: {'status': status}, ), _thread, ); } Future registerDevice({ required String token, required String platform, }) { return _send( dio.put( '/api/support/devices', data: {'token': token, 'platform': platform}, ), (_) {}, ); } Future unregisterDevice(String token) { return _send( dio.delete('/api/support/devices', data: {'token': token}), (_) {}, ); } SupportThread _thread(Object? data) { if (data is! Map) { throw const FormatException('Unexpected thread payload'); } return SupportThread.fromJson(Map.from(data)); } Future _send( Future> call, T Function(Object? data) parse, ) async { try { final response = await call; return parse(response.data); } on DioException catch (error) { final wrapped = error.error; if (wrapped is ApiException) { throw wrapped; } throw _exceptionFor(error); } on FormatException { throw const ApiException(message: 'Unexpected response from the server.'); } } ApiException _exceptionFor(DioException error) { final offline = error.type == DioExceptionType.connectionError || error.type == DioExceptionType.connectionTimeout || error.type == DioExceptionType.receiveTimeout || error.type == DioExceptionType.sendTimeout; return ApiException.fromBody( statusCode: error.response?.statusCode, body: error.response?.data, fallback: offline ? 'Could not reach the server.' : 'Something went wrong.', ); } }