mirror of
https://git.vectorsigma.ru/public/immich.git
synced 2026-07-29 00:18:18 +00:00
apply custom headers immediately
This commit is contained in:
@@ -90,12 +90,13 @@ class NetworkApiImpl: NetworkApi {
|
||||
URLSessionManager.cookieStorage.removeCookies(since: .distantPast)
|
||||
}
|
||||
|
||||
guard let groupDefaults = UserDefaults(suiteName: APP_GROUP) else { return }
|
||||
if headers != groupDefaults.dictionary(forKey: HEADERS_KEY) as? [String: String] {
|
||||
groupDefaults.set(headers, forKey: HEADERS_KEY)
|
||||
if serverUrls.first != UserDefaults.group.string(forKey: SERVER_URL_KEY) {
|
||||
UserDefaults.group.set(serverUrls.first, forKey: SERVER_URL_KEY)
|
||||
}
|
||||
if serverUrls.first != groupDefaults.string(forKey: SERVER_URL_KEY) {
|
||||
groupDefaults.set(serverUrls.first, forKey: SERVER_URL_KEY)
|
||||
|
||||
if headers != UserDefaults.group.dictionary(forKey: HEADERS_KEY) as? [String: String] {
|
||||
UserDefaults.group.set(headers, forKey: HEADERS_KEY)
|
||||
URLSessionManager.shared.recreateSession() // Recreate session to apply custom headers without app restart
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,41 +6,34 @@ let HEADERS_KEY = "immich.request_headers"
|
||||
let SERVER_URL_KEY = "immich.server_url"
|
||||
let APP_GROUP = "group.app.immich.share"
|
||||
|
||||
extension UserDefaults {
|
||||
static let group = UserDefaults(suiteName: APP_GROUP)!
|
||||
}
|
||||
|
||||
/// Manages a shared URLSession with SSL configuration support.
|
||||
/// Old sessions are kept alive by Dart's FFI retain until all isolates release them.
|
||||
class URLSessionManager: NSObject {
|
||||
static let shared = URLSessionManager()
|
||||
|
||||
let session: URLSession
|
||||
private(set) var session: URLSession
|
||||
let delegate: URLSessionManagerDelegate
|
||||
static let cookieStorage = HTTPCookieStorage.sharedCookieStorage(forGroupContainerIdentifier: APP_GROUP)
|
||||
private let configuration = {
|
||||
let config = URLSessionConfiguration.default
|
||||
|
||||
let cacheDir = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)
|
||||
private static let cacheDir: URL = {
|
||||
let dir = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)
|
||||
.first!
|
||||
.appendingPathComponent("api", isDirectory: true)
|
||||
try! FileManager.default.createDirectory(at: cacheDir, withIntermediateDirectories: true)
|
||||
|
||||
config.urlCache = URLCache(
|
||||
memoryCapacity: 0,
|
||||
diskCapacity: 1024 * 1024 * 1024,
|
||||
directory: cacheDir
|
||||
)
|
||||
|
||||
config.httpCookieStorage = cookieStorage
|
||||
config.httpMaximumConnectionsPerHost = 64
|
||||
config.timeoutIntervalForRequest = 60
|
||||
config.timeoutIntervalForResource = 300
|
||||
|
||||
let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "unknown"
|
||||
var headers: [String: String] = ["User-Agent": "Immich_iOS_\(version)"]
|
||||
if let saved = UserDefaults(suiteName: APP_GROUP)?.dictionary(forKey: HEADERS_KEY) as? [String: String] {
|
||||
headers.merge(saved) { _, new in new }
|
||||
}
|
||||
config.httpAdditionalHeaders = headers
|
||||
|
||||
return config
|
||||
try! FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
|
||||
return dir
|
||||
}()
|
||||
private static let urlCache = URLCache(
|
||||
memoryCapacity: 0,
|
||||
diskCapacity: 1024 * 1024 * 1024,
|
||||
directory: cacheDir
|
||||
)
|
||||
private static let userAgent: String = {
|
||||
let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "unknown"
|
||||
return "Immich_iOS_\(version)"
|
||||
}()
|
||||
static let cookieStorage = HTTPCookieStorage.sharedCookieStorage(forGroupContainerIdentifier: APP_GROUP)
|
||||
|
||||
var sessionPointer: UnsafeMutableRawPointer {
|
||||
Unmanaged.passUnretained(session).toOpaque()
|
||||
@@ -48,9 +41,28 @@ class URLSessionManager: NSObject {
|
||||
|
||||
private override init() {
|
||||
delegate = URLSessionManagerDelegate()
|
||||
session = URLSession(configuration: configuration, delegate: delegate, delegateQueue: nil)
|
||||
session = Self.buildSession(delegate: delegate)
|
||||
super.init()
|
||||
}
|
||||
|
||||
func recreateSession() {
|
||||
session = Self.buildSession(delegate: delegate)
|
||||
}
|
||||
|
||||
private static func buildSession(delegate: URLSessionManagerDelegate) -> URLSession {
|
||||
let config = URLSessionConfiguration.default
|
||||
config.urlCache = urlCache
|
||||
config.httpCookieStorage = cookieStorage
|
||||
config.httpMaximumConnectionsPerHost = 64
|
||||
config.timeoutIntervalForRequest = 60
|
||||
config.timeoutIntervalForResource = 300
|
||||
|
||||
var headers = UserDefaults.group.dictionary(forKey: HEADERS_KEY) as? [String: String] ?? [:]
|
||||
headers["User-Agent"] = headers["User-Agent"] ?? userAgent
|
||||
config.httpAdditionalHeaders = headers
|
||||
|
||||
return URLSession(configuration: config, delegate: delegate, delegateQueue: nil)
|
||||
}
|
||||
}
|
||||
|
||||
class URLSessionManagerDelegate: NSObject, URLSessionTaskDelegate, URLSessionWebSocketDelegate {
|
||||
|
||||
@@ -9,26 +9,37 @@ import 'package:web_socket/web_socket.dart';
|
||||
|
||||
class NetworkRepository {
|
||||
static http.Client? _client;
|
||||
static late Pointer<Void> _clientPointer;
|
||||
static Pointer<Void>? _clientPointer;
|
||||
|
||||
static Future<void> init() async {
|
||||
_clientPointer = Pointer<Void>.fromAddress(await networkApi.getClientPointer());
|
||||
final clientPointer = Pointer<Void>.fromAddress(await networkApi.getClientPointer());
|
||||
if (clientPointer == _clientPointer) {
|
||||
return;
|
||||
}
|
||||
_clientPointer = clientPointer;
|
||||
_client?.close();
|
||||
if (Platform.isIOS) {
|
||||
final session = URLSession.fromRawPointer(_clientPointer.cast());
|
||||
final session = URLSession.fromRawPointer(clientPointer.cast());
|
||||
_client = CupertinoClient.fromSharedSession(session);
|
||||
} else {
|
||||
_client = OkHttpClient.fromJniGlobalRef(_clientPointer);
|
||||
_client = OkHttpClient.fromJniGlobalRef(clientPointer);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> setHeaders(Map<String, String> headers, List<String> serverUrls) async {
|
||||
await networkApi.setRequestHeaders(headers, serverUrls);
|
||||
if (Platform.isIOS) {
|
||||
await init();
|
||||
}
|
||||
}
|
||||
|
||||
// ignore: avoid-unused-parameters
|
||||
static Future<WebSocket> createWebSocket(Uri uri, {Map<String, String>? headers, Iterable<String>? protocols}) {
|
||||
if (Platform.isIOS) {
|
||||
final session = URLSession.fromRawPointer(_clientPointer.cast());
|
||||
final session = URLSession.fromRawPointer(_clientPointer!.cast());
|
||||
return CupertinoWebSocket.connectWithSession(session, uri, protocols: protocols);
|
||||
} else {
|
||||
return OkHttpWebSocket.connectFromJniGlobalRef(_clientPointer, uri, protocols: protocols);
|
||||
return OkHttpWebSocket.connectFromJniGlobalRef(_clientPointer!, uri, protocols: protocols);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,8 +8,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/generated/translations.g.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
import 'package:immich_mobile/providers/api.provider.dart';
|
||||
|
||||
class SettingsHeader {
|
||||
String key = "";
|
||||
@@ -76,7 +75,7 @@ class HeaderSettingsPage extends HookConsumerWidget {
|
||||
],
|
||||
),
|
||||
body: PopScope(
|
||||
onPopInvokedWithResult: (didPop, _) => saveHeaders(headers.value),
|
||||
onPopInvokedWithResult: (didPop, _) => saveHeaders(ref, headers.value),
|
||||
child: ListView.separated(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 16.0),
|
||||
itemCount: list.length,
|
||||
@@ -88,7 +87,7 @@ class HeaderSettingsPage extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
saveHeaders(List<SettingsHeader> headers) async {
|
||||
saveHeaders(WidgetRef ref, List<SettingsHeader> headers) async {
|
||||
final headersMap = {};
|
||||
for (var header in headers) {
|
||||
final key = header.key.trim();
|
||||
@@ -100,7 +99,7 @@ class HeaderSettingsPage extends HookConsumerWidget {
|
||||
|
||||
var encoded = jsonEncode(headersMap);
|
||||
await Store.put(StoreKey.customHeaders, encoded);
|
||||
await networkApi.setRequestHeaders(ApiService.getRequestHeaders(), ApiService.getServerUrls());
|
||||
await ref.read(apiServiceProvider).updateHeaders();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/models/auth/auth_state.model.dart';
|
||||
import 'package:immich_mobile/models/auth/login_response.model.dart';
|
||||
import 'package:immich_mobile/providers/api.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
import 'package:immich_mobile/services/auth.service.dart';
|
||||
@@ -91,7 +90,7 @@ class AuthNotifier extends StateNotifier<AuthState> {
|
||||
await _widgetService.clearCredentials();
|
||||
|
||||
await _authService.logout();
|
||||
await networkApi.setRequestHeaders(const {}, const []);
|
||||
await _apiService.updateHeaders();
|
||||
await _ref.read(backgroundUploadServiceProvider).cancel();
|
||||
_ref.read(foregroundUploadServiceProvider).cancel();
|
||||
} finally {
|
||||
@@ -126,7 +125,7 @@ class AuthNotifier extends StateNotifier<AuthState> {
|
||||
|
||||
Future<bool> saveAuthInfo({required String accessToken}) async {
|
||||
await _apiService.setAccessToken(accessToken);
|
||||
await networkApi.setRequestHeaders(ApiService.getRequestHeaders(), ApiService.getServerUrls());
|
||||
await _apiService.updateHeaders();
|
||||
|
||||
final serverEndpoint = Store.get(StoreKey.serverEndpoint);
|
||||
final customHeaders = Store.tryGet(StoreKey.customHeaders);
|
||||
|
||||
@@ -48,6 +48,11 @@ class ApiService implements Authentication {
|
||||
String? _accessToken;
|
||||
final _log = Logger("ApiService");
|
||||
|
||||
Future<void> updateHeaders() async {
|
||||
await NetworkRepository.setHeaders(getRequestHeaders(), getServerUrls());
|
||||
_apiClient.client = NetworkRepository.client;
|
||||
}
|
||||
|
||||
setEndpoint(String endpoint) {
|
||||
_apiClient = ApiClient(basePath: endpoint, authentication: this);
|
||||
_apiClient.client = NetworkRepository.client;
|
||||
|
||||
Reference in New Issue
Block a user