2020-08-07 23:57:18 +02:00
|
|
|
// Copyright © 2020 Metabolist. All rights reserved.
|
|
|
|
|
|
|
|
import Foundation
|
|
|
|
import Combine
|
|
|
|
|
2020-08-09 01:08:47 +02:00
|
|
|
class IdentityService {
|
2020-08-09 10:04:43 +02:00
|
|
|
@Published private(set) var identity: Identity
|
2020-08-07 23:57:18 +02:00
|
|
|
let observationErrors: AnyPublisher<Error, Never>
|
|
|
|
|
2020-08-09 13:27:38 +02:00
|
|
|
private let identityDatabase: IdentityDatabase
|
2020-08-09 07:37:04 +02:00
|
|
|
private let environment: AppEnvironment
|
2020-08-09 13:27:38 +02:00
|
|
|
private let networkClient: MastodonClient
|
2020-08-14 03:24:53 +02:00
|
|
|
private let secretsService: SecretsService
|
2020-08-07 23:57:18 +02:00
|
|
|
private let observationErrorsInput = PassthroughSubject<Error, Never>()
|
|
|
|
|
2020-08-09 13:27:38 +02:00
|
|
|
init(identityID: UUID,
|
|
|
|
identityDatabase: IdentityDatabase,
|
|
|
|
environment: AppEnvironment) throws {
|
|
|
|
self.identityDatabase = identityDatabase
|
2020-08-09 07:37:04 +02:00
|
|
|
self.environment = environment
|
2020-08-07 23:57:18 +02:00
|
|
|
observationErrors = observationErrorsInput.eraseToAnyPublisher()
|
|
|
|
|
2020-08-09 13:27:38 +02:00
|
|
|
let observation = identityDatabase.identityObservation(id: identityID).share()
|
2020-08-07 23:57:18 +02:00
|
|
|
var initialIdentity: Identity?
|
|
|
|
|
2020-08-09 10:04:43 +02:00
|
|
|
_ = observation.first().sink(
|
2020-08-07 23:57:18 +02:00
|
|
|
receiveCompletion: { _ in },
|
|
|
|
receiveValue: { initialIdentity = $0 })
|
|
|
|
|
|
|
|
guard let identity = initialIdentity else { throw IdentityDatabaseError.identityNotFound }
|
|
|
|
|
|
|
|
self.identity = identity
|
2020-08-14 03:24:53 +02:00
|
|
|
secretsService = SecretsService(
|
2020-08-09 10:04:43 +02:00
|
|
|
identityID: identityID,
|
2020-08-14 03:59:17 +02:00
|
|
|
keychainService: environment.keychainServiceType)
|
2020-08-14 03:24:53 +02:00
|
|
|
networkClient = MastodonClient(session: environment.session)
|
|
|
|
networkClient.instanceURL = identity.url
|
|
|
|
networkClient.accessToken = try secretsService.item(.accessToken)
|
2020-08-07 23:57:18 +02:00
|
|
|
|
|
|
|
observation.catch { [weak self] error -> Empty<Identity, Never> in
|
|
|
|
self?.observationErrorsInput.send(error)
|
|
|
|
|
|
|
|
return Empty()
|
|
|
|
}
|
|
|
|
.assign(to: &$identity)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-09 01:08:47 +02:00
|
|
|
extension IdentityService {
|
2020-08-07 23:57:18 +02:00
|
|
|
var isAuthorized: Bool { networkClient.accessToken != nil }
|
|
|
|
|
2020-08-09 07:37:04 +02:00
|
|
|
func updateLastUse() -> AnyPublisher<Void, Error> {
|
2020-08-09 13:27:38 +02:00
|
|
|
identityDatabase.updateLastUsedAt(identityID: identity.id)
|
2020-08-09 07:37:04 +02:00
|
|
|
}
|
|
|
|
|
2020-08-07 23:57:18 +02:00
|
|
|
func verifyCredentials() -> AnyPublisher<Void, Error> {
|
|
|
|
networkClient.request(AccountEndpoint.verifyCredentials)
|
2020-08-12 10:45:01 +02:00
|
|
|
.zip(Just(identity.id).first().setFailureType(to: Error.self))
|
2020-08-09 13:27:38 +02:00
|
|
|
.flatMap(identityDatabase.updateAccount)
|
2020-08-07 23:57:18 +02:00
|
|
|
.eraseToAnyPublisher()
|
|
|
|
}
|
|
|
|
|
|
|
|
func refreshServerPreferences() -> AnyPublisher<Void, Error> {
|
|
|
|
networkClient.request(PreferencesEndpoint.preferences)
|
2020-08-12 10:45:01 +02:00
|
|
|
.zip(Just(self).first().setFailureType(to: Error.self))
|
2020-08-07 23:57:18 +02:00
|
|
|
.map { ($1.identity.preferences.updated(from: $0), $1.identity.id) }
|
2020-08-09 13:27:38 +02:00
|
|
|
.flatMap(identityDatabase.updatePreferences)
|
2020-08-07 23:57:18 +02:00
|
|
|
.eraseToAnyPublisher()
|
|
|
|
}
|
|
|
|
|
|
|
|
func refreshInstance() -> AnyPublisher<Void, Error> {
|
|
|
|
networkClient.request(InstanceEndpoint.instance)
|
2020-08-12 10:45:01 +02:00
|
|
|
.zip(Just(identity.id).first().setFailureType(to: Error.self))
|
2020-08-09 13:27:38 +02:00
|
|
|
.flatMap(identityDatabase.updateInstance)
|
2020-08-07 23:57:18 +02:00
|
|
|
.eraseToAnyPublisher()
|
|
|
|
}
|
|
|
|
|
|
|
|
func identitiesObservation() -> AnyPublisher<[Identity], Error> {
|
2020-08-09 13:27:38 +02:00
|
|
|
identityDatabase.identitiesObservation()
|
2020-08-07 23:57:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func recentIdentitiesObservation() -> AnyPublisher<[Identity], Error> {
|
2020-08-09 13:27:38 +02:00
|
|
|
identityDatabase.recentIdentitiesObservation(excluding: identity.id)
|
2020-08-07 23:57:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func updatePreferences(_ preferences: Identity.Preferences) -> AnyPublisher<Void, Error> {
|
2020-08-09 13:27:38 +02:00
|
|
|
identityDatabase.updatePreferences(preferences, forIdentityID: identity.id)
|
2020-08-07 23:57:18 +02:00
|
|
|
}
|
2020-08-14 03:24:53 +02:00
|
|
|
|
|
|
|
func createPushSubscription(deviceToken: String, alerts: PushSubscription.Alerts) -> AnyPublisher<Void, Error> {
|
|
|
|
let publicKey: String
|
|
|
|
let auth: String
|
|
|
|
|
|
|
|
do {
|
|
|
|
publicKey = try secretsService.generatePushKeyAndReturnPublicKey().base64EncodedString()
|
|
|
|
auth = try secretsService.generatePushAuth().base64EncodedString()
|
|
|
|
} catch {
|
|
|
|
return Fail(error: error).eraseToAnyPublisher()
|
|
|
|
}
|
|
|
|
|
|
|
|
let identityID = identity.id
|
|
|
|
let endpoint = Self.pushSubscriptionEndpointURL
|
|
|
|
.appendingPathComponent(deviceToken)
|
|
|
|
.appendingPathComponent(identityID.uuidString)
|
|
|
|
|
|
|
|
return networkClient.request(
|
|
|
|
PushSubscriptionEndpoint.create(
|
|
|
|
endpoint: endpoint,
|
|
|
|
publicKey: publicKey,
|
|
|
|
auth: auth,
|
|
|
|
alerts: alerts))
|
|
|
|
.map { (deviceToken, $0.alerts, identityID) }
|
|
|
|
.flatMap(identityDatabase.updatePushSubscription(deviceToken:alerts:forIdentityID:))
|
|
|
|
.eraseToAnyPublisher()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private extension IdentityService {
|
|
|
|
#if DEBUG
|
|
|
|
static let pushSubscriptionEndpointURL = URL(string: "https://metatext-apns.metabolist.com/push?sandbox=true")!
|
|
|
|
#else
|
|
|
|
static let pushSubscriptionEndpointURL = URL(string: "https://metatext-apns.metabolist.com/push")!
|
|
|
|
#endif
|
2020-08-07 23:57:18 +02:00
|
|
|
}
|