luminate/Sources/LuminateServices/Session/JellyfinClientConfiguration.swift

90 lines
4 KiB
Swift

//
// JellyfinClientConfiguration.swift
//
// Copyright 2026 Brendan Szymanski <hello@bscubed.dev>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
import Foundation
import LuminateCore
/// Everything a ``JellyfinClient`` needs to reach one server as one client identity.
///
/// The identity fields end up in the `Authorization` header that ``AuthenticationMiddleware``
/// builds. They are not cosmetic: the server lists ``deviceName`` in its active-devices UI and
/// keys the session by ``deviceID``.
package struct JellyfinClientConfiguration: Sendable, Hashable {
/// The base URL of the Jellyfin server, or `nil` before a server is chosen.
package var serverURL: URL?
/// The application name reported to the server.
package var clientName: String
/// The human-readable device name shown in the server's active-devices list.
package var deviceName: String
/// The stable per-machine device identifier the server keys its session by.
package var deviceID: String
/// The application version reported to the server.
package var version: String
/// The access token, or `nil` before sign-in.
package var accessToken: String?
/// Creates a configuration, defaulting the client identity to Luminate's.
///
/// - Parameters:
/// - serverURL: The base URL of the Jellyfin server, or `nil` before a server is chosen.
/// - clientName: The application name reported to the server.
/// - deviceName: The device name shown by the server; defaults to the machine's host name.
/// - deviceID: The device identifier; defaults to ``defaultDeviceID()``.
/// - version: The application version reported to the server.
/// - accessToken: An access token to start authenticated with, if one was persisted.
package init(
serverURL: URL?,
clientName: String = AppInfo.name,
deviceName: String = ProcessInfo.processInfo.hostName,
deviceID: String = JellyfinClientConfiguration.defaultDeviceID(),
version: String = AppInfo.version,
accessToken: String? = nil
) {
self.serverURL = serverURL
self.clientName = clientName
self.deviceName = deviceName
self.deviceID = deviceID
self.version = version
self.accessToken = accessToken
}
/// Derives a device identifier that survives application restarts.
///
/// Jellyfin opens a new server-side session for every unfamiliar device identifier, so a value
/// regenerated each launch would litter the server's device list. The machine ID is stable,
/// already present on every systemd and D-Bus system, and needs no persistence layer of
/// Luminate's own.
///
/// - Returns: The contents of `/etc/machine-id`, else `/var/lib/dbus/machine-id`, else a fresh
/// UUID string when neither file is readable or both are empty.
package static func defaultDeviceID() -> String {
let candidates = ["/etc/machine-id", "/var/lib/dbus/machine-id"]
for candidate in candidates {
guard let contents = try? String(contentsOf: URL(filePath: candidate), encoding: .utf8) else {
continue
}
let trimmed = contents.trimmingCharacters(in: .whitespacesAndNewlines)
if !trimmed.isEmpty {
return trimmed
}
}
return UUID().uuidString
}
}