import 'dart:convert'; import 'dart:html' as html; /// API-клиент admin-консоли (spec 06 m08): токен в sessionStorage, /// 401 → [UnauthorizedException] → экран логина. class UnauthorizedException implements Exception {} class Api { static const _base = ''; static String? token = html.window.sessionStorage['adminToken']; static bool get authorized => token != null && token!.isNotEmpty; static void logout() { token = null; html.window.sessionStorage.remove('adminToken'); } static Future> login( String login, String password) async { final data = await _send('POST', '/api/admin/login', body: {'login': login, 'password': password}); token = data['token'] as String; html.window.sessionStorage['adminToken'] = token!; return data; } static Future get(String path) => _send('GET', path); static Future post(String path, [Map? body]) => _send('POST', path, body: body); /// CSV — запрос с Bearer и Blob-download (spec 06 §2.3). static Future downloadCsv(String query) async { final request = html.HttpRequest() ..open('GET', _base + query) ..responseType = 'blob' ..setRequestHeader('Authorization', 'Bearer $token'); await request.onLoad.first; if (request.status == 401) { logout(); throw UnauthorizedException(); } final blob = request.response as html.Blob; final url = html.Url.createObjectUrlFromBlob(blob); html.AnchorElement(href: url) ..download = 'workouts.csv' ..click(); html.Url.revokeObjectUrl(url); } static Future _send(String method, String path, {Map? body}) async { final request = html.HttpRequest() ..open(method, _base + path) ..setRequestHeader('Content-Type', 'application/json'); if (token != null) { request.setRequestHeader('Authorization', 'Bearer $token'); } await request.onLoad.first; // ignore: only_throw_errors if (request.status == 401) { logout(); throw UnauthorizedException(); } if (request.status! >= 400) { throw Exception('HTTP ${request.status}: ${request.responseText}'); } final text = request.responseText ?? ''; return text.isEmpty ? {} : jsonDecode(text); } }