@mentra/glasses-media 3.4.0-dev.977 → 3.4.0-dev.979
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ios/CoreKit/Sources/GlassesMediaCore/MacHotspotAssociation.swift +38 -14
- package/ios/CoreKit/Sources/GlassesMediaCore/MacHotspotAuthorization.swift +73 -14
- package/ios/CoreKit/Sources/GlassesMediaCore/MacHotspotAuthorizationFacts.swift +1 -1
- package/ios/CoreKit/Sources/GlassesMediaCore/MacHotspotCleanupContinuation.swift +35 -0
- package/ios/GlassesHotspotNetwork.swift +28 -6
- package/package.json +1 -1
|
@@ -13,28 +13,32 @@ final class MacHotspotAssociation {
|
|
|
13
13
|
private let now: () -> TimeInterval
|
|
14
14
|
private let pause: ((TimeInterval) -> Void)?
|
|
15
15
|
private let makeInterface: () throws -> MacWifiInterface
|
|
16
|
+
private let authorization: MacHotspotAuthorization?
|
|
16
17
|
|
|
17
18
|
init(deadline: TimeInterval = ProcessInfo.processInfo.systemUptime + 60,
|
|
18
19
|
now: @escaping () -> TimeInterval = { ProcessInfo.processInfo.systemUptime },
|
|
19
20
|
pause: ((TimeInterval) -> Void)? = nil,
|
|
20
21
|
makeInterface: (() throws -> MacWifiInterface)? = nil,
|
|
21
|
-
authorizationFacts: MacHotspotAuthorizationFacts? = nil
|
|
22
|
+
authorizationFacts: MacHotspotAuthorizationFacts? = nil,
|
|
23
|
+
authorization: MacHotspotAuthorization? = nil)
|
|
22
24
|
{
|
|
23
25
|
self.deadline = deadline
|
|
24
26
|
self.now = now
|
|
25
27
|
self.pause = pause
|
|
26
|
-
self.
|
|
28
|
+
self.authorization = authorization
|
|
29
|
+
self.makeInterface = makeInterface ?? { try PublicCoreWlanInterface(authorizationFacts: authorizationFacts, authorization: authorization) }
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
func associate(ssid: String, password: String) throws -> String {
|
|
30
33
|
try checkCurrent()
|
|
31
34
|
let interface = try makeInterface()
|
|
32
35
|
self.interface = interface
|
|
33
|
-
if try interface
|
|
36
|
+
if try readSSID(interface) != ssid {
|
|
34
37
|
let network = try discoverNetwork(ssid: ssid, interface: interface)
|
|
35
38
|
try checkDiscoveryCurrent()
|
|
36
|
-
if let network, try interface
|
|
39
|
+
if let network, try readSSID(interface) != ssid {
|
|
37
40
|
try checkDiscoveryCurrent()
|
|
41
|
+
try authorization?.requireCurrentAuthorization()
|
|
38
42
|
// Record the intended association before this blocking call. A late
|
|
39
43
|
// successful join still belongs to us and must be cleaned up.
|
|
40
44
|
joinedSSID = ssid
|
|
@@ -42,7 +46,7 @@ final class MacHotspotAssociation {
|
|
|
42
46
|
}
|
|
43
47
|
}
|
|
44
48
|
try checkCurrent()
|
|
45
|
-
guard try interface
|
|
49
|
+
guard try readSSID(interface) == ssid else {
|
|
46
50
|
throw LocalMediaError("Mac Wi-Fi did not associate with the requested glasses hotspot")
|
|
47
51
|
}
|
|
48
52
|
return try interface.name()
|
|
@@ -56,9 +60,9 @@ final class MacHotspotAssociation {
|
|
|
56
60
|
/// A connection the user replaced must never be disconnected by our cleanup.
|
|
57
61
|
func leave() throws {
|
|
58
62
|
if let joinedSSID {
|
|
59
|
-
if try interface
|
|
60
|
-
interface
|
|
61
|
-
guard try interface
|
|
63
|
+
if let interface, try readSSID(interface) == joinedSSID {
|
|
64
|
+
interface.disassociate()
|
|
65
|
+
guard try readSSID(interface) != joinedSSID else {
|
|
62
66
|
throw LocalMediaError("Mac Wi-Fi has not released the glasses hotspot")
|
|
63
67
|
}
|
|
64
68
|
}
|
|
@@ -68,7 +72,15 @@ final class MacHotspotAssociation {
|
|
|
68
72
|
}
|
|
69
73
|
|
|
70
74
|
func currentSSID() throws -> String? {
|
|
71
|
-
|
|
75
|
+
guard let interface else { return nil }
|
|
76
|
+
return try readSSID(interface)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private func readSSID(_ interface: MacWifiInterface) throws -> String? {
|
|
80
|
+
try authorization?.requireCurrentAuthorization()
|
|
81
|
+
let ssid = try interface.currentSSID()
|
|
82
|
+
try authorization?.requireCurrentAuthorization()
|
|
83
|
+
return ssid
|
|
72
84
|
}
|
|
73
85
|
|
|
74
86
|
static func observeCurrentSSID() throws -> String? {
|
|
@@ -79,10 +91,11 @@ final class MacHotspotAssociation {
|
|
|
79
91
|
while true {
|
|
80
92
|
try checkDiscoveryCurrent()
|
|
81
93
|
// Recheck Location authorization before and after each blocking scan.
|
|
82
|
-
if try interface
|
|
94
|
+
if try readSSID(interface) == ssid { return nil }
|
|
95
|
+
try authorization?.requireCurrentAuthorization()
|
|
83
96
|
let network = try interface.network(ssid: ssid)
|
|
84
97
|
try checkDiscoveryCurrent()
|
|
85
|
-
if try interface
|
|
98
|
+
if try readSSID(interface) == ssid { return nil }
|
|
86
99
|
if let network { return network }
|
|
87
100
|
// Successful empty scans remain pending within this same join window.
|
|
88
101
|
// Native scan errors escape immediately; association is never retried.
|
|
@@ -131,10 +144,12 @@ private final class PublicCoreWlanInterface: MacWifiInterface {
|
|
|
131
144
|
private let bundle: Bundle
|
|
132
145
|
private let interface: NSObject
|
|
133
146
|
private let authorizationFacts: MacHotspotAuthorizationFacts
|
|
147
|
+
private let authorization: MacHotspotAuthorization?
|
|
134
148
|
private typealias Disassociate = @convention(c) (AnyObject, Selector) -> Void
|
|
135
149
|
|
|
136
|
-
init(authorizationFacts: MacHotspotAuthorizationFacts? = nil) throws {
|
|
150
|
+
init(authorizationFacts: MacHotspotAuthorizationFacts? = nil, authorization: MacHotspotAuthorization? = nil) throws {
|
|
137
151
|
self.authorizationFacts = authorizationFacts ?? MacHotspotAuthorizationFacts()
|
|
152
|
+
self.authorization = authorization
|
|
138
153
|
guard let bundle = Bundle(path: "/System/Library/Frameworks/CoreWLAN.framework") else {
|
|
139
154
|
throw LocalMediaError("Mac Wi-Fi framework is unavailable")
|
|
140
155
|
}
|
|
@@ -162,16 +177,24 @@ private final class PublicCoreWlanInterface: MacWifiInterface {
|
|
|
162
177
|
}
|
|
163
178
|
|
|
164
179
|
func currentSSID() throws -> String? {
|
|
180
|
+
try requireAuthorization()
|
|
181
|
+
guard let data = interface.perform(NSSelectorFromString("ssidData"))?.takeUnretainedValue() as? Data else { return nil }
|
|
182
|
+
return String(data: data, encoding: .utf8)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private func requireAuthorization() throws {
|
|
186
|
+
if let authorization { try authorization.requireCurrentAuthorization(); return }
|
|
187
|
+
// Independent internet-return observation has no owned join recipient.
|
|
188
|
+
// Its existing static authorization guard remains unchanged.
|
|
165
189
|
let authorization = CLLocationManager.authorizationStatus()
|
|
166
190
|
guard MacHotspotAuthorization.isAuthorized(authorization) else {
|
|
167
191
|
throw authorizationFacts.failure("Location access is required to verify the current Mac Wi-Fi network", raw: authorization.rawValue,
|
|
168
192
|
getter: .staticSSID, foreground: MacHotspotAuthorizationFacts.foreground())
|
|
169
193
|
}
|
|
170
|
-
guard let data = interface.perform(NSSelectorFromString("ssidData"))?.takeUnretainedValue() as? Data else { return nil }
|
|
171
|
-
return String(data: data, encoding: .utf8)
|
|
172
194
|
}
|
|
173
195
|
|
|
174
196
|
func network(ssid: String) throws -> NSObject? {
|
|
197
|
+
try requireAuthorization()
|
|
175
198
|
let selector = NSSelectorFromString("scanForNetworksWithSSID:error:")
|
|
176
199
|
let invoke = unsafeBitCast(interface.method(for: selector), to: MacWifiInvocation.Scan.self)
|
|
177
200
|
var error: NSError?
|
|
@@ -190,6 +213,7 @@ private final class PublicCoreWlanInterface: MacWifiInterface {
|
|
|
190
213
|
}
|
|
191
214
|
|
|
192
215
|
func associate(network: NSObject, password: String) throws {
|
|
216
|
+
try requireAuthorization()
|
|
193
217
|
let selector = NSSelectorFromString("associateToNetwork:password:error:")
|
|
194
218
|
let invoke = unsafeBitCast(interface.method(for: selector), to: MacWifiInvocation.Associate.self)
|
|
195
219
|
var error: NSError?
|
|
@@ -6,19 +6,31 @@ import Foundation
|
|
|
6
6
|
final class MacHotspotAuthorization: NSObject, CLLocationManagerDelegate {
|
|
7
7
|
private var manager: CLLocationManager?
|
|
8
8
|
private var completion: ((Result<Void, Error>) -> Void)?
|
|
9
|
-
private var
|
|
9
|
+
private var started = false
|
|
10
|
+
private var closed = false
|
|
11
|
+
private var ready = false
|
|
12
|
+
private var revoked = false
|
|
10
13
|
private let facts: MacHotspotAuthorizationFacts
|
|
14
|
+
private let makeManager: () -> CLLocationManager
|
|
15
|
+
private let locationServicesEnabled: () -> Bool
|
|
16
|
+
var onRevoked: ((Error) -> Void)?
|
|
17
|
+
var onRestored: (() -> Void)?
|
|
11
18
|
|
|
12
|
-
init(facts: MacHotspotAuthorizationFacts = MacHotspotAuthorizationFacts()) {
|
|
19
|
+
init(facts: MacHotspotAuthorizationFacts = MacHotspotAuthorizationFacts(), makeManager: @escaping () -> CLLocationManager = { CLLocationManager() },
|
|
20
|
+
locationServicesEnabled: @escaping () -> Bool = { CLLocationManager.locationServicesEnabled() })
|
|
21
|
+
{
|
|
13
22
|
self.facts = facts
|
|
23
|
+
self.makeManager = makeManager
|
|
24
|
+
self.locationServicesEnabled = locationServicesEnabled
|
|
14
25
|
super.init()
|
|
15
26
|
}
|
|
16
27
|
|
|
17
28
|
func start(completion: @escaping (Result<Void, Error>) -> Void) {
|
|
18
29
|
DispatchQueue.main.async {
|
|
19
|
-
guard !self.
|
|
30
|
+
guard !self.closed, !self.started else { completion(.failure(CancellationError())); return }
|
|
31
|
+
self.started = true
|
|
20
32
|
self.completion = completion
|
|
21
|
-
let manager =
|
|
33
|
+
let manager = self.makeManager()
|
|
22
34
|
self.manager = manager
|
|
23
35
|
manager.delegate = self
|
|
24
36
|
self.handle(manager.authorizationStatus, getter: .instanceInitial)
|
|
@@ -27,10 +39,48 @@ final class MacHotspotAuthorization: NSObject, CLLocationManagerDelegate {
|
|
|
27
39
|
}
|
|
28
40
|
|
|
29
41
|
func cancel() {
|
|
30
|
-
DispatchQueue.main.async {
|
|
42
|
+
DispatchQueue.main.async {
|
|
43
|
+
self.closed = true
|
|
44
|
+
self.ready = false
|
|
45
|
+
self.reply(.failure(CancellationError()))
|
|
46
|
+
self.manager?.delegate = nil
|
|
47
|
+
self.manager = nil
|
|
48
|
+
self.onRevoked = nil
|
|
49
|
+
self.onRestored = nil
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/// A ready session retains its manager through owned Wi-Fi cleanup. An unanswered
|
|
54
|
+
/// authorization request must still settle when the join is cancelled.
|
|
55
|
+
func cancelPending() {
|
|
56
|
+
DispatchQueue.main.async {
|
|
57
|
+
if self.completion != nil { self.cancel() }
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/// Query the same initialized recipient on its owning thread for every native read
|
|
62
|
+
/// or write. An earlier success is never an authorization cache.
|
|
63
|
+
func requireCurrentAuthorization() throws {
|
|
64
|
+
let check = {
|
|
65
|
+
guard !self.closed, let manager = self.manager else { throw CancellationError() }
|
|
66
|
+
let status = manager.authorizationStatus
|
|
67
|
+
let foreground = MacHotspotAuthorizationFacts.foreground()
|
|
68
|
+
self.facts.observeInstance(raw: status.rawValue, getter: .instanceCurrent, foreground: foreground)
|
|
69
|
+
guard self.locationServicesEnabled() else {
|
|
70
|
+
throw self.facts.failure("Location services are required to verify the current Mac Wi-Fi network", raw: status.rawValue,
|
|
71
|
+
getter: .instanceCurrent, foreground: foreground)
|
|
72
|
+
}
|
|
73
|
+
guard Self.isAuthorized(status) else {
|
|
74
|
+
throw self.facts.failure("Location access is required to verify the current Mac Wi-Fi network", raw: status.rawValue,
|
|
75
|
+
getter: .instanceCurrent, foreground: foreground)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if Thread.isMainThread { try check() }
|
|
79
|
+
else { try DispatchQueue.main.sync(execute: check) }
|
|
31
80
|
}
|
|
32
81
|
|
|
33
82
|
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
|
|
83
|
+
guard manager === self.manager, !closed else { return }
|
|
34
84
|
handle(manager.authorizationStatus, getter: .instanceCallback)
|
|
35
85
|
}
|
|
36
86
|
|
|
@@ -44,22 +94,31 @@ final class MacHotspotAuthorization: NSObject, CLLocationManagerDelegate {
|
|
|
44
94
|
|
|
45
95
|
private func handle(_ status: CLAuthorizationStatus, getter: MacHotspotAuthorizationFacts.Getter) {
|
|
46
96
|
let foreground = MacHotspotAuthorizationFacts.foreground()
|
|
47
|
-
|
|
48
|
-
|
|
97
|
+
guard !closed else { return }
|
|
98
|
+
facts.observeInstance(raw: status.rawValue, getter: getter, foreground: foreground)
|
|
99
|
+
if Self.isAuthorized(status) {
|
|
100
|
+
ready = true
|
|
101
|
+
if revoked { revoked = false; onRestored?() }
|
|
102
|
+
reply(.success(()))
|
|
103
|
+
return
|
|
104
|
+
}
|
|
105
|
+
let error = facts.failure("Location access is required to identify the glasses hotspot on Mac", raw: status.rawValue, getter: getter, foreground: foreground)
|
|
106
|
+
if ready {
|
|
107
|
+
ready = false
|
|
108
|
+
revoked = true
|
|
109
|
+
onRevoked?(error)
|
|
110
|
+
return
|
|
111
|
+
}
|
|
49
112
|
switch status {
|
|
50
113
|
case .denied, .restricted:
|
|
51
|
-
|
|
114
|
+
reply(.failure(error))
|
|
52
115
|
case .notDetermined: break
|
|
53
116
|
default:
|
|
54
|
-
|
|
117
|
+
reply(.failure(facts.failure("Mac hotspot Location authorization is unavailable", raw: status.rawValue, getter: getter, foreground: foreground)))
|
|
55
118
|
}
|
|
56
119
|
}
|
|
57
120
|
|
|
58
|
-
private func
|
|
59
|
-
guard !finished else { return }
|
|
60
|
-
finished = true
|
|
61
|
-
manager?.delegate = nil
|
|
62
|
-
manager = nil
|
|
121
|
+
private func reply(_ result: Result<Void, Error>) {
|
|
63
122
|
let reply = completion
|
|
64
123
|
completion = nil
|
|
65
124
|
reply?(result)
|
|
@@ -7,7 +7,7 @@ import Foundation
|
|
|
7
7
|
|
|
8
8
|
/// Diagnostic observations only. These facts never authorize a network read or a join.
|
|
9
9
|
final class MacHotspotAuthorizationFacts {
|
|
10
|
-
enum Getter: String { case instanceInitial = "instance-initial", instanceCallback = "instance-callback", staticSSID = "static-ssid" }
|
|
10
|
+
enum Getter: String { case instanceInitial = "instance-initial", instanceCallback = "instance-callback", instanceCurrent = "instance-current", staticSSID = "static-ssid" }
|
|
11
11
|
enum Foreground: String { case active, inactive, background, unknown }
|
|
12
12
|
private let lock = NSLock()
|
|
13
13
|
private var instance: (raw: Int32, getter: Getter, foreground: Foreground)?
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/// Retain a real authorization restoration while this session's cleanup result
|
|
4
|
+
/// is still returning from its worker. All state belongs to the hotspot owner queue.
|
|
5
|
+
final class MacHotspotCleanupContinuation {
|
|
6
|
+
private var restorationPending = false
|
|
7
|
+
private let isCurrentCancelledSession: () -> Bool
|
|
8
|
+
private let requestCleanup: () -> Void
|
|
9
|
+
|
|
10
|
+
init(authorization: MacHotspotAuthorization, queue: DispatchQueue,
|
|
11
|
+
isCurrentCancelledSession: @escaping () -> Bool, requestCleanup: @escaping () -> Void)
|
|
12
|
+
{
|
|
13
|
+
self.isCurrentCancelledSession = isCurrentCancelledSession
|
|
14
|
+
self.requestCleanup = requestCleanup
|
|
15
|
+
authorization.onRestored = { [weak self] in
|
|
16
|
+
queue.async { [weak self] in
|
|
17
|
+
guard let self, self.isCurrentCancelledSession() else { return }
|
|
18
|
+
self.restorationPending = true
|
|
19
|
+
self.requestCleanup()
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
func cleanupStarted() {
|
|
25
|
+
restorationPending = false
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
func cleanupFailed() {
|
|
29
|
+
guard restorationPending, isCurrentCancelledSession() else { return }
|
|
30
|
+
restorationPending = false
|
|
31
|
+
// A current restoration was not consumed by the failed worker attempt.
|
|
32
|
+
// Resume only its owned cleanup, which rechecks live permission and SSID.
|
|
33
|
+
requestCleanup()
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -31,6 +31,7 @@ public final class GlassesHotspotNetwork {
|
|
|
31
31
|
private let macWorker = DispatchQueue(label: "com.mentra.glassesmedia.mac-wifi")
|
|
32
32
|
private var macAssociation: MacHotspotAssociation?
|
|
33
33
|
private var macAuthorization: MacHotspotAuthorization?
|
|
34
|
+
private var macCleanupContinuation: MacHotspotCleanupContinuation?
|
|
34
35
|
private var ownershipToken: UUID?
|
|
35
36
|
private var finishingLeave = false
|
|
36
37
|
private var waitForInternetAfterLeave = false
|
|
@@ -75,7 +76,7 @@ public final class GlassesHotspotNetwork {
|
|
|
75
76
|
guard gen == self.generation, self.joinReply != nil, self.localAccess == nil else { return }
|
|
76
77
|
self.cancelled = true
|
|
77
78
|
self.macAssociation?.cancel()
|
|
78
|
-
self.macAuthorization?.
|
|
79
|
+
self.macAuthorization?.cancelPending()
|
|
79
80
|
self.finishJoin(.failure(LocalMediaError("Hotspot join timed out")))
|
|
80
81
|
// apply() cannot be cancelled. Retain the reservation until its callback and remove the
|
|
81
82
|
// late configuration before another call is permitted to acquire the network.
|
|
@@ -91,7 +92,7 @@ public final class GlassesHotspotNetwork {
|
|
|
91
92
|
self.waitForInternetAfterLeave = self.waitForInternetAfterLeave || waitForInternet
|
|
92
93
|
self.cancelled = true
|
|
93
94
|
self.macAssociation?.cancel()
|
|
94
|
-
self.macAuthorization?.
|
|
95
|
+
self.macAuthorization?.cancelPending()
|
|
95
96
|
if self.leaveCompletion == nil {
|
|
96
97
|
self.leaveCompletion = HotspotLeaveCompletion {
|
|
97
98
|
if let token = self.ownershipToken { HotspotNetworkOwnership.shared.release(token) }
|
|
@@ -242,12 +243,30 @@ public final class GlassesHotspotNetwork {
|
|
|
242
243
|
let facts = MacHotspotAuthorizationFacts()
|
|
243
244
|
let authorization = MacHotspotAuthorization(facts: facts)
|
|
244
245
|
macAuthorization = authorization
|
|
246
|
+
authorization.onRevoked = { [weak self] error in
|
|
247
|
+
guard let self else { return }
|
|
248
|
+
self.queue.async {
|
|
249
|
+
guard gen == self.generation, !self.cancelled else { return }
|
|
250
|
+
self.cancelled = true
|
|
251
|
+
self.macAssociation?.cancel()
|
|
252
|
+
self.finishJoin(.failure(error))
|
|
253
|
+
self.onLost?("Glasses hotspot Location authorization was revoked")
|
|
254
|
+
if !self.applying { self.finishLeave() }
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
macCleanupContinuation = MacHotspotCleanupContinuation(authorization: authorization, queue: queue,
|
|
258
|
+
isCurrentCancelledSession: { [weak self] in
|
|
259
|
+
guard let self else { return false }
|
|
260
|
+
return gen == self.generation && self.cancelled
|
|
261
|
+
}, requestCleanup: { [weak self] in
|
|
262
|
+
guard let self, !self.applying else { return }
|
|
263
|
+
self.finishLeave()
|
|
264
|
+
})
|
|
245
265
|
authorization.start { result in
|
|
246
266
|
self.queue.async {
|
|
247
267
|
guard gen == self.generation, !self.cancelled else { return }
|
|
248
|
-
self.macAuthorization = nil
|
|
249
268
|
switch result {
|
|
250
|
-
case .success: self.applyMacAssociation(ssid: ssid, passphrase: passphrase, generation: gen, deadline: deadline, authorizationFacts: facts)
|
|
269
|
+
case .success: self.applyMacAssociation(ssid: ssid, passphrase: passphrase, generation: gen, deadline: deadline, authorizationFacts: facts, authorization: authorization)
|
|
251
270
|
case let .failure(error):
|
|
252
271
|
self.finishJoin(.failure(error)); self.finishLeave()
|
|
253
272
|
}
|
|
@@ -255,8 +274,8 @@ public final class GlassesHotspotNetwork {
|
|
|
255
274
|
}
|
|
256
275
|
}
|
|
257
276
|
|
|
258
|
-
private func applyMacAssociation(ssid: String, passphrase: String, generation gen: Int, deadline: TimeInterval, authorizationFacts: MacHotspotAuthorizationFacts) {
|
|
259
|
-
let association = MacHotspotAssociation(deadline: deadline, authorizationFacts: authorizationFacts)
|
|
277
|
+
private func applyMacAssociation(ssid: String, passphrase: String, generation gen: Int, deadline: TimeInterval, authorizationFacts: MacHotspotAuthorizationFacts, authorization: MacHotspotAuthorization) {
|
|
278
|
+
let association = MacHotspotAssociation(deadline: deadline, authorizationFacts: authorizationFacts, authorization: authorization)
|
|
260
279
|
macAssociation = association
|
|
261
280
|
applying = true
|
|
262
281
|
macWorker.async {
|
|
@@ -457,6 +476,7 @@ public final class GlassesHotspotNetwork {
|
|
|
457
476
|
private func finishLeave() {
|
|
458
477
|
guard !finishingLeave else { return }
|
|
459
478
|
finishingLeave = true
|
|
479
|
+
macCleanupContinuation?.cleanupStarted()
|
|
460
480
|
if let association = macAssociation {
|
|
461
481
|
association.cancel()
|
|
462
482
|
// This queue is also the association queue: cleanup cannot overtake
|
|
@@ -475,6 +495,7 @@ public final class GlassesHotspotNetwork {
|
|
|
475
495
|
let completion = self.leaveCompletion
|
|
476
496
|
self.leaveCompletion = nil
|
|
477
497
|
completion?.complete(.failure(error))
|
|
498
|
+
self.macCleanupContinuation?.cleanupFailed()
|
|
478
499
|
}
|
|
479
500
|
}
|
|
480
501
|
}
|
|
@@ -483,6 +504,7 @@ public final class GlassesHotspotNetwork {
|
|
|
483
504
|
|
|
484
505
|
private func completeLeave() {
|
|
485
506
|
internetObservationOwner.cancel()
|
|
507
|
+
macCleanupContinuation = nil
|
|
486
508
|
generation += 1
|
|
487
509
|
macAuthorization?.cancel(); macAuthorization = nil
|
|
488
510
|
localAccess?.cancel(); localAccess = nil
|