feat(mobile): use custom headers when connecting in widget

This commit is contained in:
bwees 2025-08-04 21:37:33 -05:00
parent 3e92e837f1
commit 43fb4f3bcf
No known key found for this signature in database
6 changed files with 65 additions and 8 deletions

View File

@ -3,6 +3,7 @@ package app.alextran.immich.widget
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.util.Log
import app.alextran.immich.widget.model.*
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
@ -24,14 +25,23 @@ class ImmichAPI(cfg: ServerConfig) {
val serverURL = prefs.getString("widget_server_url", "") ?: ""
val sessionKey = prefs.getString("widget_auth_token", "") ?: ""
val customHeadersJSON = prefs.getString("widget_custom_headers", "") ?: ""
if (serverURL.isBlank() || sessionKey.isBlank()) {
return null
}
var customHeaders: Map<String, String> = HashMap<String, String>()
if (customHeadersJSON.isNotBlank()) {
val stringMapType = object : TypeToken<Map<String, String>>() {}.type
customHeaders = Gson().fromJson(customHeadersJSON, stringMapType)
}
return ServerConfig(
serverURL,
sessionKey
sessionKey,
customHeaders
)
}
}
@ -55,6 +65,12 @@ class ImmichAPI(cfg: ServerConfig) {
val connection = (url.openConnection() as HttpURLConnection).apply {
requestMethod = "POST"
setRequestProperty("Content-Type", "application/json")
// Custom Headers
serverConfig.customHeaders.forEach { (key, value) ->
setRequestProperty(key, value)
}
doOutput = true
}
@ -75,6 +91,11 @@ class ImmichAPI(cfg: ServerConfig) {
val url = buildRequestURL("/memories", listOf("for" to iso8601))
val connection = (url.openConnection() as HttpURLConnection).apply {
requestMethod = "GET"
// Custom Headers
serverConfig.customHeaders.forEach { (key, value) ->
setRequestProperty(key, value)
}
}
val response = connection.inputStream.bufferedReader().readText()
@ -94,6 +115,11 @@ class ImmichAPI(cfg: ServerConfig) {
val url = buildRequestURL("/albums")
val connection = (url.openConnection() as HttpURLConnection).apply {
requestMethod = "GET"
// Custom Headers
serverConfig.customHeaders.forEach { (key, value) ->
setRequestProperty(key, value)
}
}
val response = connection.inputStream.bufferedReader().readText()

View File

@ -55,7 +55,11 @@ data class WidgetEntry (
val deeplink: String?
)
data class ServerConfig(val serverEndpoint: String, val sessionKey: String)
data class ServerConfig(
val serverEndpoint: String,
val sessionKey: String,
val customHeaders: Map<String, String>
)
// MARK: Widget State Keys
val kImageUUID = stringPreferencesKey("uuid")

View File

@ -104,10 +104,13 @@ struct Album: Codable, Equatable {
// MARK: API
class ImmichAPI {
typealias CustomHeaders = [String:String]
struct ServerConfig {
let serverEndpoint: String
let sessionKey: String
let customHeaders: CustomHeaders
}
let serverConfig: ServerConfig
init() async throws {
@ -122,10 +125,20 @@ class ImmichAPI {
if serverURL == "" || sessionKey == "" {
throw WidgetError.noLogin
}
// custom headers come in the form of KV pairs in JSON
var customHeadersJSON = (defaults.string(forKey: "widget_custom_headers") ?? "")
var customHeaders: CustomHeaders = [:]
if customHeadersJSON != "",
let parsedHeaders = try? JSONDecoder().decode(CustomHeaders.self, from: customHeadersJSON.data(using: .utf8)!) {
customHeaders = parsedHeaders
}
serverConfig = ServerConfig(
serverEndpoint: serverURL,
sessionKey: sessionKey
sessionKey: sessionKey,
customHeaders: customHeaders
)
}
@ -155,6 +168,12 @@ class ImmichAPI {
return components?.url
}
func applyHeaders(for request: inout URLRequest) {
for (header, value) in serverConfig.customHeaders {
request.addValue(value, forHTTPHeaderField: header)
}
}
func fetchSearchResults(with filters: SearchFilter = Album.NONE.filter)
async throws
@ -174,7 +193,8 @@ class ImmichAPI {
request.httpMethod = "POST"
request.httpBody = try JSONEncoder().encode(filters)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
applyHeaders(for: &request)
let (data, _) = try await URLSession.shared.data(for: request)
// decode data
@ -196,6 +216,7 @@ class ImmichAPI {
var request = URLRequest(url: searchURL)
request.httpMethod = "GET"
applyHeaders(for: &request)
let (data, _) = try await URLSession.shared.data(for: request)
@ -254,7 +275,8 @@ class ImmichAPI {
var request = URLRequest(url: searchURL)
request.httpMethod = "GET"
applyHeaders(for: &request)
let (data, _) = try await URLSession.shared.data(for: request)
// decode data

View File

@ -30,9 +30,10 @@ const int kTimelineAssetLoadBatchSize = 256;
const int kTimelineAssetLoadOppositeSize = 64;
// Widget keys
const String appShareGroupId = "group.app.immich.share";
const String kWidgetAuthToken = "widget_auth_token";
const String kWidgetServerEndpoint = "widget_server_url";
const String appShareGroupId = "group.app.immich.share";
const String kWidgetCustomHeaders = "widget_custom_headers";
// add widget identifiers here for new widgets
// these are used to force a widget refresh

View File

@ -121,7 +121,9 @@ class AuthNotifier extends StateNotifier<AuthState> {
Future<bool> saveAuthInfo({required String accessToken}) async {
await _apiService.setAccessToken(accessToken);
await _widgetService.writeCredentials(Store.get(StoreKey.serverEndpoint), accessToken);
final serverEndpoint = Store.get(StoreKey.serverEndpoint);
final customHeaders = Store.get(StoreKey.customHeaders);
await _widgetService.writeCredentials(serverEndpoint, accessToken, customHeaders);
// Get the deviceid from the store if it exists, otherwise generate a new one
String deviceId = Store.tryGet(StoreKey.deviceId) ?? await FlutterUdid.consistentUdid;

View File

@ -11,10 +11,11 @@ class WidgetService {
const WidgetService(this._repository);
Future<void> writeCredentials(String serverURL, String sessionKey) async {
Future<void> writeCredentials(String serverURL, String sessionKey, String customHeaders) async {
await _repository.setAppGroupId(appShareGroupId);
await _repository.saveData(kWidgetServerEndpoint, serverURL);
await _repository.saveData(kWidgetAuthToken, sessionKey);
await _repository.saveData(kWidgetCustomHeaders, customHeaders);
// wait 3 seconds to ensure the widget is updated, dont block
Future.delayed(const Duration(seconds: 3), refreshWidgets);
@ -24,6 +25,7 @@ class WidgetService {
await _repository.setAppGroupId(appShareGroupId);
await _repository.saveData(kWidgetServerEndpoint, "");
await _repository.saveData(kWidgetAuthToken, "");
await _repository.saveData(kWidgetCustomHeaders, "");
// wait 3 seconds to ensure the widget is updated, dont block
Future.delayed(const Duration(seconds: 3), refreshWidgets);