1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
|
//
// DeviceManagementInteractor.swift
// MullvadVPN
//
// Created by pronebird on 26/07/2022.
// Copyright © 2025 Mullvad VPN AB. All rights reserved.
//
import Foundation
import MullvadREST
import MullvadTypes
import WireGuardKitTypes
protocol DeviceManaging {
var currentDeviceId: String? { get }
func getDevices(_ completionHandler: @escaping @Sendable (Result<[Device], Error>) -> Void) -> Cancellable
func deleteDevice(
_ identifier: String,
completionHandler: @escaping @Sendable (Result<Bool, Error>) -> Void
) -> Cancellable
}
class DeviceManagementInteractor: DeviceManaging, @unchecked Sendable {
private let devicesProxy: DeviceHandling
private let accountNumber: String
let currentDeviceId: String?
init(accountNumber: String, currentDeviceId: String? = nil, devicesProxy: DeviceHandling) {
self.accountNumber = accountNumber
self.devicesProxy = devicesProxy
self.currentDeviceId = currentDeviceId
}
@discardableResult
func getDevices(_ completionHandler: @escaping @Sendable (Result<[Device], Error>) -> Void) -> Cancellable {
devicesProxy.getDevices(
accountNumber: accountNumber,
retryStrategy: .default,
completion: completionHandler
)
}
@discardableResult
func deleteDevice(
_ identifier: String,
completionHandler: @escaping @Sendable (Result<Bool, Error>) -> Void
) -> Cancellable {
devicesProxy.deleteDevice(
accountNumber: accountNumber,
identifier: identifier,
retryStrategy: .default,
completion: completionHandler
)
}
}
class MockDeviceManaging: DeviceManaging {
let currentDeviceId: String? = "123"
let getDevicesCompletionHandler: (() -> Result<[Device], Error>)?
static private let mockDevices = [
Device(
id: "123",
name: "Blind Mole",
pubkey: PrivateKey().publicKey,
hijackDNS: false,
created: Date(),
ipv4Address: IPAddressRange(from: "127.0.0.1/32")!,
ipv6Address: IPAddressRange(from: "::ff/64")!
),
Device(
id: "456",
name: "Tall Mole",
pubkey: PrivateKey().publicKey,
hijackDNS: false,
created: Date(),
ipv4Address: IPAddressRange(from: "127.0.0.1/32")!,
ipv6Address: IPAddressRange(from: "::ff/64")!
),
Device(
id: "543",
name: "Old Mole",
pubkey: PrivateKey().publicKey,
hijackDNS: false,
created: Date(),
ipv4Address: IPAddressRange(from: "127.0.0.1/32")!,
ipv6Address: IPAddressRange(from: "::ff/64")!
),
Device(
id: "867",
name: "Young Mole",
pubkey: PrivateKey().publicKey,
hijackDNS: false,
created: Date(),
ipv4Address: IPAddressRange(from: "127.0.0.1/32")!,
ipv6Address: IPAddressRange(from: "::ff/64")!
),
Device(
id: "234",
name: "Rich Mole",
pubkey: PrivateKey().publicKey,
hijackDNS: false,
created: Date(),
ipv4Address: IPAddressRange(from: "127.0.0.1/32")!,
ipv6Address: IPAddressRange(from: "::ff/64")!
),
]
let devicesToReturn: Int
init(
devicesToReturn: Int = 5,
getDevicesCompletionHandler: (() -> Result<[Device], Error>)? = {
.success(mockDevices)
}
) {
self.devicesToReturn = devicesToReturn
self.getDevicesCompletionHandler = getDevicesCompletionHandler
}
func deleteDevice(
_ identifier: String,
completionHandler: @escaping @Sendable (Result<Bool, any Error>) -> Void
) -> any MullvadTypes.Cancellable {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
completionHandler(.success(true))
}
return AnyCancellable()
}
func getDevices(_ completionHandler: @escaping @Sendable (Result<[Device], Error>) -> Void) -> Cancellable {
if let getDevicesCompletionHandler {
completionHandler(getDevicesCompletionHandler().map { Array($0.prefix(devicesToReturn)) })
}
return AnyCancellable()
}
}
|