add ServerEnvironment.setUp, reset func
add SessionManager.updateHeader, login, logout
This commit is contained in:
parent
bc03de481d
commit
40127b0710
|
@ -97,6 +97,8 @@
|
|||
62EC3530267666A5000E9F2D /* SessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EC352E267666A5000E9F2D /* SessionManager.swift */; };
|
||||
62EC353126766848000E9F2D /* ServerEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EC352B26766675000E9F2D /* ServerEnvironment.swift */; };
|
||||
62EC353226766849000E9F2D /* SessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EC352E267666A5000E9F2D /* SessionManager.swift */; };
|
||||
62EC353426766B03000E9F2D /* DeviceRotationViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EC353326766B03000E9F2D /* DeviceRotationViewModifier.swift */; };
|
||||
62EC353526766B03000E9F2D /* DeviceRotationViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EC353326766B03000E9F2D /* DeviceRotationViewModifier.swift */; };
|
||||
AE8C3159265D6F90008AA076 /* bitrates.json in Resources */ = {isa = PBXBuildFile; fileRef = AE8C3158265D6F90008AA076 /* bitrates.json */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
|
@ -219,6 +221,7 @@
|
|||
628B953B2670D1FC0091AF3B /* WidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WidgetExtension.entitlements; sourceTree = "<group>"; };
|
||||
62EC352B26766675000E9F2D /* ServerEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerEnvironment.swift; sourceTree = "<group>"; };
|
||||
62EC352E267666A5000E9F2D /* SessionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionManager.swift; sourceTree = "<group>"; };
|
||||
62EC353326766B03000E9F2D /* DeviceRotationViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceRotationViewModifier.swift; sourceTree = "<group>"; };
|
||||
AE8C3158265D6F90008AA076 /* bitrates.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = bitrates.json; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
|
@ -402,6 +405,7 @@
|
|||
621338922660107500A81A2A /* StringExtensions.swift */,
|
||||
6267B3D526710B8900A7371D /* CollectionExtensions.swift */,
|
||||
6267B3D92671138200A7371D /* ImageExtensions.swift */,
|
||||
62EC353326766B03000E9F2D /* DeviceRotationViewModifier.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
|
@ -609,6 +613,7 @@
|
|||
5321753F2671DEA6005491E6 /* SettingsViewModel.swift in Sources */,
|
||||
535870AA2669D8AE00D05A09 /* BlurHashDecode.swift in Sources */,
|
||||
535870652669D21600D05A09 /* ContentView.swift in Sources */,
|
||||
62EC353526766B03000E9F2D /* DeviceRotationViewModifier.swift in Sources */,
|
||||
535870A62669D8AE00D05A09 /* LazyView.swift in Sources */,
|
||||
53C4404F266C75C70049424C /* HandleAPIRequestCompletion.swift in Sources */,
|
||||
5358706F2669D21700D05A09 /* JellyfinPlayer_tvOS.xcdatamodeld in Sources */,
|
||||
|
@ -653,6 +658,7 @@
|
|||
62EC352C26766675000E9F2D /* ServerEnvironment.swift in Sources */,
|
||||
6267B3DA2671138200A7371D /* ImageExtensions.swift in Sources */,
|
||||
5377CBF7263B596A003A4E83 /* ContentView.swift in Sources */,
|
||||
62EC353426766B03000E9F2D /* DeviceRotationViewModifier.swift in Sources */,
|
||||
5389277C263CC3DB0035E14B /* BlurHashDecode.swift in Sources */,
|
||||
539B2DA5263BA5B8007FF1A4 /* SettingsView.swift in Sources */,
|
||||
5338F74E263B61370014BF09 /* ConnectToServerView.swift in Sources */,
|
||||
|
|
|
@ -12,7 +12,6 @@ import JellyfinAPI
|
|||
|
||||
struct ConnectToServerView: View {
|
||||
@Environment(\.managedObjectContext) private var viewContext
|
||||
@EnvironmentObject var globalData: GlobalData
|
||||
@EnvironmentObject var jsi: justSignedIn
|
||||
|
||||
@State private var uri = ""
|
||||
|
|
|
@ -17,8 +17,6 @@ struct ContentView: View {
|
|||
@EnvironmentObject var orientationInfo: OrientationInfo
|
||||
@EnvironmentObject var jsi: justSignedIn
|
||||
|
||||
@StateObject private var globalData = GlobalData()
|
||||
|
||||
@State private var needsToSelectServer = false
|
||||
@State private var isLoading = false
|
||||
@State private var tabSelection: String = "Home"
|
||||
|
|
|
@ -9,7 +9,6 @@ import Foundation
|
|||
import SwiftUI
|
||||
|
||||
struct LibraryListView: View {
|
||||
@EnvironmentObject var globalData: GlobalData
|
||||
|
||||
@State var library_ids: [String] = ["favorites", "genres"]
|
||||
@State var library_names: [String: String] = ["favorites": "Favorites", "genres": "Genres"]
|
||||
|
|
|
@ -9,7 +9,6 @@ import SwiftUI
|
|||
import JellyfinAPI
|
||||
|
||||
struct NextUpView: View {
|
||||
@EnvironmentObject var globalData: GlobalData
|
||||
|
||||
@State private var items: [BaseItemDto] = []
|
||||
@State private var viewDidLoad: Bool = false
|
||||
|
|
|
@ -9,7 +9,6 @@ import SwiftUI
|
|||
import JellyfinAPI
|
||||
|
||||
struct SeriesItemView: View {
|
||||
@EnvironmentObject private var globalData: GlobalData
|
||||
@EnvironmentObject private var orientationInfo: OrientationInfo
|
||||
|
||||
var item: BaseItemDto
|
||||
|
|
|
@ -11,7 +11,6 @@ import SwiftUI
|
|||
struct SettingsView: View {
|
||||
@Environment(\.managedObjectContext) private var viewContext
|
||||
|
||||
@EnvironmentObject var globalData: GlobalData
|
||||
@EnvironmentObject var jsi: justSignedIn
|
||||
|
||||
@ObservedObject var viewModel: SettingsViewModel
|
||||
|
|
|
@ -39,7 +39,6 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
|
|||
weak var delegate: PlayerViewControllerDelegate?
|
||||
|
||||
var mediaPlayer = VLCMediaPlayer()
|
||||
var globalData = GlobalData()
|
||||
|
||||
@IBOutlet weak var timeText: UILabel!
|
||||
@IBOutlet weak var videoContentView: UIView!
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
//
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
// https://www.hackingwithswift.com/quick-start/swiftui/how-to-detect-device-rotation
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
// Our custom view modifier to track rotation and
|
||||
// call our action
|
||||
struct DeviceRotationViewModifier: ViewModifier {
|
||||
let action: (UIDeviceOrientation) -> Void
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
content
|
||||
.onAppear()
|
||||
.onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
|
||||
action(UIDevice.current.orientation)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A View wrapper to make the modifier easier to use
|
||||
extension View {
|
||||
func onRotate(perform action: @escaping (UIDeviceOrientation) -> Void) -> some View {
|
||||
self.modifier(DeviceRotationViewModifier(action: action))
|
||||
}
|
||||
}
|
|
@ -1,23 +1,59 @@
|
|||
//
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
/*
|
||||
* 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 Foundation
|
||||
import Combine
|
||||
import CoreData
|
||||
import Foundation
|
||||
import JellyfinAPI
|
||||
|
||||
final class ServerEnvironment {
|
||||
|
||||
static let shared = ServerEnvironment()
|
||||
var server: Server?
|
||||
|
||||
fileprivate(set) var server: Server!
|
||||
|
||||
init() {
|
||||
let serverRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Server")
|
||||
let servers = try? PersistenceController.shared.container.viewContext.fetch(serverRequest) as? [Server]
|
||||
server = servers?.first
|
||||
guard let baseURI = server?.baseURI else { return }
|
||||
JellyfinAPI.basePath = baseURI
|
||||
}
|
||||
|
||||
func setUp(with uri: String) -> AnyPublisher<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 in
|
||||
let server = Server(context: PersistenceController.shared.container.viewContext)
|
||||
server.baseURI = uri
|
||||
server.name = response.serverName
|
||||
server.server_id = response.id
|
||||
return server
|
||||
}
|
||||
.handleEvents(receiveOutput: { [unowned self] response in
|
||||
server = response
|
||||
_ = try? PersistenceController.shared.container.viewContext.save()
|
||||
}).eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func reset() throws {
|
||||
JellyfinAPI.basePath = ""
|
||||
server = nil
|
||||
|
||||
let serverRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "Server")
|
||||
let deleteRequest = NSBatchDeleteRequest(fetchRequest: serverRequest)
|
||||
|
||||
try PersistenceController.shared.container.viewContext.execute(deleteRequest)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,35 +1,47 @@
|
|||
//
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
/*
|
||||
* 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 Foundation
|
||||
import Combine
|
||||
import CoreData
|
||||
import Foundation
|
||||
import JellyfinAPI
|
||||
import KeychainSwift
|
||||
import UIKit
|
||||
|
||||
final class SessionManager {
|
||||
|
||||
static let shared = SessionManager()
|
||||
var user: SignedInUser?
|
||||
var authHeader: String?
|
||||
|
||||
fileprivate(set) var user: SignedInUser!
|
||||
fileprivate(set) var authHeader: String!
|
||||
fileprivate(set) var deviceIDString: String
|
||||
|
||||
init() {
|
||||
let savedUserRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "SignedInUser")
|
||||
let savedUsers = try? PersistenceController.shared.container.viewContext.fetch(savedUserRequest) as? [SignedInUser]
|
||||
user = savedUsers?.first
|
||||
|
||||
|
||||
let keychain = KeychainSwift()
|
||||
// need prefix
|
||||
keychain.accessGroup = "9R8RREG67J.me.vigue.jellyfin.sharedKeychain"
|
||||
if let deviceID = keychain.get("DeviceIDString") {
|
||||
self.deviceIDString = deviceID
|
||||
} else {
|
||||
self.deviceIDString = UUID().uuidString
|
||||
keychain.set(deviceIDString, forKey: "DeviceIDString")
|
||||
}
|
||||
|
||||
guard let authToken = keychain.get("AccessToken_\(user?.user_id ?? "")") else {
|
||||
return
|
||||
}
|
||||
|
||||
updateHeader(with: authToken)
|
||||
}
|
||||
|
||||
func updateHeader(with authToken: String?) {
|
||||
let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
|
||||
var deviceName = UIDevice.current.name
|
||||
deviceName = deviceName.folding(options: .diacriticInsensitive, locale: .current)
|
||||
|
@ -38,10 +50,54 @@ final class SessionManager {
|
|||
var header = "MediaBrowser "
|
||||
header.append("Client=\"SwiftFin\", ")
|
||||
header.append("Device=\"\(deviceName)\", ")
|
||||
header.append("DeviceId=\"\(user?.device_uuid ?? "")\", ")
|
||||
header.append("DeviceId=\"\(deviceIDString)\", ")
|
||||
header.append("Version=\"\(appVersion ?? "0.0.1")\", ")
|
||||
header.append("Token=\"\(authToken)\"")
|
||||
if let token = authToken {
|
||||
header.append("Token=\"\(token)\"")
|
||||
}
|
||||
|
||||
self.authHeader = header
|
||||
authHeader = header
|
||||
JellyfinAPI.customHeaders["X-Emby-Authorization"] = authHeader
|
||||
}
|
||||
|
||||
func login(username: String, password: String) -> AnyPublisher<SignedInUser, Error> {
|
||||
updateHeader(with: nil)
|
||||
|
||||
return UserAPI.authenticateUserByName(authenticateUserByName: AuthenticateUserByName(username: username, pw: password))
|
||||
.map { [unowned self] response -> (SignedInUser, String?) in
|
||||
let user = SignedInUser(context: PersistenceController.shared.container.viewContext)
|
||||
user.device_uuid = deviceIDString
|
||||
user.username = response.user?.name
|
||||
user.user_id = response.user?.id
|
||||
return (user, response.accessToken)
|
||||
}
|
||||
.handleEvents(receiveOutput: { [unowned self] response, accessToken in
|
||||
user = response
|
||||
_ = try? PersistenceController.shared.container.viewContext.save()
|
||||
if let userID = user.user_id,
|
||||
let token = accessToken
|
||||
{
|
||||
let keychain = KeychainSwift()
|
||||
keychain.accessGroup = "9R8RREG67J.me.vigue.jellyfin.sharedKeychain"
|
||||
keychain.set(token, forKey: "AccessToken_\(userID)")
|
||||
}
|
||||
updateHeader(with: accessToken)
|
||||
})
|
||||
.map(\.0)
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func logout() throws {
|
||||
let keychain = KeychainSwift()
|
||||
keychain.accessGroup = "9R8RREG67J.me.vigue.jellyfin.sharedKeychain"
|
||||
keychain.delete("AccessToken_\(user.user_id ?? "")")
|
||||
JellyfinAPI.customHeaders["X-Emby-Authorization"] = nil
|
||||
user = nil
|
||||
authHeader = nil
|
||||
|
||||
let userRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "SignedInUser")
|
||||
let deleteRequest = NSBatchDeleteRequest(fetchRequest: userRequest)
|
||||
|
||||
try PersistenceController.shared.container.viewContext.execute(deleteRequest)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,24 +26,3 @@ public enum SortBy: String, Codable, CaseIterable {
|
|||
class justSignedIn: ObservableObject {
|
||||
@Published var did: Bool = false
|
||||
}
|
||||
|
||||
class GlobalData: ObservableObject {
|
||||
@Published var user: SignedInUser!
|
||||
@Published var authToken: String = ""
|
||||
@Published var server: Server!
|
||||
@Published var authHeader: String = ""
|
||||
@Published var isInNetwork: Bool = true
|
||||
@Published var networkError: Bool = false
|
||||
@Published var expiredCredentials: Bool = false
|
||||
var pendingAPIRequests = Set<AnyCancellable>()
|
||||
}
|
||||
|
||||
extension GlobalData: Equatable {
|
||||
|
||||
static func == (lhs: GlobalData, rhs: GlobalData) -> Bool {
|
||||
lhs.user == rhs.user
|
||||
&& lhs.authToken == rhs.authToken
|
||||
&& lhs.server == rhs.server
|
||||
&& lhs.authHeader == rhs.authHeader
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue