iOS/Sources/App/Onboarding/API/Steps/OnboardingAuthStepDuplicate...

141 lines
5.1 KiB
Swift
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import HAKit
import PromiseKit
import Shared
struct OnboardingAuthStepDuplicate: OnboardingAuthPostStep {
init(
api: HomeAssistantAPI,
sender: UIViewController
) {
self.api = api
self.sender = sender
}
var api: HomeAssistantAPI
var sender: UIViewController
static var supportedPoints: Set<OnboardingAuthStepPoint> {
Set([.beforeRegister])
}
var timeout: TimeInterval = 30.0
func perform(point: OnboardingAuthStepPoint) -> Promise<Void> {
guard let connection = api.connection else {
return .init(error: HomeAssistantAPI.APIError.noAPIAvailable)
}
let devices = firstly { () -> Promise<[HAData]> in
connection.send(.init(type: "config/device_registry/list")).promise.compactMap {
if case let .array(value) = $0 {
return value
} else {
throw HomeAssistantAPI.APIError.invalidResponse
}
}
}.compactMapValues { value -> RegisteredDevice? in
try? RegisteredDevice(data: value)
}
let timeout: Promise<[RegisteredDevice]> = after(seconds: timeout).then { () -> Promise<[RegisteredDevice]> in
switch connection.state {
case let .disconnected(reason: .waitingToReconnect(lastError: .some(error), atLatest: _, retryCount: _)):
throw error
default:
throw OnboardingAuthError(kind: .invalidURL, data: nil)
}
}
// racing the request, not the whole flow, importantly.
// otherwise we'd fail out before the user finished typing.
return race(timeout, devices).then { [self] registeredDevices -> Promise<Void> in
guard !registeredDevices.contains(where: { $0.id == Current.settingsStore.integrationDeviceID }) else {
// if the integration is registered already, we will take over that one, so we don't need to look
return .value(())
}
// this can be removed once the mobile_app notify service stops being device name specific
return promptForDeviceName(
deviceName: Current.device.deviceName(),
registeredDevices: registeredDevices,
sender: sender
)
}
}
private struct RegisteredDevice {
var name: String
var id: String
init?(data: HAData) throws {
self.name = try data.decode("name")
self.id = try {
let identifiers: [[String]] = try data.decode("identifiers")
for identifier in identifiers where identifier.count == 2 && identifier.starts(with: ["mobile_app"]) {
return identifier[1]
}
throw HADataError.couldntTransform(key: "identifiers")
}()
}
func matches(name other: String) -> Bool {
name.lowercased() == other.lowercased()
}
}
private func promptForDeviceName(
deviceName: String,
registeredDevices: [RegisteredDevice],
sender: UIViewController
) -> Promise<Void> {
guard registeredDevices.contains(where: { $0.matches(name: deviceName) }) else {
// if the device name is not already taken, we can safely use it and don't need to prompt
return .value(())
}
return Promise<Void> { seal in
let alert = UIAlertController(
title: L10n.Onboarding.DeviceNameCheck.Error.title(deviceName),
message: L10n.Onboarding.DeviceNameCheck.Error.prompt,
preferredStyle: .alert
)
alert.addTextField { textField in
textField.keyboardType = .default
textField.placeholder = deviceName
textField.text = deviceName
textField.enablesReturnKeyAutomatically = true
textField.autocapitalizationType = .words
}
alert.addAction(.init(title: L10n.cancelLabel, style: .cancel, handler: { _ in
seal.reject(PMKError.cancelled)
}))
alert.addAction(.init(
title: L10n.Onboarding.DeviceNameCheck.Error.renameAction,
style: .default,
handler: { [self] _ in
let name = alert.textFields?.first?.text?.trimmingCharacters(in: .whitespaces)
guard let name, name.isEmpty == false,
!registeredDevices.contains(where: { $0.matches(name: name) }) else {
promptForDeviceName(
deviceName: deviceName,
registeredDevices: registeredDevices,
sender: sender
).pipe(to: seal.resolve)
return
}
api.server.info.setSetting(value: name, for: .overrideDeviceName)
seal.fulfill(())
}
))
sender.present(alert, animated: true, completion: nil)
}
}
}