2023-07-27 20:05:27 -07:00
|
|
|
import 'dart:async';
|
2023-01-19 07:45:37 -08:00
|
|
|
import 'dart:convert';
|
2023-07-27 20:05:27 -07:00
|
|
|
import 'dart:io';
|
2023-01-19 07:45:37 -08:00
|
|
|
|
2024-11-26 12:43:44 -06:00
|
|
|
import 'package:device_info_plus/device_info_plus.dart';
|
2025-02-20 00:57:32 +05:30
|
|
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
2024-04-30 21:36:40 -05:00
|
|
|
import 'package:immich_mobile/entities/store.entity.dart';
|
2026-03-05 12:04:45 -05:00
|
|
|
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
|
2026-06-12 04:35:50 +05:30
|
|
|
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
|
2025-10-27 20:02:52 +05:30
|
|
|
import 'package:immich_mobile/utils/debug_print.dart';
|
2023-01-19 07:45:37 -08:00
|
|
|
import 'package:immich_mobile/utils/url_helper.dart';
|
2024-04-23 16:09:10 -05:00
|
|
|
import 'package:logging/logging.dart';
|
2022-07-13 07:23:48 -05:00
|
|
|
import 'package:openapi/api.dart';
|
|
|
|
|
|
2026-03-11 12:07:27 -05:00
|
|
|
class ApiService {
|
2026-05-29 03:14:11 +05:30
|
|
|
final ApiClient _apiClient = ApiClient(basePath: '');
|
2022-07-13 07:23:48 -05:00
|
|
|
|
2024-05-30 00:26:57 +02:00
|
|
|
late UsersApi usersApi;
|
2022-07-13 07:23:48 -05:00
|
|
|
late AuthenticationApi authenticationApi;
|
2025-11-11 17:01:14 -05:00
|
|
|
late AuthenticationApi oAuthApi;
|
2024-05-30 00:26:57 +02:00
|
|
|
late AlbumsApi albumsApi;
|
|
|
|
|
late AssetsApi assetsApi;
|
2023-03-23 11:08:14 -04:00
|
|
|
late SearchApi searchApi;
|
2024-08-20 08:50:14 -04:00
|
|
|
late ServerApi serverInfoApi;
|
2024-05-29 17:51:01 +02:00
|
|
|
late MapApi mapApi;
|
2024-05-30 00:26:57 +02:00
|
|
|
late PartnersApi partnersApi;
|
|
|
|
|
late PeopleApi peopleApi;
|
|
|
|
|
late SharedLinksApi sharedLinksApi;
|
2024-05-14 17:35:37 +02:00
|
|
|
late SyncApi syncApi;
|
2023-11-09 17:10:56 +01:00
|
|
|
late SystemConfigApi systemConfigApi;
|
2024-05-30 00:26:57 +02:00
|
|
|
late ActivitiesApi activitiesApi;
|
2024-02-08 16:57:54 -05:00
|
|
|
late DownloadApi downloadApi;
|
|
|
|
|
late TrashApi trashApi;
|
2024-08-19 13:37:15 -04:00
|
|
|
late StacksApi stacksApi;
|
2025-11-11 17:01:14 -05:00
|
|
|
late ViewsApi viewApi;
|
2025-02-25 19:10:31 -06:00
|
|
|
late MemoriesApi memoriesApi;
|
2025-06-08 21:55:23 -05:00
|
|
|
late SessionsApi sessionsApi;
|
2026-02-18 13:16:26 -08:00
|
|
|
late TagsApi tagsApi;
|
2022-07-13 07:23:48 -05:00
|
|
|
|
2023-01-19 07:45:37 -08:00
|
|
|
ApiService() {
|
2025-03-18 19:02:33 +05:30
|
|
|
// The below line ensures that the api clients are initialized when the service is instantiated
|
|
|
|
|
// This is required to avoid late initialization errors when the clients are access before the endpoint is resolved
|
|
|
|
|
setEndpoint('');
|
2023-03-23 02:36:44 +01:00
|
|
|
final endpoint = Store.tryGet(StoreKey.serverEndpoint);
|
|
|
|
|
if (endpoint != null && endpoint.isNotEmpty) {
|
|
|
|
|
setEndpoint(endpoint);
|
2023-01-19 07:45:37 -08:00
|
|
|
}
|
|
|
|
|
}
|
2024-04-23 16:09:10 -05:00
|
|
|
final _log = Logger("ApiService");
|
2023-01-19 07:45:37 -08:00
|
|
|
|
2026-03-05 12:04:45 -05:00
|
|
|
Future<void> updateHeaders() async {
|
|
|
|
|
await NetworkRepository.setHeaders(getRequestHeaders(), getServerUrls());
|
|
|
|
|
_apiClient.client = NetworkRepository.client;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-30 01:56:46 -07:00
|
|
|
void setEndpoint(String endpoint) {
|
2026-05-29 03:14:11 +05:30
|
|
|
_apiClient.basePath = endpoint;
|
2026-03-05 12:04:45 -05:00
|
|
|
_apiClient.client = NetworkRepository.client;
|
2024-05-30 00:26:57 +02:00
|
|
|
usersApi = UsersApi(_apiClient);
|
2022-07-13 07:23:48 -05:00
|
|
|
authenticationApi = AuthenticationApi(_apiClient);
|
2025-11-11 17:01:14 -05:00
|
|
|
oAuthApi = AuthenticationApi(_apiClient);
|
2024-05-30 00:26:57 +02:00
|
|
|
albumsApi = AlbumsApi(_apiClient);
|
|
|
|
|
assetsApi = AssetsApi(_apiClient);
|
2024-08-20 08:50:14 -04:00
|
|
|
serverInfoApi = ServerApi(_apiClient);
|
2023-03-23 11:08:14 -04:00
|
|
|
searchApi = SearchApi(_apiClient);
|
2024-05-29 17:51:01 +02:00
|
|
|
mapApi = MapApi(_apiClient);
|
2024-05-30 00:26:57 +02:00
|
|
|
partnersApi = PartnersApi(_apiClient);
|
|
|
|
|
peopleApi = PeopleApi(_apiClient);
|
|
|
|
|
sharedLinksApi = SharedLinksApi(_apiClient);
|
2024-05-14 17:35:37 +02:00
|
|
|
syncApi = SyncApi(_apiClient);
|
2023-11-09 17:10:56 +01:00
|
|
|
systemConfigApi = SystemConfigApi(_apiClient);
|
2024-05-30 00:26:57 +02:00
|
|
|
activitiesApi = ActivitiesApi(_apiClient);
|
2024-02-08 16:57:54 -05:00
|
|
|
downloadApi = DownloadApi(_apiClient);
|
|
|
|
|
trashApi = TrashApi(_apiClient);
|
2024-08-19 13:37:15 -04:00
|
|
|
stacksApi = StacksApi(_apiClient);
|
2025-11-11 17:01:14 -05:00
|
|
|
viewApi = ViewsApi(_apiClient);
|
2025-02-25 19:10:31 -06:00
|
|
|
memoriesApi = MemoriesApi(_apiClient);
|
2025-06-08 21:55:23 -05:00
|
|
|
sessionsApi = SessionsApi(_apiClient);
|
2026-02-18 13:16:26 -08:00
|
|
|
tagsApi = TagsApi(_apiClient);
|
2022-07-13 07:23:48 -05:00
|
|
|
}
|
|
|
|
|
|
2023-01-19 07:45:37 -08:00
|
|
|
Future<String> resolveAndSetEndpoint(String serverUrl) async {
|
2024-12-05 09:11:48 -06:00
|
|
|
final endpoint = await resolveEndpoint(serverUrl);
|
2023-01-19 07:45:37 -08:00
|
|
|
setEndpoint(endpoint);
|
|
|
|
|
|
2024-11-26 12:43:44 -06:00
|
|
|
// Save in local database for next startup
|
2025-10-27 20:02:52 +05:30
|
|
|
await Store.put(StoreKey.serverEndpoint, endpoint);
|
2023-01-19 07:45:37 -08:00
|
|
|
return endpoint;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Takes a server URL and attempts to resolve the API endpoint.
|
|
|
|
|
///
|
|
|
|
|
/// Input: [schema://]host[:port][/path]
|
|
|
|
|
/// schema - optional (default: https)
|
|
|
|
|
/// host - required
|
|
|
|
|
/// port - optional (default: based on schema)
|
|
|
|
|
/// path - optional
|
2024-12-05 09:11:48 -06:00
|
|
|
Future<String> resolveEndpoint(String serverUrl) async {
|
2026-07-23 07:16:48 -07:00
|
|
|
String url = normalizeServerUrl(serverUrl);
|
2023-07-27 20:05:27 -07:00
|
|
|
|
2023-01-19 07:45:37 -08:00
|
|
|
// Check for /.well-known/immich
|
|
|
|
|
final wellKnownEndpoint = await _getWellKnownEndpoint(url);
|
2025-03-05 03:25:57 +01:00
|
|
|
if (wellKnownEndpoint.isNotEmpty) {
|
2026-07-23 07:16:48 -07:00
|
|
|
url = normalizeServerUrl(wellKnownEndpoint);
|
2025-03-05 03:25:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!await _isEndpointAvailable(url)) {
|
|
|
|
|
throw ApiException(503, "Server is not reachable");
|
|
|
|
|
}
|
2023-01-19 07:45:37 -08:00
|
|
|
|
|
|
|
|
// Otherwise, assume the URL provided is the api endpoint
|
|
|
|
|
return url;
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-27 20:05:27 -07:00
|
|
|
Future<bool> _isEndpointAvailable(String serverUrl) async {
|
|
|
|
|
if (!serverUrl.endsWith('/api')) {
|
|
|
|
|
serverUrl += '/api';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
2026-07-30 01:56:46 -07:00
|
|
|
setEndpoint(serverUrl);
|
2025-07-01 06:02:50 +08:00
|
|
|
await serverInfoApi.pingServer().timeout(const Duration(seconds: 5));
|
2023-07-27 20:05:27 -07:00
|
|
|
} on TimeoutException catch (_) {
|
|
|
|
|
return false;
|
|
|
|
|
} on SocketException catch (_) {
|
|
|
|
|
return false;
|
2024-04-29 09:17:49 -05:00
|
|
|
} catch (error, stackTrace) {
|
2025-07-29 00:34:03 +05:30
|
|
|
_log.severe("Error while checking server availability", error, stackTrace);
|
2024-04-29 09:17:49 -05:00
|
|
|
return false;
|
2023-07-27 20:05:27 -07:00
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-19 07:45:37 -08:00
|
|
|
Future<String> _getWellKnownEndpoint(String baseUrl) async {
|
|
|
|
|
try {
|
2026-03-05 12:04:45 -05:00
|
|
|
final res = await NetworkRepository.client
|
|
|
|
|
.get(Uri.parse("$baseUrl/.well-known/immich"))
|
2025-03-05 03:25:57 +01:00
|
|
|
.timeout(const Duration(seconds: 5));
|
2023-01-19 07:45:37 -08:00
|
|
|
|
|
|
|
|
if (res.statusCode == 200) {
|
|
|
|
|
final data = jsonDecode(res.body);
|
|
|
|
|
final endpoint = data['api']['endpoint'].toString();
|
|
|
|
|
|
|
|
|
|
if (endpoint.startsWith('/')) {
|
|
|
|
|
// Full URL is relative to base
|
|
|
|
|
return "$baseUrl$endpoint";
|
|
|
|
|
}
|
|
|
|
|
return endpoint;
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
2025-09-12 18:56:00 -04:00
|
|
|
dPrint(() => "Could not locate /.well-known/immich at $baseUrl");
|
2023-01-19 07:45:37 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-26 12:43:44 -06:00
|
|
|
Future<void> setDeviceInfoHeader() async {
|
2026-07-30 01:56:46 -07:00
|
|
|
final DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
|
2024-11-26 12:43:44 -06:00
|
|
|
|
|
|
|
|
if (Platform.isIOS) {
|
|
|
|
|
final iosInfo = await deviceInfoPlugin.iosInfo;
|
2025-07-25 08:07:22 +05:30
|
|
|
authenticationApi.apiClient.addDefaultHeader('deviceModel', iosInfo.utsname.machine);
|
2024-11-26 12:43:44 -06:00
|
|
|
authenticationApi.apiClient.addDefaultHeader('deviceType', 'iOS');
|
2025-03-24 10:26:05 -06:00
|
|
|
} else if (Platform.isAndroid) {
|
2024-11-26 12:43:44 -06:00
|
|
|
final androidInfo = await deviceInfoPlugin.androidInfo;
|
2025-07-25 08:07:22 +05:30
|
|
|
authenticationApi.apiClient.addDefaultHeader('deviceModel', androidInfo.model);
|
2024-11-26 12:43:44 -06:00
|
|
|
authenticationApi.apiClient.addDefaultHeader('deviceType', 'Android');
|
2025-03-24 10:26:05 -06:00
|
|
|
} else {
|
|
|
|
|
authenticationApi.apiClient.addDefaultHeader('deviceModel', 'Unknown');
|
|
|
|
|
authenticationApi.apiClient.addDefaultHeader('deviceType', 'Unknown');
|
2024-11-26 12:43:44 -06:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-05 12:04:45 -05:00
|
|
|
static List<String> getServerUrls() {
|
|
|
|
|
final urls = <String>[];
|
|
|
|
|
final serverEndpoint = Store.tryGet(StoreKey.serverEndpoint);
|
|
|
|
|
if (serverEndpoint != null && serverEndpoint.isNotEmpty) {
|
|
|
|
|
urls.add(serverEndpoint);
|
|
|
|
|
}
|
2026-05-30 20:57:55 +05:30
|
|
|
final network = SettingsRepository.instance.appConfig.network;
|
2026-05-18 21:52:42 +05:30
|
|
|
final localEndpoint = network.localEndpoint;
|
2026-06-12 04:35:50 +05:30
|
|
|
if (localEndpoint != null && localEndpoint.isNotEmpty) {
|
2026-03-05 12:04:45 -05:00
|
|
|
urls.add(localEndpoint);
|
|
|
|
|
}
|
2026-05-18 21:52:42 +05:30
|
|
|
for (final url in network.externalEndpointList) {
|
|
|
|
|
if (url.isNotEmpty) {
|
|
|
|
|
urls.add(url);
|
2026-03-05 12:04:45 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return urls;
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-26 15:31:55 -04:00
|
|
|
static Map<String, String> getRequestHeaders() {
|
2026-05-30 20:57:55 +05:30
|
|
|
return SettingsRepository.instance.appConfig.network.customHeaders;
|
2022-07-13 07:23:48 -05:00
|
|
|
}
|
2023-03-03 23:38:30 +01:00
|
|
|
|
|
|
|
|
ApiClient get apiClient => _apiClient;
|
2022-07-13 07:23:48 -05:00
|
|
|
}
|