237 lines
9.6 KiB
Swift
237 lines
9.6 KiB
Swift
//
|
|
// Copyright 2018 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
import Foundation
|
|
import LibSignalClient
|
|
|
|
@objc
|
|
public enum SignalServiceError: Int, Error {
|
|
case obsoleteLinkedDevice
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
public protocol SignalServiceClient {
|
|
func getAvailablePreKeys(for identity: OWSIdentity) -> Promise<(ecCount: Int, pqCount: Int)>
|
|
/// If a username and password are both provided, those are used for the request's
|
|
/// Authentication header. Otherwise, the default header is used (whatever's on
|
|
/// TSAccountManager).
|
|
func registerPreKeys(
|
|
for identity: OWSIdentity,
|
|
signedPreKeyRecord: SignalServiceKit.SignedPreKeyRecord?,
|
|
preKeyRecords: [SignalServiceKit.PreKeyRecord]?,
|
|
pqLastResortPreKeyRecord: KyberPreKeyRecord?,
|
|
pqPreKeyRecords: [KyberPreKeyRecord]?,
|
|
auth: ChatServiceAuth
|
|
) -> Promise<Void>
|
|
func setCurrentSignedPreKey(_ signedPreKey: SignalServiceKit.SignedPreKeyRecord, for identity: OWSIdentity) -> Promise<Void>
|
|
func requestUDSenderCertificate(uuidOnly: Bool) -> Promise<Data>
|
|
func updatePrimaryDeviceAccountAttributes(authedAccount: AuthedAccount) async throws -> AccountAttributes
|
|
func getAccountWhoAmI() -> Promise<WhoAmIRequestFactory.Responses.WhoAmI>
|
|
func requestStorageAuth(chatServiceAuth: ChatServiceAuth) -> Promise<(username: String, password: String)>
|
|
func getRemoteConfig(auth: ChatServiceAuth) -> Promise<RemoteConfigResponse>
|
|
|
|
// MARK: - Secondary Devices
|
|
|
|
func updateSecondaryDeviceCapabilities(_ capabilities: AccountAttributes.Capabilities, authedAccount: AuthedAccount) async throws
|
|
}
|
|
|
|
extension SignalServiceClient {
|
|
|
|
public func updatePrimaryDeviceAccountAttributes() async throws -> AccountAttributes {
|
|
return try await updatePrimaryDeviceAccountAttributes(authedAccount: .implicit())
|
|
}
|
|
|
|
public func updateSecondaryDeviceCapabilities(_ capabilities: AccountAttributes.Capabilities) async throws {
|
|
try await updateSecondaryDeviceCapabilities(capabilities, authedAccount: .implicit())
|
|
}
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
public enum RemoteConfigItem {
|
|
case isEnabled(Bool)
|
|
case value(String)
|
|
}
|
|
|
|
public struct RemoteConfigResponse {
|
|
public let items: [String: RemoteConfigItem]
|
|
public let serverEpochTimeSeconds: UInt64?
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
/// Based on libsignal-service-java's PushServiceSocket class
|
|
@objc
|
|
public class SignalServiceRestClient: NSObject, SignalServiceClient, Dependencies {
|
|
|
|
public static let shared = SignalServiceRestClient()
|
|
|
|
// MARK: - Public
|
|
|
|
public func getAvailablePreKeys(for identity: OWSIdentity) -> Promise<(ecCount: Int, pqCount: Int)> {
|
|
let request = OWSRequestFactory.availablePreKeysCountRequest(for: identity)
|
|
return firstly {
|
|
networkManager.makePromise(request: request)
|
|
}.map(on: DispatchQueue.global()) { response in
|
|
guard let json = response.responseBodyJson else {
|
|
throw OWSAssertionError("Missing or invalid JSON.")
|
|
}
|
|
guard let params = ParamParser(responseObject: json) else {
|
|
throw OWSAssertionError("Missing or invalid response.")
|
|
}
|
|
|
|
let ecCount: Int = try params.required(key: "count")
|
|
let pqCount: Int = try params.optional(key: "pqCount") ?? 0
|
|
|
|
return (ecCount, pqCount)
|
|
}
|
|
}
|
|
|
|
public func registerPreKeys(
|
|
for identity: OWSIdentity,
|
|
signedPreKeyRecord: SignalServiceKit.SignedPreKeyRecord?,
|
|
preKeyRecords: [SignalServiceKit.PreKeyRecord]?,
|
|
pqLastResortPreKeyRecord: KyberPreKeyRecord?,
|
|
pqPreKeyRecords: [KyberPreKeyRecord]?,
|
|
auth: ChatServiceAuth
|
|
) -> Promise<Void> {
|
|
let request = OWSRequestFactory.registerPrekeysRequest(
|
|
identity: identity,
|
|
signedPreKeyRecord: signedPreKeyRecord,
|
|
prekeyRecords: preKeyRecords,
|
|
pqLastResortPreKeyRecord: pqLastResortPreKeyRecord,
|
|
pqPreKeyRecords: pqPreKeyRecords,
|
|
auth: auth
|
|
)
|
|
return networkManager.makePromise(request: request).asVoid()
|
|
}
|
|
|
|
public func setCurrentSignedPreKey(_ signedPreKey: SignalServiceKit.SignedPreKeyRecord, for identity: OWSIdentity) -> Promise<Void> {
|
|
let request = OWSRequestFactory.registerSignedPrekeyRequest(for: identity, signedPreKey: signedPreKey)
|
|
return networkManager.makePromise(request: request).asVoid()
|
|
}
|
|
|
|
public func requestUDSenderCertificate(uuidOnly: Bool) -> Promise<Data> {
|
|
let request = OWSRequestFactory.udSenderCertificateRequest(uuidOnly: uuidOnly)
|
|
return firstly {
|
|
self.networkManager.makePromise(request: request)
|
|
}.map(on: DispatchQueue.global()) { response in
|
|
guard let json = response.responseBodyJson else {
|
|
throw OWSUDError.invalidData(description: "Missing or invalid JSON")
|
|
}
|
|
guard let parser = ParamParser(responseObject: json) else {
|
|
throw OWSUDError.invalidData(description: "Invalid sender certificate response")
|
|
}
|
|
|
|
return try parser.requiredBase64EncodedData(key: "certificate")
|
|
}
|
|
}
|
|
|
|
public func updatePrimaryDeviceAccountAttributes(authedAccount: AuthedAccount) async throws -> AccountAttributes {
|
|
guard DependenciesBridge.shared.tsAccountManager.registrationStateWithMaybeSneakyTransaction.isPrimaryDevice == true else {
|
|
throw OWSAssertionError("only primary device should update account attributes")
|
|
}
|
|
|
|
let attributes = await self.databaseStorage.awaitableWrite { transaction in
|
|
return AccountAttributes.generateForPrimaryDevice(
|
|
fromDependencies: self,
|
|
svr: DependenciesBridge.shared.svr,
|
|
transaction: transaction
|
|
)
|
|
}
|
|
|
|
let request = AccountAttributesRequestFactory.updatePrimaryDeviceAttributesRequest(attributes)
|
|
request.setAuth(authedAccount.chatServiceAuth)
|
|
_ = try await networkManager.makePromise(request: request).awaitable()
|
|
|
|
return attributes
|
|
}
|
|
|
|
public func getAccountWhoAmI() -> Promise<WhoAmIRequestFactory.Responses.WhoAmI> {
|
|
let request = WhoAmIRequestFactory.whoAmIRequest(auth: .implicit())
|
|
|
|
return firstly {
|
|
networkManager.makePromise(request: request)
|
|
}.map(on: DispatchQueue.global()) { response in
|
|
guard let json = response.responseBodyData else {
|
|
throw OWSAssertionError("Missing or invalid JSON.")
|
|
}
|
|
return try JSONDecoder().decode(WhoAmIRequestFactory.Responses.WhoAmI.self, from: json)
|
|
}
|
|
}
|
|
|
|
public func requestStorageAuth(chatServiceAuth: ChatServiceAuth) -> Promise<(username: String, password: String)> {
|
|
let request = OWSRequestFactory.storageAuthRequest()
|
|
request.setAuth(chatServiceAuth)
|
|
return firstly {
|
|
networkManager.makePromise(request: request)
|
|
}.map(on: DispatchQueue.global()) { response in
|
|
guard let json = response.responseBodyJson else {
|
|
throw OWSAssertionError("Missing or invalid JSON.")
|
|
}
|
|
guard let parser = ParamParser(responseObject: json) else {
|
|
throw OWSAssertionError("Missing or invalid response.")
|
|
}
|
|
|
|
let username: String = try parser.required(key: "username")
|
|
let password: String = try parser.required(key: "password")
|
|
|
|
return (username: username, password: password)
|
|
}
|
|
}
|
|
|
|
// yields a map of ["feature_name": isEnabled]
|
|
public func getRemoteConfig(auth: ChatServiceAuth) -> Promise<RemoteConfigResponse> {
|
|
let request = OWSRequestFactory.getRemoteConfigRequest()
|
|
request.setAuth(auth)
|
|
|
|
return firstly {
|
|
networkManager.makePromise(request: request)
|
|
}.map(on: DispatchQueue.global()) { response in
|
|
guard let json = response.responseBodyJson else {
|
|
throw OWSAssertionError("Missing or invalid JSON.")
|
|
}
|
|
guard let parser = ParamParser(responseObject: json) else {
|
|
throw OWSAssertionError("Missing or invalid response.")
|
|
}
|
|
|
|
let config: [[String: Any]] = try parser.required(key: "config")
|
|
let serverEpochTimeSeconds: UInt64? = try parser.optional(key: "serverEpochTime")
|
|
|
|
let items: [String: RemoteConfigItem] = try config.reduce([:]) { accum, item in
|
|
var accum = accum
|
|
guard let itemParser = ParamParser(responseObject: item) else {
|
|
throw OWSAssertionError("Missing or invalid remote config item.")
|
|
}
|
|
|
|
let name: String = try itemParser.required(key: "name")
|
|
let isEnabled: Bool = try itemParser.required(key: "enabled")
|
|
|
|
if let value: String = try itemParser.optional(key: "value") {
|
|
accum[name] = .value(value)
|
|
} else {
|
|
accum[name] = .isEnabled(isEnabled)
|
|
}
|
|
|
|
return accum
|
|
}
|
|
|
|
return .init(items: items, serverEpochTimeSeconds: serverEpochTimeSeconds)
|
|
}
|
|
}
|
|
|
|
// MARK: - Secondary Devices
|
|
|
|
public func updateSecondaryDeviceCapabilities(_ capabilities: AccountAttributes.Capabilities, authedAccount: AuthedAccount) async throws {
|
|
let request = AccountAttributesRequestFactory.updateLinkedDeviceCapabilitiesRequest(
|
|
capabilities,
|
|
tsAccountManager: DependenciesBridge.shared.tsAccountManager
|
|
)
|
|
request.setAuth(authedAccount.chatServiceAuth)
|
|
_ = try await networkManager.makePromise(request: request).awaitable()
|
|
}
|
|
}
|