jellyflood/JellyfinPlayer/ConnectToServerView.swift

341 lines
15 KiB
Swift

/* JellyfinPlayer/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 SwiftUI
import CoreData
import KeychainSwift
import NukeUI
import JellyfinAPI
struct ConnectToServerView: View {
@Environment(\.managedObjectContext) private var viewContext
@EnvironmentObject var globalData: GlobalData
@EnvironmentObject var jsi: justSignedIn
@State private var uri = "";
@State private var isWorking = false;
@State private var isErrored = false;
@State private var isDone = false;
@State private var isSignInErrored = false;
@State private var isConnected = false;
@State private var serverName = "";
@State private var usernameDisabled: Bool = false;
@State private var publicUsers: [UserDto] = [];
@State private var lastPublicUsers: [UserDto] = [];
@State private var username = "";
@State private var password = "";
@State private var server_id = "";
@State private var serverSkipped: Bool = false;
@State private var serverSkippedAlert: Bool = false;
@State private var skip_server_bool: Bool = false;
@State private var skip_server_obj: Server!;
@Binding var rootIsActive: Bool
private var reauthDeviceID: String = "";
private let userUUID = UUID();
init(skip_server: Bool, skip_server_prefill: Server, reauth_deviceId: String, isActive: Binding<Bool>) {
_rootIsActive = isActive
skip_server_bool = skip_server
skip_server_obj = skip_server_prefill
reauthDeviceID = reauth_deviceId
}
init(isActive: Binding<Bool>) {
_rootIsActive = isActive
}
func start() {
if(skip_server_bool) {
uri = skip_server_obj.baseURI!
UserAPI.getPublicUsers()
.sink(receiveCompletion: { completion in
switch completion {
case .finished:
break
case .failure(_):
skip_server_bool = false;
skip_server_obj = Server();
break
}
}, receiveValue: { response in
publicUsers = response
serverSkipped = true;
serverSkippedAlert = true;
server_id = skip_server_obj.server_id!
serverName = skip_server_obj.name!
isConnected = true;
})
.store(in: &globalData.pendingAPIRequests)
}
}
func doLogin() {
isWorking = true
let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String;
var deviceName = UIDevice.current.name;
deviceName = deviceName.folding(options: .diacriticInsensitive, locale: .current)
deviceName = deviceName.removeRegexMatches(pattern: "[^\\w\\s]");
let authHeader = "MediaBrowser Client=\"SwiftFin\", Device=\"\(deviceName)\", DeviceId=\"\(serverSkipped ? reauthDeviceID : userUUID.uuidString)\", Version=\"\(appVersion ?? "0.0.1")\"";
print(authHeader)
JellyfinAPI.customHeaders["X-Emby-Authorization"] = authHeader
let x: AuthenticateUserByName = AuthenticateUserByName(username: username, pw: password, password: nil)
UserAPI.authenticateUserByName(authenticateUserByName: x)
.sink(receiveCompletion: { completion in
isWorking = false
HandleAPIRequestCompletion(globalData: globalData, completion: completion)
}, receiveValue: { response in
isWorking = true
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "Server")
let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
do {
try viewContext.execute(deleteRequest)
} catch _ as NSError {
}
let fetchRequest2: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "SignedInUser")
let deleteRequest2 = NSBatchDeleteRequest(fetchRequest: fetchRequest2)
do {
try viewContext.execute(deleteRequest2)
} catch _ as NSError {
}
let newServer = Server(context: viewContext)
newServer.baseURI = uri
newServer.name = serverName
newServer.server_id = server_id
let newUser = SignedInUser(context: viewContext)
newUser.device_uuid = userUUID.uuidString
newUser.username = username
newUser.user_id = response.user!.id!
let keychain = KeychainSwift()
keychain.set(response.accessToken!, forKey: "AccessToken_\(newUser.user_id!)")
do {
try viewContext.save()
DispatchQueue.main.async { [self] in
globalData.authHeader = authHeader
_rootIsActive.wrappedValue = false
jsi.did = true
print("logged in")
isWorking = false
}
} catch {
print("Couldn't store objects to CoreData")
}
})
.store(in: &globalData.pendingAPIRequests)
}
var body: some View {
Form {
if(!isConnected) {
Section(header: Text("Server Information")) {
TextField("Jellyfin Server URL", text: $uri)
.disableAutocorrection(true)
.autocapitalization(.none)
Button {
isWorking = true;
if(!uri.contains("http")) {
uri = "http://" + uri;
}
if(uri.last == "/") {
uri = String(uri.dropLast())
}
JellyfinAPI.basePath = uri
SystemAPI.getPublicSystemInfo()
.sink(receiveCompletion: { completion in
switch completion {
case .finished:
break
case .failure(_):
isErrored = true
isWorking = false
break
}
}, receiveValue: { response in
let server = response
serverName = server.serverName!
server_id = server.id!
if(server.startupWizardCompleted!) {
isConnected = true;
UserAPI.getPublicUsers()
.sink(receiveCompletion: { completion in
switch completion {
case .finished:
break
case .failure(_):
isErrored = true
isWorking = false
break
}
}, receiveValue: { response in
publicUsers = response
isWorking = false
})
.store(in: &globalData.pendingAPIRequests)
}
})
.store(in: &globalData.pendingAPIRequests)
} label: {
HStack {
Text("Connect")
Spacer()
if(isWorking == true) {
ProgressView()
}
}
}.disabled(isWorking || uri.isEmpty)
}.alert(isPresented: $isErrored) {
Alert(title: Text("Error"), message: Text("Couldn't connect to server"), dismissButton: .default(Text("Try again")))
}
} else {
if(publicUsers.count == 0) {
Section(header: Text("\(serverSkipped ? "Reauthenticate" : "Login") to \(serverName)")) {
TextField("Username", text: $username)
.disableAutocorrection(true)
.autocapitalization(.none)
.disabled(usernameDisabled)
SecureField("Password", text: $password)
.disableAutocorrection(true)
.autocapitalization(.none)
Button {
doLogin()
} label: {
HStack {
Text("Login")
Spacer()
if(isWorking) {
ProgressView()
}
}
}.disabled(isWorking || username.isEmpty)
.alert(isPresented: $isSignInErrored) {
Alert(title: Text("Error"), message: Text("Invalid credentials"), dismissButton: .default(Text("Back")))
}
}
if(serverSkipped) {
Section() {
Button {
serverSkippedAlert = false
server_id = ""
serverName = ""
isConnected = false
serverSkipped = false
} label: {
HStack() {
HStack() {
Image(systemName: "chevron.left")
Text("Change Server")
}
Spacer()
}
}
}
} else {
Section() {
Button {
publicUsers = lastPublicUsers
usernameDisabled = false;
} label: {
HStack() {
HStack() {
Image(systemName: "chevron.left")
Text("Back")
}
Spacer()
}
}
}
}
} else {
Section(header: Text("\(serverSkipped ? "Reauthenticate" : "Login") to \(serverName)")) {
ForEach(publicUsers, id: \.id) { publicUser in
HStack() {
Button() {
if(publicUser.hasPassword!) {
lastPublicUsers = publicUsers
username = publicUser.name!
usernameDisabled = true
publicUsers = []
} else {
publicUsers = []
password = ""
username = publicUser.name!
doLogin()
}
} label: {
HStack() {
Text(publicUser.name!).font(.subheadline).fontWeight(.semibold)
Spacer()
if(publicUser.primaryImageTag != "") {
LazyImage(source: URL(string: "\(uri)/Users/\(publicUser.id!)/Images/Primary?width=200&quality=80&tag=\(publicUser.primaryImageTag!)"))
.contentMode(.aspectFill)
.frame(width: 60, height: 60)
.cornerRadius(30.0)
.shadow(radius: 6)
} else {
Image(systemName: "person.fill")
.foregroundColor(Color(red: 1, green: 1, blue: 1).opacity(0.8))
.font(.system(size: 35))
.frame(width: 60, height: 60)
.background(Color(red: 98/255, green: 121/255, blue: 205/255))
.cornerRadius(30.0)
.shadow(radius: 6)
}
}
}
}
}
}
Section() {
Button() {
lastPublicUsers = publicUsers
publicUsers = []
username = ""
} label: {
HStack() {
Text("Other User").font(.subheadline).fontWeight(.semibold)
Spacer()
Image(systemName: "person.fill.questionmark")
.foregroundColor(Color(red: 1, green: 1, blue: 1).opacity(0.8))
.font(.system(size: 35))
.frame(width: 60, height: 60)
.background(Color(red: 98/255, green: 121/255, blue: 205/255))
.cornerRadius(30.0)
.shadow(radius: 6)
}
}
}
}
}
}.navigationTitle("Connect to Server")
.alert(isPresented: $serverSkippedAlert) {
Alert(title: Text("Error"), message: Text("Credentials have expired"), dismissButton: .default(Text("Sign in again")))
}
.onAppear(perform: start)
}
}