Signal-iOS/SignalServiceKit/Network/API/OWSDevicesService.swift
2024-04-24 11:28:33 -07:00

123 lines
4.4 KiB
Swift

//
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
import SignalCoreKit
@objc
open class OWSDevicesService: NSObject {
@available(*, unavailable, message: "Do not instantiate this class.")
private override init() {}
@objc
public static let deviceListUpdateSucceeded = Notification.Name("deviceListUpdateSucceeded")
@objc
public static let deviceListUpdateFailed = Notification.Name("deviceListUpdateFailed")
@objc
public static let deviceListUpdateModifiedDeviceList = Notification.Name("deviceListUpdateModifiedDeviceList")
public static func refreshDevices() -> Promise<Void> {
return firstly {
Self.getDevices()
}.done(on: DispatchQueue.global()) { (devices: [OWSDevice]) in
let didAddOrRemove = databaseStorage.write { transaction in
// If we have more than one device we may have a linked device.
// Setting this flag here shouldn't be necessary, but we do so
// because the "cost" is low and it will improve robustness.
if !devices.isEmpty {
DependenciesBridge.shared.deviceManager.setMayHaveLinkedDevices(
true,
transaction: transaction.asV2Write
)
}
return OWSDevice.replaceAll(with: devices, transaction: transaction)
}
NotificationCenter.default.postNotificationNameAsync(Self.deviceListUpdateSucceeded, object: nil)
if didAddOrRemove {
NotificationCenter.default.postNotificationNameAsync(Self.deviceListUpdateModifiedDeviceList, object: nil)
}
}.catch(on: DispatchQueue.global()) { error in
owsFailDebugUnlessNetworkFailure(error)
NotificationCenter.default.postNotificationNameAsync(Self.deviceListUpdateFailed, object: error)
}
}
private static func getDevices() -> Promise<[OWSDevice]> {
let request = OWSRequestFactory.getDevicesRequest()
return firstly(on: DispatchQueue.global()) {
Self.networkManager.makePromise(request: request, canUseWebSocket: true)
}.map(on: DispatchQueue.global()) { response in
guard let devices = Self.parseDeviceList(response: response) else {
throw OWSAssertionError("Unable to parse devices response.")
}
return devices
}
}
private static func parseDeviceList(response: HTTPResponse) -> [OWSDevice]? {
struct DeviceListResponse: Decodable {
struct Device: Decodable {
enum CodingKeys: String, CodingKey {
case createdAtMs = "created"
case lastSeenAtMs = "lastSeen"
case id
case encryptedName = "name"
}
let createdAtMs: UInt64
let lastSeenAtMs: UInt64
let id: Int
let encryptedName: String?
}
let devices: [Device]
}
guard
let devicesJsonData = response.responseBodyData,
let devicesResponse = try? JSONDecoder().decode(DeviceListResponse.self, from: devicesJsonData)
else {
owsFailDebug("Missing or invalid devices response!")
return nil
}
return devicesResponse.devices.compactMap { device in
guard device.id >= OWSDevice.primaryDeviceId else {
owsFailBeta("Invalid device ID: \(device.id)!")
return nil
}
return OWSDevice(
deviceId: device.id,
encryptedName: device.encryptedName,
createdAt: Date(millisecondsSince1970: device.createdAtMs),
lastSeenAt: Date(millisecondsSince1970: device.lastSeenAtMs)
)
}
}
public static func unlinkDevice(
_ device: OWSDevice,
success: @escaping () -> Void,
failure: @escaping (Error) -> Void
) {
let request = OWSRequestFactory.deleteDeviceRequest(device)
firstly {
Self.networkManager.makePromise(request: request)
}.map(on: DispatchQueue.main) { _ in
success()
}.catch(on: DispatchQueue.main) { error in
owsFailDebugUnlessNetworkFailure(error)
failure(error)
}
}
}