2024-02-18 16:23:10 +01:00
|
|
|
import 'dart:convert';
|
|
|
|
|
2024-05-25 18:09:39 +02:00
|
|
|
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-05-27 17:21:29 +02:00
|
|
|
import 'package:simplecloudnotifier/models/client.dart';
|
2024-02-18 17:36:58 +01:00
|
|
|
import 'package:simplecloudnotifier/models/key_token_auth.dart';
|
2024-05-27 17:21:29 +02:00
|
|
|
import 'package:simplecloudnotifier/models/keytoken.dart';
|
|
|
|
import 'package:simplecloudnotifier/models/subscription.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-05-31 23:21:24 +02:00
|
|
|
import 'package:simplecloudnotifier/utils/toaster.dart';
|
2024-02-18 17:36:58 +01:00
|
|
|
|
|
|
|
enum ChannelSelector {
|
|
|
|
owned(apiKey: 'owned'), // Return all channels of the user
|
2024-05-27 17:21:29 +02:00
|
|
|
subscribedAny(apiKey: 'subscribed_any'), // Return all channels that the user is subscribing to (even unconfirmed)
|
|
|
|
allAny(apiKey: 'all_any'), // Return channels that the user owns or is subscribing (even unconfirmed)
|
|
|
|
subscribed(apiKey: 'subscribed'), // Return all channels that the user is subscribing to
|
|
|
|
all(apiKey: 'all'); // Return channels that the user owns or is subscribing
|
2024-02-18 17:36:58 +01:00
|
|
|
|
|
|
|
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,
|
2024-06-01 14:00:16 +02:00
|
|
|
String? authToken,
|
2024-05-25 18:09:39 +02:00
|
|
|
Map<String, String>? header,
|
|
|
|
}) async {
|
|
|
|
final t0 = DateTime.now();
|
|
|
|
|
|
|
|
final uri = Uri.parse('$_base/$relURL').replace(queryParameters: query ?? {});
|
|
|
|
|
|
|
|
final req = http.Request(method, uri);
|
|
|
|
|
2024-06-01 03:06:02 +02:00
|
|
|
print('[REQUEST|RUN] [${method}] ${name}');
|
|
|
|
|
2024-05-25 18:09:39 +02:00
|
|
|
if (jsonBody != null) {
|
|
|
|
req.body = jsonEncode(jsonBody);
|
|
|
|
req.headers['Content-Type'] = 'application/json';
|
|
|
|
}
|
2024-02-11 01:08:51 +01:00
|
|
|
|
2024-06-01 14:00:16 +02:00
|
|
|
if (authToken != null) {
|
|
|
|
req.headers['Authorization'] = 'SCN ${authToken}';
|
2024-05-25 18:09:39 +02:00
|
|
|
}
|
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-31 23:21:24 +02:00
|
|
|
Toaster.error("Error", 'Request "${name}" failed');
|
2024-05-26 00:20:25 +02:00
|
|
|
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);
|
2024-05-31 23:21:24 +02:00
|
|
|
Toaster.error("Error", 'Request "${name}" failed');
|
2024-05-25 18:09:39 +02:00
|
|
|
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);
|
2024-05-31 23:21:24 +02:00
|
|
|
Toaster.error("Error", 'Request "${name}" failed');
|
2024-05-25 18:09:39 +02:00
|
|
|
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);
|
2024-06-01 03:06:02 +02:00
|
|
|
print('[REQUEST|FIN] [${method}] ${name}');
|
2024-05-25 18:09:39 +02:00
|
|
|
return result;
|
|
|
|
} else {
|
|
|
|
RequestLog.addRequestSuccess(name, t0, method, uri, req.body, req.headers, responseStatusCode, responseBody, responseHeaders);
|
2024-06-01 03:06:02 +02:00
|
|
|
print('[REQUEST|FIN] [${method}] ${name}');
|
2024-05-25 18:09:39 +02:00
|
|
|
return null as T;
|
|
|
|
}
|
|
|
|
} catch (exc, trace) {
|
|
|
|
RequestLog.addRequestDecodeError(name, t0, method, uri, req.body, req.headers, responseStatusCode, responseBody, responseHeaders, exc, trace);
|
2024-05-31 23:21:24 +02:00
|
|
|
Toaster.error("Error", 'Request "${name}" failed');
|
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<User> getUser(KeyTokenAuth auth, String uid) async {
|
|
|
|
return await _request(
|
|
|
|
name: 'getUser',
|
|
|
|
method: 'GET',
|
|
|
|
relURL: 'users/$uid',
|
|
|
|
fn: User.fromJson,
|
2024-06-01 14:00:16 +02:00
|
|
|
authToken: auth.tokenAdmin,
|
2024-05-25 18:09:39 +02:00
|
|
|
);
|
|
|
|
}
|
2024-02-18 17:36:58 +01:00
|
|
|
|
2024-06-01 14:00:16 +02:00
|
|
|
static Future<Client> addClient(KeyTokenAuth? auth, String fcmToken, String agentModel, String agentVersion, String? name, String clientType) async {
|
2024-05-31 15:22:27 +02:00
|
|
|
return await _request(
|
|
|
|
name: 'addClient',
|
|
|
|
method: 'POST',
|
|
|
|
relURL: 'users/${auth!.userId}/clients',
|
|
|
|
jsonBody: {
|
|
|
|
'fcm_token': fcmToken,
|
|
|
|
'agent_model': agentModel,
|
|
|
|
'agent_version': agentVersion,
|
|
|
|
'client_type': clientType,
|
2024-06-01 14:00:16 +02:00
|
|
|
'name': name,
|
2024-05-31 15:22:27 +02:00
|
|
|
},
|
|
|
|
fn: Client.fromJson,
|
2024-06-01 14:00:16 +02:00
|
|
|
authToken: auth.tokenAdmin,
|
2024-05-31 15:22:27 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-06-01 14:00:16 +02:00
|
|
|
static Future<Client> updateClient(KeyTokenAuth? auth, String clientID, String fcmToken, String agentModel, String? name, String agentVersion) async {
|
2024-05-31 15:22:27 +02:00
|
|
|
return await _request(
|
|
|
|
name: 'updateClient',
|
|
|
|
method: 'PUT',
|
|
|
|
relURL: 'users/${auth!.userId}/clients/$clientID',
|
|
|
|
jsonBody: {
|
|
|
|
'fcm_token': fcmToken,
|
|
|
|
'agent_model': agentModel,
|
|
|
|
'agent_version': agentVersion,
|
2024-06-01 14:00:16 +02:00
|
|
|
'name': name,
|
2024-05-31 15:22:27 +02:00
|
|
|
},
|
|
|
|
fn: Client.fromJson,
|
2024-06-01 14:00:16 +02:00
|
|
|
authToken: auth.tokenAdmin,
|
2024-05-31 15:22:27 +02: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-06-01 14:00:16 +02:00
|
|
|
authToken: auth.tokenAdmin,
|
2024-05-25 18:09:39 +02:00
|
|
|
);
|
|
|
|
}
|
2024-02-18 17:36:58 +01:00
|
|
|
|
2024-05-26 19:24:19 +02:00
|
|
|
static Future<(String, List<Message>)> getMessageList(KeyTokenAuth auth, String pageToken, {int? pageSize, List<String>? channelIDs}) async {
|
2024-05-25 18:09:39 +02:00
|
|
|
return await _request(
|
|
|
|
name: 'getMessageList',
|
|
|
|
method: 'GET',
|
|
|
|
relURL: 'messages',
|
2024-05-26 19:24:19 +02:00
|
|
|
query: {
|
|
|
|
'next_page_token': pageToken,
|
|
|
|
if (pageSize != null) 'page_size': pageSize.toString(),
|
|
|
|
if (channelIDs != null) 'channel_id': channelIDs.join(","),
|
|
|
|
},
|
2024-05-25 18:09:39 +02:00
|
|
|
fn: (json) => Message.fromPaginatedJsonArray(json, 'messages', 'next_page_token'),
|
2024-06-01 14:00:16 +02:00
|
|
|
authToken: auth.tokenAdmin,
|
2024-05-25 18:09:39 +02:00
|
|
|
);
|
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,
|
2024-06-01 14:00:16 +02:00
|
|
|
authToken: auth.tokenAdmin,
|
2024-05-25 18:09:39 +02:00
|
|
|
);
|
2024-02-18 17:36:58 +01:00
|
|
|
}
|
2024-05-27 17:21:29 +02:00
|
|
|
|
|
|
|
static Future<List<Subscription>> getSubscriptionList(KeyTokenAuth auth) async {
|
|
|
|
return await _request(
|
|
|
|
name: 'getSubscriptionList',
|
|
|
|
method: 'GET',
|
|
|
|
relURL: 'users/${auth.userId}/subscriptions',
|
|
|
|
fn: (json) => Subscription.fromJsonArray(json['subscriptions'] as List<dynamic>),
|
2024-06-01 14:00:16 +02:00
|
|
|
authToken: auth.tokenAdmin,
|
2024-05-27 17:21:29 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
static Future<List<Client>> getClientList(KeyTokenAuth auth) async {
|
|
|
|
return await _request(
|
|
|
|
name: 'getClientList',
|
|
|
|
method: 'GET',
|
|
|
|
relURL: 'users/${auth.userId}/clients',
|
|
|
|
fn: (json) => Client.fromJsonArray(json['clients'] as List<dynamic>),
|
2024-06-01 14:00:16 +02:00
|
|
|
authToken: auth.tokenAdmin,
|
2024-05-27 17:21:29 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
static Future<List<KeyToken>> getKeyTokenList(KeyTokenAuth auth) async {
|
|
|
|
return await _request(
|
|
|
|
name: 'getKeyTokenList',
|
|
|
|
method: 'GET',
|
|
|
|
relURL: 'users/${auth.userId}/keys',
|
|
|
|
fn: (json) => KeyToken.fromJsonArray(json['keys'] as List<dynamic>),
|
2024-06-01 14:00:16 +02:00
|
|
|
authToken: auth.tokenAdmin,
|
2024-05-27 17:21:29 +02:00
|
|
|
);
|
|
|
|
}
|
2024-06-01 03:06:02 +02:00
|
|
|
|
2024-06-01 14:00:16 +02:00
|
|
|
static Future<UserWithClientsAndKeys> createUserWithClient(String? username, String clientFcmToken, String clientAgentModel, String clientAgentVersion, String? clientName, String clientType) async {
|
2024-06-01 03:06:02 +02:00
|
|
|
return await _request(
|
|
|
|
name: 'createUserWithClient',
|
|
|
|
method: 'POST',
|
|
|
|
relURL: 'users',
|
|
|
|
jsonBody: {
|
|
|
|
'username': username,
|
|
|
|
'fcm_token': clientFcmToken,
|
|
|
|
'agent_model': clientAgentModel,
|
|
|
|
'agent_version': clientAgentVersion,
|
2024-06-01 14:00:16 +02:00
|
|
|
'client_name': clientName,
|
2024-06-01 03:06:02 +02:00
|
|
|
'client_type': clientType,
|
|
|
|
'no_client': false,
|
|
|
|
},
|
|
|
|
fn: UserWithClientsAndKeys.fromJson,
|
|
|
|
);
|
|
|
|
}
|
2024-06-01 14:00:16 +02:00
|
|
|
|
|
|
|
static Future<KeyToken> getKeyToken(KeyTokenAuth auth, String kid) async {
|
|
|
|
return await _request(
|
|
|
|
name: 'getKeyToken',
|
|
|
|
method: 'GET',
|
|
|
|
relURL: 'users/${auth.userId}/keys/$kid',
|
|
|
|
fn: KeyToken.fromJson,
|
|
|
|
authToken: auth.tokenAdmin,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
static Future<KeyToken> getKeyTokenByToken(String userid, String token) async {
|
|
|
|
return await _request(
|
|
|
|
name: 'getCurrentKeyToken',
|
|
|
|
method: 'GET',
|
|
|
|
relURL: 'users/${userid}/keys/current',
|
|
|
|
fn: KeyToken.fromJson,
|
|
|
|
authToken: token,
|
|
|
|
);
|
|
|
|
}
|
2024-02-11 01:08:51 +01:00
|
|
|
}
|