jellyflood/Shared/Singleton/SessionManager.swift

330 lines
14 KiB
Swift

//
/*
* SwiftFin is subject to the terms of the Mozilla Public
* License, v2.0. If a copy of the MPL was not distributed with this
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
*
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
*/
import Combine
import CoreData
import CoreStore
import Defaults
import Foundation
import JellyfinAPI
import KeychainSwift
import UIKit
#if os(tvOS)
import TVServices
import SwiftUIFocusGuide
#endif
typealias CurrentLogin = (server: SwiftfinStore.Models.Server, user: SwiftfinStore.Models.User)
// MARK: NewSessionManager
final class SessionManager {
// MARK: currentLogin
private(set) var currentLogin: CurrentLogin!
// MARK: main
static let main = SessionManager()
private init() {
if let lastUserID = SwiftfinStore.Defaults.suite[.lastServerUserID],
let user = try? SwiftfinStore.dataStack.fetchOne(From<SwiftfinStore.Models.User>(),
[Where<SwiftfinStore.Models.User>("id == %@", lastUserID)]) {
// TODO: Fetch for right queue
// Strongly assuming that we didn't delete the server associate with the user
guard let server = user.server, let accessToken = user.accessToken else { return }
guard let existingServer = SwiftfinStore.dataStack.fetchExisting(server) else { return }
setAuthHeader(with: accessToken.value)
currentLogin = (server: existingServer, user: user)
}
}
private func generateServerUserID(server: SwiftfinStore.Models.Server, user: SwiftfinStore.Models.User) -> String {
return "\(server.id)-\(user.id)"
}
func fetchServers() -> [SwiftfinStore.Models.Server] {
let servers = try! SwiftfinStore.dataStack.fetchAll(From<SwiftfinStore.Models.Server>())
return servers
}
// Connects to a server at the given uri, storing if successful
func connectToServer(with uri: String) -> AnyPublisher<SwiftfinStore.Models.Server, Error> {
var uri = uri
if !uri.contains("http") {
uri = "https://" + uri
}
if uri.last == "/" {
uri = String(uri.dropLast())
}
JellyfinAPI.basePath = uri
return SystemAPI.getPublicSystemInfo()
.map({ response -> (SwiftfinStore.Models.Server, UnsafeDataTransaction) in
let transaction = SwiftfinStore.dataStack.beginUnsafe()
let newServer = transaction.create(Into<SwiftfinStore.Models.Server>())
newServer.uri = response.localAddress ?? "SfUri"
newServer.name = response.serverName ?? "SfServerName"
newServer.id = response.id ?? ""
newServer.os = response.operatingSystem ?? "SfOS"
newServer.version = response.version ?? "SfVersion"
newServer.users = []
return (newServer, transaction)
})
.handleEvents(receiveOutput: { (_, transaction) in
try? transaction.commitAndWait()
})
.map({ (server, _) in
return server
})
.eraseToAnyPublisher()
}
// Logs in a user with an associated server, storing if successful
func loginUser(server: SwiftfinStore.Models.Server, username: String, password: String) -> AnyPublisher<SwiftfinStore.Models.User, Error> {
setAuthHeader(with: "")
return UserAPI.authenticateUserByName(authenticateUserByName: AuthenticateUserByName(username: username, pw: password))
.map({ response -> (SwiftfinStore.Models.User, UnsafeDataTransaction) in
guard let accessToken = response.accessToken else { fatalError("Received successful user with no access token") }
let transaction = SwiftfinStore.dataStack.beginUnsafe()
let newUser = transaction.create(Into<SwiftfinStore.Models.User>())
newUser.username = response.user?.name ?? "SfUsername"
newUser.id = response.user?.id ?? "SfID"
newUser.appleTVID = ""
let newAccessToken = transaction.create(Into<SwiftfinStore.Models.AccessToken>())
newAccessToken.value = accessToken
newUser.accessToken = newAccessToken
let userServer = transaction.edit(server)
userServer?.users.insert(newUser)
return (newUser, transaction)
})
.handleEvents(receiveOutput: { [unowned self] (user, transaction) in
setAuthHeader(with: user.accessToken?.value ?? "")
try? transaction.commitAndWait()
// Fetch for the right queue
let currentServer = SwiftfinStore.dataStack.fetchExisting(server)!
let currentUser = SwiftfinStore.dataStack.fetchExisting(user)!
SwiftfinStore.Defaults.suite[.lastServerUserID] = user.id
currentLogin = (server: currentServer, user: currentUser)
})
.map({ (user, _) in
return user
})
.eraseToAnyPublisher()
}
func logout() {
// TODO: todo
}
private func setAuthHeader(with accessToken: String) {
let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
var deviceName = UIDevice.current.name
deviceName = deviceName.folding(options: .diacriticInsensitive, locale: .current)
deviceName = String(deviceName.unicodeScalars.filter {CharacterSet.urlQueryAllowed.contains($0) })
let platform: String
#if os(tvOS)
platform = "tvOS"
#else
platform = "iOS"
#endif
var header = "MediaBrowser "
header.append("Client=\"Jellyfin \(platform)\", ")
header.append("Device=\"\(deviceName)\", ")
header.append("DeviceId=\"\(platform)_\(UIDevice.vendorUUIDString)_\(String(Date().timeIntervalSince1970))\", ")
header.append("Version=\"\(appVersion ?? "0.0.1")\", ")
header.append("Token=\"\(accessToken)\"")
JellyfinAPI.customHeaders["X-Emby-Authorization"] = header
}
}
//final class SessionManager {
// static let current = SessionManager()
// fileprivate(set) var user: SignedInUser!
// fileprivate(set) var deviceID: String = ""
// fileprivate(set) var accessToken: String = ""
//
// #if os(tvOS)
// let tvUserManager = TVUserManager()
// #endif
// let userDefaults = UserDefaults()
//
// init() {
// let savedUserRequest: NSFetchRequest<SignedInUser> = SignedInUser.fetchRequest()
// let lastUsedUserID = userDefaults.string(forKey: "lastUsedUserID")
// let savedUsers = try? PersistenceController.shared.container.viewContext.fetch(savedUserRequest)
//
// #if os(tvOS)
// savedUsers?.forEach { savedUser in
// if savedUser.appletv_id == tvUserManager.currentUserIdentifier ?? "" {
// self.user = savedUser
// }
// }
// #else
// if lastUsedUserID != nil {
// savedUsers?.forEach { savedUser in
// if savedUser.user_id ?? "" == lastUsedUserID! {
// user = savedUser
// }
// }
// } else {
// user = savedUsers?.first
// }
// #endif
//
// if user != nil {
// let authToken = getAuthToken(userID: user.user_id!)
// generateAuthHeader(with: authToken, deviceID: user.device_uuid)
// }
// }
//
// fileprivate func generateAuthHeader(with authToken: String?, deviceID devID: String?) {
// let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
// var deviceName = UIDevice.current.name
// deviceName = deviceName.folding(options: .diacriticInsensitive, locale: .current)
// deviceName = String(deviceName.unicodeScalars.filter {CharacterSet.urlQueryAllowed.contains($0) })
//
// var header = "MediaBrowser "
// #if os(tvOS)
// header.append("Client=\"Jellyfin tvOS\", ")
// #else
// header.append("Client=\"SwiftFin iOS\", ")
// #endif
//
// header.append("Device=\"\(deviceName)\", ")
//
// if devID == nil {
// LogManager.shared.log.info("Generating device ID...")
// #if os(tvOS)
// header.append("DeviceId=\"tvOS_\(UIDevice.current.identifierForVendor!.uuidString)_\(String(Date().timeIntervalSince1970))\", ")
// deviceID = "tvOS_\(UIDevice.current.identifierForVendor!.uuidString)_\(String(Date().timeIntervalSince1970))"
// #else
// header.append("DeviceId=\"iOS_\(UIDevice.current.identifierForVendor!.uuidString)_\(String(Date().timeIntervalSince1970))\", ")
// deviceID = "iOS_\(UIDevice.current.identifierForVendor!.uuidString)_\(String(Date().timeIntervalSince1970))"
// #endif
// } else {
// LogManager.shared.log.info("Using stored device ID...")
// header.append("DeviceId=\"\(devID!)\", ")
// deviceID = devID!
// }
//
// header.append("Version=\"\(appVersion ?? "0.0.1")\", ")
//
// if authToken != nil {
// header.append("Token=\"\(authToken!)\"")
// accessToken = authToken!
// }
//
// JellyfinAPI.customHeaders["X-Emby-Authorization"] = header
// }
//
// fileprivate func getAuthToken(userID: String) -> String? {
// let keychain = KeychainSwift()
// keychain.accessGroup = "9R8RREG67J.me.vigue.jellyfin.sharedKeychain"
// return keychain.get("AccessToken_\(userID)")
// }
//
// func doesUserHaveSavedSession(userID: String) -> Bool {
// let savedUserRequest: NSFetchRequest<SignedInUser> = SignedInUser.fetchRequest()
// savedUserRequest.predicate = NSPredicate(format: "user_id == %@", userID)
// let savedUsers = try? PersistenceController.shared.container.viewContext.fetch(savedUserRequest)
//
// if savedUsers!.isEmpty {
// return false
// }
//
// return true
// }
//
// func getSavedSession(userID: String) -> SignedInUser {
// let savedUserRequest: NSFetchRequest<SignedInUser> = SignedInUser.fetchRequest()
// savedUserRequest.predicate = NSPredicate(format: "user_id == %@", userID)
// let savedUsers = try? PersistenceController.shared.container.viewContext.fetch(savedUserRequest)
// return savedUsers!.first!
// }
//
// func loginWithSavedSession(user: SignedInUser) {
// let accessToken = getAuthToken(userID: user.user_id!)
// userDefaults.set(user.user_id!, forKey: "lastUsedUserID")
// self.user = user
// generateAuthHeader(with: accessToken, deviceID: user.device_uuid)
// print(JellyfinAPI.customHeaders)
// let nc = NotificationCenter.default
// nc.post(name: Notification.Name("didSignIn"), object: nil)
// }
//
// func login(username: String, password: String) -> AnyPublisher<SignedInUser, Error> {
// generateAuthHeader(with: nil, deviceID: nil)
//
// return UserAPI.authenticateUserByName(authenticateUserByName: AuthenticateUserByName(username: username, pw: password))
// .map { response -> (SignedInUser, String?) in
// let user = SignedInUser(context: PersistenceController.shared.container.viewContext)
// user.username = response.user?.name
// user.user_id = response.user?.id
// user.device_uuid = self.deviceID
//
// #if os(tvOS)
// let descriptor: TVAppProfileDescriptor = TVAppProfileDescriptor(name: user.username!)
// self.tvUserManager.shouldStorePreferenceForCurrentUser(to: descriptor) { should in
// if should {
// user.appletv_id = self.tvUserManager.currentUserIdentifier ?? ""
// }
// }
// #endif
//
// return (user, response.accessToken)
// }
// .handleEvents(receiveOutput: { [unowned self] response, accessToken in
// user = response
// _ = try? PersistenceController.shared.container.viewContext.save()
//
// let keychain = KeychainSwift()
// keychain.accessGroup = "9R8RREG67J.me.vigue.jellyfin.sharedKeychain"
// keychain.set(accessToken!, forKey: "AccessToken_\(user.user_id!)")
//
// generateAuthHeader(with: accessToken, deviceID: user.device_uuid)
//
// let nc = NotificationCenter.default
// nc.post(name: Notification.Name("didSignIn"), object: nil)
// })
// .map(\.0)
// .eraseToAnyPublisher()
// }
//
// func logout() {
// let nc = NotificationCenter.default
// nc.post(name: Notification.Name("didSignOut"), object: nil)
// let keychain = KeychainSwift()
// keychain.accessGroup = "9R8RREG67J.me.vigue.jellyfin.sharedKeychain"
// keychain.delete("AccessToken_\(user?.user_id ?? "")")
// generateAuthHeader(with: nil, deviceID: nil)
// if user != nil {
// let deleteRequest = NSBatchDeleteRequest(objectIDs: [user.objectID])
// user = nil
// _ = try? PersistenceController.shared.container.viewContext.execute(deleteRequest)
// }
// }
//}