platform-side headers

This commit is contained in:
mertalev
2026-02-03 15:07:05 -05:00
parent 577da2bc85
commit 1ab8cad358
24 changed files with 139 additions and 88 deletions

View File

@@ -5,6 +5,7 @@ import app.alextran.immich.BuildConfig
import okhttp3.Cache import okhttp3.Cache
import okhttp3.ConnectionPool import okhttp3.ConnectionPool
import okhttp3.Dispatcher import okhttp3.Dispatcher
import okhttp3.Headers
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.File import java.io.File
@@ -38,6 +39,7 @@ object HttpClientManager {
private val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) } private val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
var headers: Headers = Headers.headersOf("User-Agent", USER_AGENT)
val isMtls: Boolean get() = keyStore.containsAlias(CERT_ALIAS) val isMtls: Boolean get() = keyStore.containsAlias(CERT_ALIAS)
fun initialize(context: Context) { fun initialize(context: Context) {
@@ -92,6 +94,12 @@ object HttpClientManager {
synchronized(this) { clientChangedListeners.add(listener) } synchronized(this) { clientChangedListeners.add(listener) }
} }
fun setRequestHeaders(headerMap: Map<String, String>) {
val builder = Headers.Builder()
headerMap.forEach { (key, value) -> builder.add(key, value) }
headers = builder.build()
}
private fun build(cacheDir: File): OkHttpClient { private fun build(cacheDir: File): OkHttpClient {
val connectionPool = ConnectionPool( val connectionPool = ConnectionPool(
maxIdleConnections = KEEP_ALIVE_CONNECTIONS, maxIdleConnections = KEEP_ALIVE_CONNECTIONS,
@@ -107,8 +115,10 @@ object HttpClientManager {
.apply { init(arrayOf(DynamicKeyManager()), arrayOf(trustManager), null) } .apply { init(arrayOf(DynamicKeyManager()), arrayOf(trustManager), null) }
return OkHttpClient.Builder() return OkHttpClient.Builder()
.addInterceptor { chain -> .addInterceptor {
chain.proceed(chain.request().newBuilder().header("User-Agent", USER_AGENT).build()) val builder = it.request().newBuilder()
headers.forEach { (key, value) -> builder.addHeader(key, value) }
it.proceed(builder.build())
} }
.connectionPool(connectionPool) .connectionPool(connectionPool)
.dispatcher(Dispatcher().apply { maxRequestsPerHost = MAX_REQUESTS_PER_HOST }) .dispatcher(Dispatcher().apply { maxRequestsPerHost = MAX_REQUESTS_PER_HOST })

View File

@@ -228,6 +228,7 @@ interface NetworkApi {
* iOS only - Android should use OkHttpWebSocket.connectWithClient directly. * iOS only - Android should use OkHttpWebSocket.connectWithClient directly.
*/ */
fun createWebSocketTask(url: String, protocols: List<String>?, callback: (Result<WebSocketTaskResult>) -> Unit) fun createWebSocketTask(url: String, protocols: List<String>?, callback: (Result<WebSocketTaskResult>) -> Unit)
fun setRequestHeaders(headers: Map<String, String>)
companion object { companion object {
/** The codec used by NetworkApi. */ /** The codec used by NetworkApi. */
@@ -330,6 +331,24 @@ interface NetworkApi {
channel.setMessageHandler(null) channel.setMessageHandler(null)
} }
} }
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NetworkApi.setRequestHeaders$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val headersArg = args[0] as Map<String, String>
val wrapped: List<Any?> = try {
api.setRequestHeaders(headersArg)
listOf(null)
} catch (exception: Throwable) {
NetworkPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
} }
} }
} }

View File

@@ -104,6 +104,17 @@ private class NetworkApiImpl(private val context: Context) : NetworkApi {
return NativeBuffer.createGlobalRef(client) return NativeBuffer.createGlobalRef(client)
} }
// only used on iOS
override fun createWebSocketTask(
url: String,
protocols: List<String>?,
callback: (Result<WebSocketTaskResult>) -> Unit
) {}
override fun setRequestHeaders(headers: Map<String, String>) {
HttpClientManager.setRequestHeaders(headers)
}
private fun handlePickedFile(uri: Uri) { private fun handlePickedFile(uri: Uri) {
val callback = pendingCallback ?: return val callback = pendingCallback ?: return
pendingCallback = null pendingCallback = null

View File

@@ -47,7 +47,7 @@ private open class RemoteImagesPigeonCodec : StandardMessageCodec() {
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */ /** Generated interface from Pigeon that represents a handler of messages from Flutter. */
interface RemoteImageApi { interface RemoteImageApi {
fun requestImage(url: String, headers: Map<String, String>, requestId: Long, callback: (Result<Map<String, Long>?>) -> Unit) fun requestImage(url: String, requestId: Long, callback: (Result<Map<String, Long>?>) -> Unit)
fun cancelRequest(requestId: Long) fun cancelRequest(requestId: Long)
fun clearCache(callback: (Result<Long>) -> Unit) fun clearCache(callback: (Result<Long>) -> Unit)
@@ -66,9 +66,8 @@ interface RemoteImageApi {
channel.setMessageHandler { message, reply -> channel.setMessageHandler { message, reply ->
val args = message as List<Any?> val args = message as List<Any?>
val urlArg = args[0] as String val urlArg = args[0] as String
val headersArg = args[1] as Map<String, String> val requestIdArg = args[1] as Long
val requestIdArg = args[2] as Long api.requestImage(urlArg, requestIdArg) { result: Result<Map<String, Long>?> ->
api.requestImage(urlArg, headersArg, requestIdArg) { result: Result<Map<String, Long>?> ->
val error = result.exceptionOrNull() val error = result.exceptionOrNull()
if (error != null) { if (error != null) {
reply.reply(RemoteImagesPigeonUtils.wrapError(error)) reply.reply(RemoteImagesPigeonUtils.wrapError(error))

View File

@@ -49,7 +49,6 @@ class RemoteImagesImpl(context: Context) : RemoteImageApi {
override fun requestImage( override fun requestImage(
url: String, url: String,
headers: Map<String, String>,
requestId: Long, requestId: Long,
callback: (Result<Map<String, Long>?>) -> Unit callback: (Result<Map<String, Long>?>) -> Unit
) { ) {
@@ -58,7 +57,6 @@ class RemoteImagesImpl(context: Context) : RemoteImageApi {
ImageFetcherManager.fetch( ImageFetcherManager.fetch(
url, url,
headers,
signal, signal,
onSuccess = { buffer -> onSuccess = { buffer ->
requestMap.remove(requestId) requestMap.remove(requestId)
@@ -119,12 +117,11 @@ private object ImageFetcherManager {
fun fetch( fun fetch(
url: String, url: String,
headers: Map<String, String>,
signal: CancellationSignal, signal: CancellationSignal,
onSuccess: (NativeByteBuffer) -> Unit, onSuccess: (NativeByteBuffer) -> Unit,
onFailure: (Exception) -> Unit, onFailure: (Exception) -> Unit,
) { ) {
fetcher.fetch(url, headers, signal, onSuccess, onFailure) fetcher.fetch(url, signal, onSuccess, onFailure)
} }
fun clearCache(onCleared: (Result<Long>) -> Unit) { fun clearCache(onCleared: (Result<Long>) -> Unit) {
@@ -151,7 +148,6 @@ private object ImageFetcherManager {
private sealed interface ImageFetcher { private sealed interface ImageFetcher {
fun fetch( fun fetch(
url: String, url: String,
headers: Map<String, String>,
signal: CancellationSignal, signal: CancellationSignal,
onSuccess: (NativeByteBuffer) -> Unit, onSuccess: (NativeByteBuffer) -> Unit,
onFailure: (Exception) -> Unit, onFailure: (Exception) -> Unit,
@@ -178,7 +174,6 @@ private class CronetImageFetcher(context: Context, cacheDir: File) : ImageFetche
override fun fetch( override fun fetch(
url: String, url: String,
headers: Map<String, String>,
signal: CancellationSignal, signal: CancellationSignal,
onSuccess: (NativeByteBuffer) -> Unit, onSuccess: (NativeByteBuffer) -> Unit,
onFailure: (Exception) -> Unit, onFailure: (Exception) -> Unit,
@@ -193,7 +188,7 @@ private class CronetImageFetcher(context: Context, cacheDir: File) : ImageFetche
val callback = FetchCallback(onSuccess, onFailure, ::onComplete) val callback = FetchCallback(onSuccess, onFailure, ::onComplete)
val requestBuilder = engine.newUrlRequestBuilder(url, callback, executor) val requestBuilder = engine.newUrlRequestBuilder(url, callback, executor)
headers.forEach { (key, value) -> requestBuilder.addHeader(key, value) } HttpClientManager.headers.forEach { (key, value) -> requestBuilder.addHeader(key, value) }
val request = requestBuilder.build() val request = requestBuilder.build()
signal.setOnCancelListener(request::cancel) signal.setOnCancelListener(request::cancel)
request.start() request.start()
@@ -390,7 +385,6 @@ private class OkHttpImageFetcher private constructor(
override fun fetch( override fun fetch(
url: String, url: String,
headers: Map<String, String>,
signal: CancellationSignal, signal: CancellationSignal,
onSuccess: (NativeByteBuffer) -> Unit, onSuccess: (NativeByteBuffer) -> Unit,
onFailure: (Exception) -> Unit, onFailure: (Exception) -> Unit,
@@ -403,7 +397,6 @@ private class OkHttpImageFetcher private constructor(
} }
val requestBuilder = Request.Builder().url(url) val requestBuilder = Request.Builder().url(url)
headers.forEach { (key, value) -> requestBuilder.addHeader(key, value) }
val call = client.newCall(requestBuilder.build()) val call = client.newCall(requestBuilder.build())
signal.setOnCancelListener(call::cancel) signal.setOnCancelListener(call::cancel)

View File

@@ -261,6 +261,7 @@ protocol NetworkApi {
/// Creates a WebSocket task and waits for connection to be established. /// Creates a WebSocket task and waits for connection to be established.
/// iOS only - Android should use OkHttpWebSocket.connectWithClient directly. /// iOS only - Android should use OkHttpWebSocket.connectWithClient directly.
func createWebSocketTask(url: String, protocols: [String]?, completion: @escaping (Result<WebSocketTaskResult, Error>) -> Void) func createWebSocketTask(url: String, protocols: [String]?, completion: @escaping (Result<WebSocketTaskResult, Error>) -> Void)
func setRequestHeaders(headers: [String: String]) throws
} }
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. /// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
@@ -351,5 +352,20 @@ class NetworkApiSetup {
} else { } else {
createWebSocketTaskChannel.setMessageHandler(nil) createWebSocketTaskChannel.setMessageHandler(nil)
} }
let setRequestHeadersChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NetworkApi.setRequestHeaders\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
setRequestHeadersChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let headersArg = args[0] as! [String: String]
do {
try api.setRequestHeaders(headers: headersArg)
reply(wrapResult(nil))
} catch {
reply(wrapError(error))
}
}
} else {
setRequestHeadersChannel.setMessageHandler(nil)
}
} }
} }

View File

@@ -67,6 +67,10 @@ class NetworkApiImpl: NetworkApi {
} }
} }
} }
func setRequestHeaders(headers: [String : String]) throws {
URLSessionManager.shared.session.configuration.httpAdditionalHeaders = headers
}
} }
private class CertImporter: NSObject, UIDocumentPickerDelegate { private class CertImporter: NSObject, UIDocumentPickerDelegate {

View File

@@ -70,7 +70,7 @@ class RemoteImagesPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable
/// Generated protocol from Pigeon that represents a handler of messages from Flutter. /// Generated protocol from Pigeon that represents a handler of messages from Flutter.
protocol RemoteImageApi { protocol RemoteImageApi {
func requestImage(url: String, headers: [String: String], requestId: Int64, completion: @escaping (Result<[String: Int64]?, Error>) -> Void) func requestImage(url: String, requestId: Int64, completion: @escaping (Result<[String: Int64]?, Error>) -> Void)
func cancelRequest(requestId: Int64) throws func cancelRequest(requestId: Int64) throws
func clearCache(completion: @escaping (Result<Int64, Error>) -> Void) func clearCache(completion: @escaping (Result<Int64, Error>) -> Void)
} }
@@ -86,9 +86,8 @@ class RemoteImageApiSetup {
requestImageChannel.setMessageHandler { message, reply in requestImageChannel.setMessageHandler { message, reply in
let args = message as! [Any?] let args = message as! [Any?]
let urlArg = args[0] as! String let urlArg = args[0] as! String
let headersArg = args[1] as! [String: String] let requestIdArg = args[1] as! Int64
let requestIdArg = args[2] as! Int64 api.requestImage(url: urlArg, requestId: requestIdArg) { result in
api.requestImage(url: urlArg, headers: headersArg, requestId: requestIdArg) { result in
switch result { switch result {
case .success(let res): case .success(let res):
reply(wrapResult(res)) reply(wrapResult(res))

View File

@@ -33,12 +33,9 @@ class RemoteImageApiImpl: NSObject, RemoteImageApi {
kCGImageSourceCreateThumbnailFromImageAlways: true kCGImageSourceCreateThumbnailFromImageAlways: true
] as CFDictionary ] as CFDictionary
func requestImage(url: String, headers: [String : String], requestId: Int64, completion: @escaping (Result<[String : Int64]?, any Error>) -> Void) { func requestImage(url: String, requestId: Int64, completion: @escaping (Result<[String : Int64]?, any Error>) -> Void) {
var urlRequest = URLRequest(url: URL(string: url)!) var urlRequest = URLRequest(url: URL(string: url)!)
urlRequest.cachePolicy = .returnCacheDataElseLoad urlRequest.cachePolicy = .returnCacheDataElseLoad
for (key, value) in headers {
urlRequest.setValue(value, forHTTPHeaderField: key)
}
let task = URLSessionManager.shared.session.dataTask(with: urlRequest) { data, response, error in let task = URLSessionManager.shared.session.dataTask(with: urlRequest) { data, response, error in
Self.handleCompletion(requestId: requestId, data: data, response: response, error: error) Self.handleCompletion(requestId: requestId, data: data, response: response, error: error)

View File

@@ -2,9 +2,8 @@ part of 'image_request.dart';
class RemoteImageRequest extends ImageRequest { class RemoteImageRequest extends ImageRequest {
final String uri; final String uri;
final Map<String, String> headers;
RemoteImageRequest({required this.uri, required this.headers}); RemoteImageRequest({required this.uri});
@override @override
Future<ImageInfo?> load(ImageDecoderCallback decode, {double scale = 1.0}) async { Future<ImageInfo?> load(ImageDecoderCallback decode, {double scale = 1.0}) async {
@@ -12,7 +11,7 @@ class RemoteImageRequest extends ImageRequest {
return null; return null;
} }
final info = await remoteImageApi.requestImage(uri, headers: headers, requestId: requestId); final info = await remoteImageApi.requestImage(uri, requestId: requestId);
final frame = switch (info) { final frame = switch (info) {
{'pointer': int pointer, 'length': int length} => await _fromEncodedPlatformImage(pointer, length), {'pointer': int pointer, 'length': int length} => await _fromEncodedPlatformImage(pointer, length),
{'pointer': int pointer, 'width': int width, 'height': int height, 'rowBytes': int rowBytes} => {'pointer': int pointer, 'width': int width, 'height': int height, 'rowBytes': int rowBytes} =>

View File

@@ -60,7 +60,7 @@ class NetworkRepository {
final result = await networkApi.createWebSocketTask(uri.toString(), protocols?.toList()); final result = await networkApi.createWebSocketTask(uri.toString(), protocols?.toList());
final pointer = Pointer.fromAddress(result.taskPointer); final pointer = Pointer.fromAddress(result.taskPointer);
final task = URLSessionWebSocketTask.fromRawPointer(pointer.cast()); final task = URLSessionWebSocketTask.fromRawPointer(pointer.cast());
return CupertinoWebSocket.fromConnectedTask(task, protocol: result.protocol ?? ''); return CupertinoWebSocket.fromConnectedTask(task, protocol: result.taskProtocol ?? '');
} }
static Future<WebSocket> _createAndroidWebSocket(Uri uri, {Iterable<String>? protocols}) { static Future<WebSocket> _createAndroidWebSocket(Uri uri, {Iterable<String>? protocols}) {

View File

@@ -36,10 +36,6 @@ class SyncApiRepository {
final headers = {'Content-Type': 'application/json', 'Accept': 'application/jsonlines+json'}; final headers = {'Content-Type': 'application/json', 'Accept': 'application/jsonlines+json'};
final headerParams = <String, String>{};
await _api.applyToParams([], headerParams);
headers.addAll(headerParams);
final shouldReset = Store.get(StoreKey.shouldResetSync, false); final shouldReset = Store.get(StoreKey.shouldResetSync, false);
final request = http.Request('POST', Uri.parse(endpoint)); final request = http.Request('POST', Uri.parse(endpoint));
request.headers.addAll(headers); request.headers.addAll(headers);

View File

@@ -8,6 +8,8 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/generated/intl_keys.g.dart'; import 'package:immich_mobile/generated/intl_keys.g.dart';
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
import 'package:immich_mobile/services/api.service.dart';
class SettingsHeader { class SettingsHeader {
String key = ""; String key = "";
@@ -20,7 +22,6 @@ class HeaderSettingsPage extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
// final apiService = ref.watch(apiServiceProvider);
final headers = useState<List<SettingsHeader>>([]); final headers = useState<List<SettingsHeader>>([]);
final setInitialHeaders = useState(false); final setInitialHeaders = useState(false);
@@ -87,7 +88,7 @@ class HeaderSettingsPage extends HookConsumerWidget {
); );
} }
saveHeaders(List<SettingsHeader> headers) { saveHeaders(List<SettingsHeader> headers) async {
final headersMap = {}; final headersMap = {};
for (var header in headers) { for (var header in headers) {
final key = header.key.trim(); final key = header.key.trim();
@@ -98,7 +99,8 @@ class HeaderSettingsPage extends HookConsumerWidget {
} }
var encoded = jsonEncode(headersMap); var encoded = jsonEncode(headersMap);
Store.put(StoreKey.customHeaders, encoded); await Store.put(StoreKey.customHeaders, encoded);
await networkApi.setRequestHeaders(ApiService.getRequestHeaders());
} }
} }

View File

@@ -329,4 +329,27 @@ class NetworkApi {
return (pigeonVar_replyList[0] as WebSocketTaskResult?)!; return (pigeonVar_replyList[0] as WebSocketTaskResult?)!;
} }
} }
Future<void> setRequestHeaders(Map<String, String> headers) async {
final String pigeonVar_channelName =
'dev.flutter.pigeon.immich_mobile.NetworkApi.setRequestHeaders$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[headers]);
final List<Object?>? pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else {
return;
}
}
} }

View File

@@ -49,11 +49,7 @@ class RemoteImageApi {
final String pigeonVar_messageChannelSuffix; final String pigeonVar_messageChannelSuffix;
Future<Map<String, int>?> requestImage( Future<Map<String, int>?> requestImage(String url, {required int requestId}) async {
String url, {
required Map<String, String> headers,
required int requestId,
}) async {
final String pigeonVar_channelName = final String pigeonVar_channelName =
'dev.flutter.pigeon.immich_mobile.RemoteImageApi.requestImage$pigeonVar_messageChannelSuffix'; 'dev.flutter.pigeon.immich_mobile.RemoteImageApi.requestImage$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>( final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
@@ -61,7 +57,7 @@ class RemoteImageApi {
pigeonChannelCodec, pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger, binaryMessenger: pigeonVar_binaryMessenger,
); );
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[url, headers, requestId]); final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[url, requestId]);
final List<Object?>? pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?; final List<Object?>? pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) { if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName); throw _createConnectionError(pigeonVar_channelName);

View File

@@ -6,7 +6,6 @@ import 'package:immich_mobile/domain/services/setting.service.dart';
import 'package:immich_mobile/infrastructure/loaders/image_request.dart'; import 'package:immich_mobile/infrastructure/loaders/image_request.dart';
import 'package:immich_mobile/presentation/widgets/images/image_provider.dart'; import 'package:immich_mobile/presentation/widgets/images/image_provider.dart';
import 'package:immich_mobile/presentation/widgets/images/one_frame_multi_image_stream_completer.dart'; import 'package:immich_mobile/presentation/widgets/images/one_frame_multi_image_stream_completer.dart';
import 'package:immich_mobile/services/api.service.dart';
import 'package:immich_mobile/utils/image_url_builder.dart'; import 'package:immich_mobile/utils/image_url_builder.dart';
import 'package:openapi/api.dart'; import 'package:openapi/api.dart';
@@ -37,7 +36,7 @@ class RemoteImageProvider extends CancellableImageProvider<RemoteImageProvider>
} }
Stream<ImageInfo> _codec(RemoteImageProvider key, ImageDecoderCallback decode) { Stream<ImageInfo> _codec(RemoteImageProvider key, ImageDecoderCallback decode) {
final request = this.request = RemoteImageRequest(uri: key.url, headers: ApiService.getRequestHeaders()); final request = this.request = RemoteImageRequest(uri: key.url);
return loadRequest(request, decode); return loadRequest(request, decode);
} }
@@ -88,10 +87,8 @@ class RemoteFullImageProvider extends CancellableImageProvider<RemoteFullImagePr
return; return;
} }
final headers = ApiService.getRequestHeaders();
final previewRequest = request = RemoteImageRequest( final previewRequest = request = RemoteImageRequest(
uri: getThumbnailUrlForRemoteId(key.assetId, type: AssetMediaSize.preview, thumbhash: key.thumbhash), uri: getThumbnailUrlForRemoteId(key.assetId, type: AssetMediaSize.preview, thumbhash: key.thumbhash),
headers: headers,
); );
yield* loadRequest(previewRequest, decode); yield* loadRequest(previewRequest, decode);
@@ -104,7 +101,7 @@ class RemoteFullImageProvider extends CancellableImageProvider<RemoteFullImagePr
return; return;
} }
final originalRequest = request = RemoteImageRequest(uri: getOriginalUrlForRemoteId(key.assetId), headers: headers); final originalRequest = request = RemoteImageRequest(uri: getOriginalUrlForRemoteId(key.assetId));
yield* loadRequest(originalRequest, decode); yield* loadRequest(originalRequest, decode);
} }

View File

@@ -8,6 +8,7 @@ import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/models/auth/auth_state.model.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/models/auth/login_response.model.dart';
import 'package:immich_mobile/providers/api.provider.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/providers/infrastructure/user.provider.dart';
import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/services/api.service.dart';
import 'package:immich_mobile/services/auth.service.dart'; import 'package:immich_mobile/services/auth.service.dart';
@@ -124,6 +125,7 @@ class AuthNotifier extends StateNotifier<AuthState> {
Future<bool> saveAuthInfo({required String accessToken}) async { Future<bool> saveAuthInfo({required String accessToken}) async {
await _apiService.setAccessToken(accessToken); await _apiService.setAccessToken(accessToken);
await networkApi.setRequestHeaders(ApiService.getRequestHeaders());
final serverEndpoint = Store.get(StoreKey.serverEndpoint); final serverEndpoint = Store.get(StoreKey.serverEndpoint);
final customHeaders = Store.tryGet(StoreKey.customHeaders); final customHeaders = Store.tryGet(StoreKey.customHeaders);

View File

@@ -1,5 +1,4 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
@@ -13,7 +12,6 @@ import 'package:immich_mobile/providers/auth.provider.dart';
import 'package:immich_mobile/providers/background_sync.provider.dart'; import 'package:immich_mobile/providers/background_sync.provider.dart';
import 'package:immich_mobile/providers/db.provider.dart'; import 'package:immich_mobile/providers/db.provider.dart';
import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart';
import 'package:immich_mobile/services/api.service.dart';
import 'package:immich_mobile/services/sync.service.dart'; import 'package:immich_mobile/services/sync.service.dart';
import 'package:immich_mobile/utils/debounce.dart'; import 'package:immich_mobile/utils/debounce.dart';
import 'package:immich_mobile/utils/debug_print.dart'; import 'package:immich_mobile/utils/debug_print.dart';
@@ -100,11 +98,6 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
if (authenticationState.isAuthenticated) { if (authenticationState.isAuthenticated) {
try { try {
final endpoint = Uri.parse(Store.get(StoreKey.serverEndpoint)); final endpoint = Uri.parse(Store.get(StoreKey.serverEndpoint));
final headers = ApiService.getRequestHeaders();
if (endpoint.userInfo.isNotEmpty) {
headers["Authorization"] = "Basic ${base64.encode(utf8.encode(endpoint.userInfo))}";
}
dPrint(() => "Attempting to connect to websocket"); dPrint(() => "Attempting to connect to websocket");
// Configure socket transports must be specified // Configure socket transports must be specified
Socket socket = io( Socket socket = io(
@@ -117,7 +110,6 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
.enableForceNew() .enableForceNew()
.enableForceNewConnection() .enableForceNewConnection()
.enableAutoConnect() .enableAutoConnect()
.setExtraHeaders(headers)
.build(), .build(),
); );

View File

@@ -129,11 +129,8 @@ class ApiService implements Authentication {
Future<String> _getWellKnownEndpoint(String baseUrl) async { Future<String> _getWellKnownEndpoint(String baseUrl) async {
try { try {
var headers = {"Accept": "application/json"};
headers.addAll(getRequestHeaders());
final res = await NetworkRepository.client final res = await NetworkRepository.client
.get(Uri.parse("$baseUrl/.well-known/immich"), headers: headers) .get(Uri.parse("$baseUrl/.well-known/immich"))
.timeout(const Duration(seconds: 5)); .timeout(const Duration(seconds: 5));
if (res.statusCode == 200) { if (res.statusCode == 200) {
@@ -197,10 +194,7 @@ class ApiService implements Authentication {
@override @override
Future<void> applyToParams(List<QueryParam> queryParams, Map<String, String> headerParams) { Future<void> applyToParams(List<QueryParam> queryParams, Map<String, String> headerParams) {
return Future<void>(() { return Future.value();
var headers = ApiService.getRequestHeaders();
headerParams.addAll(headers);
});
} }
ApiClient get apiClient => _apiClient; ApiClient get apiClient => _apiClient;

View File

@@ -68,7 +68,7 @@ class AuthService {
try { try {
final uri = Uri.parse('$url/users/me'); final uri = Uri.parse('$url/users/me');
final response = await NetworkRepository.client.get(uri, headers: ApiService.getRequestHeaders()); final response = await NetworkRepository.client.get(uri);
if (response.statusCode == 200) { if (response.statusCode == 200) {
isValid = true; isValid = true;
} }

View File

@@ -52,4 +52,6 @@ abstract class NetworkApi {
/// iOS only - Android should use OkHttpWebSocket.connectWithClient directly. /// iOS only - Android should use OkHttpWebSocket.connectWithClient directly.
@async @async
WebSocketTaskResult createWebSocketTask(String url, List<String>? protocols); WebSocketTaskResult createWebSocketTask(String url, List<String>? protocols);
void setRequestHeaders(Map<String, String> headers);
} }

View File

@@ -5,8 +5,7 @@ import 'package:pigeon/pigeon.dart';
dartOut: 'lib/platform/remote_image_api.g.dart', dartOut: 'lib/platform/remote_image_api.g.dart',
swiftOut: 'ios/Runner/Images/RemoteImages.g.swift', swiftOut: 'ios/Runner/Images/RemoteImages.g.swift',
swiftOptions: SwiftOptions(includeErrorClass: false), swiftOptions: SwiftOptions(includeErrorClass: false),
kotlinOut: kotlinOut: 'android/app/src/main/kotlin/app/alextran/immich/images/RemoteImages.g.kt',
'android/app/src/main/kotlin/app/alextran/immich/images/RemoteImages.g.kt',
kotlinOptions: KotlinOptions(package: 'app.alextran.immich.images', includeErrorClass: false), kotlinOptions: KotlinOptions(package: 'app.alextran.immich.images', includeErrorClass: false),
dartOptions: DartOptions(), dartOptions: DartOptions(),
dartPackageName: 'immich_mobile', dartPackageName: 'immich_mobile',
@@ -15,11 +14,7 @@ import 'package:pigeon/pigeon.dart';
@HostApi() @HostApi()
abstract class RemoteImageApi { abstract class RemoteImageApi {
@async @async
Map<String, int>? requestImage( Map<String, int>? requestImage(String url, {required int requestId});
String url, {
required Map<String, String> headers,
required int requestId,
});
void cancelRequest(int requestId); void cancelRequest(int requestId);

View File

@@ -333,8 +333,8 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
path: "pkgs/cupertino_http" path: "pkgs/cupertino_http"
ref: "114b2807bdeee641457b5703f411318d722b67b5" ref: "398fcc427e1a15a08ea9ddb467bb8368e1acc09d"
resolved-ref: "114b2807bdeee641457b5703f411318d722b67b5" resolved-ref: "398fcc427e1a15a08ea9ddb467bb8368e1acc09d"
url: "https://github.com/mertalev/http" url: "https://github.com/mertalev/http"
source: git source: git
version: "3.0.0-wip" version: "3.0.0-wip"
@@ -1267,8 +1267,8 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
path: "pkgs/ok_http" path: "pkgs/ok_http"
ref: "114b2807bdeee641457b5703f411318d722b67b5" ref: "398fcc427e1a15a08ea9ddb467bb8368e1acc09d"
resolved-ref: "114b2807bdeee641457b5703f411318d722b67b5" resolved-ref: "398fcc427e1a15a08ea9ddb467bb8368e1acc09d"
url: "https://github.com/mertalev/http" url: "https://github.com/mertalev/http"
source: git source: git
version: "0.1.1-wip" version: "0.1.1-wip"
@@ -1727,19 +1727,20 @@ packages:
socket_io_client: socket_io_client:
dependency: "direct main" dependency: "direct main"
description: description:
name: socket_io_client path: "."
sha256: ede469f3e4c55e8528b4e023bdedbc20832e8811ab9b61679d1ba3ed5f01f23b ref: "95a61e3d92b0ad0d70ba488b9cb4598a3d655c36"
url: "https://pub.dev" resolved-ref: "95a61e3d92b0ad0d70ba488b9cb4598a3d655c36"
source: hosted url: "https://github.com/mertalev/socket.io-client-dart"
version: "2.0.3+1" source: git
version: "3.1.4"
socket_io_common: socket_io_common:
dependency: transitive dependency: transitive
description: description:
name: socket_io_common name: socket_io_common
sha256: "2ab92f8ff3ebbd4b353bf4a98bee45cc157e3255464b2f90f66e09c4472047eb" sha256: "162fbaecbf4bf9a9372a62a341b3550b51dcef2f02f3e5830a297fd48203d45b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.3" version: "3.1.1"
source_gen: source_gen:
dependency: transitive dependency: transitive
description: description:
@@ -2101,21 +2102,21 @@ packages:
source: hosted source: hosted
version: "1.1.1" version: "1.1.1"
web_socket: web_socket:
dependency: transitive dependency: "direct main"
description: description:
name: web_socket name: web_socket
sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.1.6" version: "1.0.1"
web_socket_channel: web_socket_channel:
dependency: transitive dependency: transitive
description: description:
name: web_socket_channel name: web_socket_channel
sha256: "0b8e2457400d8a859b7b2030786835a28a8e80836ef64402abef392ff4f1d0e5" sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.2" version: "3.0.3"
webdriver: webdriver:
dependency: transitive dependency: transitive
description: description:

View File

@@ -75,7 +75,6 @@ dependencies:
share_handler: ^0.0.25 share_handler: ^0.0.25
share_plus: ^10.1.4 share_plus: ^10.1.4
sliver_tools: ^0.2.12 sliver_tools: ^0.2.12
socket_io_client: ^2.0.3+1
stream_transform: ^2.1.1 stream_transform: ^2.1.1
thumbhash: 0.1.0+1 thumbhash: 0.1.0+1
timezone: ^0.9.4 timezone: ^0.9.4
@@ -83,16 +82,21 @@ dependencies:
uuid: ^4.5.1 uuid: ^4.5.1
wakelock_plus: ^1.3.0 wakelock_plus: ^1.3.0
worker_manager: ^7.2.7 worker_manager: ^7.2.7
web_socket: ^1.0.1
# TODO: upstream these changes # TODO: upstream these changes
socket_io_client:
git:
url: https://github.com/mertalev/socket.io-client-dart
ref: '95a61e3d92b0ad0d70ba488b9cb4598a3d655c36'
cupertino_http: cupertino_http:
git: git:
url: https://github.com/mertalev/http url: https://github.com/mertalev/http
ref: '114b2807bdeee641457b5703f411318d722b67b5' ref: '398fcc427e1a15a08ea9ddb467bb8368e1acc09d'
path: pkgs/cupertino_http/ path: pkgs/cupertino_http/
ok_http: ok_http:
git: git:
url: https://github.com/mertalev/http url: https://github.com/mertalev/http
ref: '114b2807bdeee641457b5703f411318d722b67b5' ref: '398fcc427e1a15a08ea9ddb467bb8368e1acc09d'
path: pkgs/ok_http/ path: pkgs/ok_http/
dev_dependencies: dev_dependencies: