import 'dart:convert'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:userfront/core/services/auth_proxy_service.dart'; import 'package:userfront/core/services/auth_token_store.dart'; import 'package:userfront/core/services/http_client.dart'; import 'package:userfront/core/services/runtime_env.dart'; class LinkedRp { final String id; final String name; final String logo; final String url; final String initUrl; final bool autoLoginSupported; final String autoLoginUrl; final String status; final List scopes; final DateTime? lastAuthenticatedAt; LinkedRp({ required this.id, required this.name, required this.logo, required this.url, required this.initUrl, required this.autoLoginSupported, required this.autoLoginUrl, required this.status, required this.scopes, required this.lastAuthenticatedAt, }); factory LinkedRp.fromJson(Map json) { final rawLastAuth = json['lastAuthenticatedAt']?.toString() ?? ''; DateTime? parsedLastAuth; if (rawLastAuth.isNotEmpty) { try { parsedLastAuth = DateTime.parse(rawLastAuth).toLocal(); } catch (_) { parsedLastAuth = null; } } return LinkedRp( id: json['id']?.toString() ?? '', name: json['name']?.toString() ?? '', logo: json['logo']?.toString() ?? '', url: json['url']?.toString() ?? '', initUrl: json['init_url']?.toString() ?? '', autoLoginSupported: json['auto_login_supported'] == true, autoLoginUrl: json['auto_login_url']?.toString() ?? '', status: json['status']?.toString() ?? 'unknown', scopes: (json['scopes'] as List?)?.whereType().toList() ?? [], lastAuthenticatedAt: parsedLastAuth, ); } } class LinkedRpsNotifier extends AsyncNotifier> { @override Future> build() async { return _fetchLinkedRps(); } Future> _fetchLinkedRps() async { try { final baseUrl = runtimeBackendUrl(); final url = Uri.parse('$baseUrl/api/v1/user/rp/linked'); final useCookie = AuthTokenStore.usesCookie(); final token = AuthTokenStore.getToken(); final client = createHttpClient(withCredentials: useCookie); final headers = {'Content-Type': 'application/json'}; if (!useCookie && token != null) { headers['Authorization'] = 'Bearer $token'; } final response = await client.get(url, headers: headers); client.close(); if (response.statusCode != 200) { throw Exception('Failed to load linked rps: ${response.statusCode}'); } final body = jsonDecode(response.body) as Map; final items = (body['items'] as List?) ?? []; return items .whereType>() .map(LinkedRp.fromJson) .toList(); } catch (e) { rethrow; } } Future refresh() async { state = const AsyncLoading(); state = await AsyncValue.guard(() => _fetchLinkedRps()); } Future revokeRp(String clientId) async { await AuthProxyService.revokeLinkedRp(clientId); await refresh(); } } final linkedRpsProvider = AsyncNotifierProvider>(() { return LinkedRpsNotifier(); });