SimpleCloudNotifier/flutter/lib/api/api_client.dart

171 lines
6.4 KiB
Dart
Raw Normal View History

2024-02-18 16:23:10 +01:00
import 'dart:convert';
2024-05-25 18:09:39 +02:00
import 'package:fl_toast/fl_toast.dart';
import 'package:flutter/material.dart';
2024-02-11 01:08:51 +01:00
import 'package:http/http.dart' as http;
2024-05-25 18:09:39 +02:00
import 'package:simplecloudnotifier/models/api_error.dart';
2024-02-18 17:36:58 +01:00
import 'package:simplecloudnotifier/models/key_token_auth.dart';
2024-02-18 16:23:10 +01:00
import 'package:simplecloudnotifier/models/user.dart';
2024-05-26 00:20:25 +02:00
import 'package:simplecloudnotifier/state/application_log.dart';
2024-05-25 18:09:39 +02:00
import 'package:simplecloudnotifier/state/globals.dart';
import 'package:simplecloudnotifier/state/request_log.dart';
2024-05-25 22:06:43 +02:00
import 'package:simplecloudnotifier/models/channel.dart';
import 'package:simplecloudnotifier/models/message.dart';
2024-02-18 17:36:58 +01:00
enum ChannelSelector {
owned(apiKey: 'owned'), // Return all channels of the user
subscribedAny(apiKey: 'subscribed_any'), // Return all channels that the user is subscribing to
allAny(apiKey: 'all_any'), // Return channels that the user owns or is subscribing
subscribed(apiKey: 'subscribed'), // Return all channels that the user is subscribing to (even unconfirmed)
all(apiKey: 'all'); // Return channels that the user owns or is subscribing (even unconfirmed)
const ChannelSelector({required this.apiKey});
final String apiKey;
}
2024-02-11 01:08:51 +01:00
class APIClient {
static const String _base = 'https://simplecloudnotifier.de/api/v2';
2024-05-25 18:09:39 +02:00
static Future<T> _request<T>({
required String name,
required String method,
required String relURL,
Map<String, String>? query,
required T Function(Map<String, dynamic> json)? fn,
dynamic jsonBody,
KeyTokenAuth? auth,
Map<String, String>? header,
}) async {
final t0 = DateTime.now();
final uri = Uri.parse('$_base/$relURL').replace(queryParameters: query ?? {});
final req = http.Request(method, uri);
if (jsonBody != null) {
req.body = jsonEncode(jsonBody);
req.headers['Content-Type'] = 'application/json';
}
2024-02-11 01:08:51 +01:00
2024-05-25 18:09:39 +02:00
if (auth != null) {
req.headers['Authorization'] = 'SCN ${auth.token}';
}
2024-02-18 16:23:10 +01:00
2024-05-25 18:09:39 +02:00
req.headers['User-Agent'] = 'simplecloudnotifier/flutter/${Globals().platform.replaceAll(' ', '_')} ${Globals().version}+${Globals().buildNumber}';
2024-02-18 16:23:10 +01:00
2024-05-25 18:09:39 +02:00
if (header != null && !header.isEmpty) {
req.headers.addAll(header);
2024-02-18 16:23:10 +01:00
}
2024-05-25 18:09:39 +02:00
int responseStatusCode = 0;
String responseBody = '';
Map<String, String> responseHeaders = {};
try {
final response = await req.send();
responseBody = await response.stream.bytesToString();
responseStatusCode = response.statusCode;
responseHeaders = response.headers;
} catch (exc, trace) {
RequestLog.addRequestException(name, t0, method, uri, req.body, req.headers, exc, trace);
2024-05-26 00:20:25 +02:00
showPlatformToast(child: Text('Request "${name}" failed'), context: ToastProvider.context);
ApplicationLog.error('Request "${name}" failed: ' + exc.toString(), trace: trace);
2024-05-25 18:09:39 +02:00
rethrow;
}
2024-02-18 17:36:58 +01:00
2024-05-25 18:09:39 +02:00
if (responseStatusCode != 200) {
try {
2024-05-25 21:36:05 +02:00
final apierr = APIError.fromJson(jsonDecode(responseBody) as Map<String, dynamic>);
2024-02-18 17:36:58 +01:00
2024-05-25 18:09:39 +02:00
RequestLog.addRequestAPIError(name, t0, method, uri, req.body, req.headers, responseStatusCode, responseBody, responseHeaders, apierr);
showPlatformToast(child: Text('Request "${name}" is fehlgeschlagen'), context: ToastProvider.context);
throw Exception(apierr.message);
2024-05-26 00:20:25 +02:00
} catch (exc, trace) {
ApplicationLog.warn('Failed to decode api response as error-object', additional: exc.toString() + "\nBody:\n" + responseBody, trace: trace);
}
2024-02-18 17:36:58 +01:00
2024-05-25 18:09:39 +02:00
RequestLog.addRequestErrorStatuscode(name, t0, method, uri, req.body, req.headers, responseStatusCode, responseBody, responseHeaders);
showPlatformToast(child: Text('Request "${name}" is fehlgeschlagen'), context: ToastProvider.context);
throw Exception('API request failed with status code ${responseStatusCode}');
2024-02-18 17:36:58 +01:00
}
2024-05-25 18:09:39 +02:00
try {
final data = jsonDecode(responseBody);
if (fn != null) {
2024-05-25 21:36:05 +02:00
final result = fn(data as Map<String, dynamic>);
2024-05-25 18:09:39 +02:00
RequestLog.addRequestSuccess(name, t0, method, uri, req.body, req.headers, responseStatusCode, responseBody, responseHeaders);
return result;
} else {
RequestLog.addRequestSuccess(name, t0, method, uri, req.body, req.headers, responseStatusCode, responseBody, responseHeaders);
return null as T;
}
} catch (exc, trace) {
RequestLog.addRequestDecodeError(name, t0, method, uri, req.body, req.headers, responseStatusCode, responseBody, responseHeaders, exc, trace);
showPlatformToast(child: Text('Request "${name}" is fehlgeschlagen'), context: ToastProvider.context);
2024-05-26 00:20:25 +02:00
ApplicationLog.error('Failed to decode response: ' + exc.toString(), additional: "\nBody:\n" + responseBody, trace: trace);
2024-05-25 18:09:39 +02:00
rethrow;
2024-02-18 17:36:58 +01:00
}
2024-05-25 18:09:39 +02:00
}
2024-02-18 17:36:58 +01:00
2024-05-25 18:09:39 +02:00
// ==========================================================================================================================================================
2024-02-18 17:36:58 +01:00
2024-05-25 18:09:39 +02:00
static Future<bool> verifyToken(String uid, String tok) async {
try {
await _request<void>(
name: 'verifyToken',
method: 'GET',
relURL: '/users/$uid',
fn: null,
auth: KeyTokenAuth(userId: uid, token: tok),
);
return true;
} catch (e) {
return false;
2024-02-18 17:36:58 +01:00
}
2024-05-25 18:09:39 +02:00
}
2024-02-18 17:36:58 +01:00
2024-05-25 18:09:39 +02:00
static Future<User> getUser(KeyTokenAuth auth, String uid) async {
return await _request(
name: 'getUser',
method: 'GET',
relURL: 'users/$uid',
fn: User.fromJson,
auth: auth,
);
}
2024-02-18 17:36:58 +01:00
2024-05-25 18:09:39 +02:00
static Future<List<ChannelWithSubscription>> getChannelList(KeyTokenAuth auth, ChannelSelector sel) async {
return await _request(
name: 'getChannelList',
method: 'GET',
relURL: 'users/${auth.userId}/channels',
query: {'selector': sel.apiKey},
2024-05-25 21:36:05 +02:00
fn: (json) => ChannelWithSubscription.fromJsonArray(json['channels'] as List<dynamic>),
2024-05-25 18:09:39 +02:00
auth: auth,
);
}
2024-02-18 17:36:58 +01:00
2024-05-25 18:09:39 +02:00
static Future<(String, List<Message>)> getMessageList(KeyTokenAuth auth, String pageToken, int? pageSize) async {
return await _request(
name: 'getMessageList',
method: 'GET',
relURL: 'messages',
query: {'next_page_token': pageToken, if (pageSize != null) 'page_size': pageSize.toString()},
fn: (json) => Message.fromPaginatedJsonArray(json, 'messages', 'next_page_token'),
auth: auth,
);
2024-05-21 23:20:34 +02:00
}
static Future<Message> getMessage(KeyTokenAuth auth, String msgid) async {
2024-05-25 18:09:39 +02:00
return await _request(
name: 'getMessage',
method: 'GET',
relURL: 'messages/$msgid',
query: {},
fn: Message.fromJson,
auth: auth,
);
2024-02-18 17:36:58 +01:00
}
2024-02-11 01:08:51 +01:00
}