@mentra/glasses-media 3.2.1-dev.294 → 3.2.1-dev.295

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.
@@ -39,6 +39,14 @@ public enum LocalMediaPolicy {
39
39
  return bytes[3] > 0 && bytes[3] < 255
40
40
  }
41
41
 
42
+ /// A Mac may join through its host network controls. Reuse that connection only
43
+ /// after both its SSID and DHCP address match the hotspot advertised over BLE.
44
+ public static func canReuseHotspot(requestedSSID: String, currentSSID: String?, address: String?, gateway: String?) -> Bool {
45
+ guard !requestedSSID.isEmpty, requestedSSID == currentSSID,
46
+ let address, let gateway else { return false }
47
+ return isHotspotClientAddress(address, gateway: gateway)
48
+ }
49
+
42
50
  /// Keep only concrete host candidates on the local link. Never rewrite a cellular socket's
43
51
  /// advertised address: a candidate must already belong to the interface it claims.
44
52
  public static func localSdp(_ sdp: String, address: String, answer: Bool) throws -> String {
@@ -0,0 +1,20 @@
1
+ #if MENTRA_E2E
2
+ import Foundation
3
+
4
+ /// Test-build dependency input, not a production network discovery mechanism.
5
+ /// The Mac harness owns association and verifies the USB fixture, gateway MAC,
6
+ /// DHCP address, local health and Ethernet before launching this process.
7
+ public struct MacE2EHotspotLease: Decodable {
8
+ public let ssid: String
9
+ public let gateway: String
10
+ public let address: String
11
+ public let issuedAt: TimeInterval
12
+
13
+ public func matches(ssid: String, gateway: String?, address: String?, now: TimeInterval) -> Bool {
14
+ let age = now - issuedAt
15
+ return age >= 0 && age <= 300 && self.gateway == gateway && self.address == address &&
16
+ LocalMediaPolicy.canReuseHotspot(requestedSSID: ssid, currentSSID: self.ssid,
17
+ address: address, gateway: gateway)
18
+ }
19
+ }
20
+ #endif
@@ -14,11 +14,15 @@ public final class GlassesHotspotNetwork {
14
14
  private var gatewayAddress: String?
15
15
  private var generation = 0
16
16
  private var applying = false
17
+ private var ownsConfiguration = false
17
18
  private var cancelled = false
18
19
  private var joinReply: ((Result<String, Error>) -> Void)?
19
20
  private var leaveReplies: [() -> Void] = []
20
21
  private var monitor: NWPathMonitor?
21
22
  private var localAccess: LocalNetworkAccessRequest?
23
+ #if MENTRA_E2E
24
+ private var testLeaseConsumed = false
25
+ #endif
22
26
  public var onLost: ((String) -> Void)?
23
27
  public var onPermissionRequired: (() -> Void)?
24
28
  public init() {}
@@ -35,8 +39,49 @@ public final class GlassesHotspotNetwork {
35
39
  self.lastHotspotSSID = ssid
36
40
  self.gatewayAddress = gateway
37
41
  self.cancelled = false
42
+ self.applying = false
43
+ self.ownsConfiguration = false
38
44
  self.joinReply = completion
39
- self.applyConfiguration(ssid: ssid, passphrase: passphrase, generation: gen)
45
+ if ProcessInfo.processInfo.isiOSAppOnMac {
46
+ #if MENTRA_E2E
47
+ if let raw = ProcessInfo.processInfo.environment["MENTRA_E2E_PREJOINED_HOTSPOT"] {
48
+ let address = Self.wifiAddress()
49
+ guard !self.testLeaseConsumed,
50
+ let lease = try? JSONDecoder().decode(MacE2EHotspotLease.self, from: Data(raw.utf8)),
51
+ lease.matches(ssid: ssid, gateway: gateway, address: address, now: Date().timeIntervalSince1970),
52
+ let address
53
+ else {
54
+ self.finishJoin(.failure(LocalMediaError("Mac E2E network lease is invalid, expired or already consumed")))
55
+ self.finishLeave()
56
+ return
57
+ }
58
+ self.testLeaseConsumed = true
59
+ self.localAddress = address
60
+ self.logger.notice("HOTSPOT_JOIN test_harness_connection native_association_untested")
61
+ self.startMonitor(generation: gen)
62
+ self.waitForLocalAccess(address: address, generation: gen)
63
+ return
64
+ }
65
+ #endif
66
+ NEHotspotNetwork.fetchCurrent { network in
67
+ self.queue.async {
68
+ guard gen == self.generation, !self.cancelled else { return }
69
+ let address = Self.wifiAddress()
70
+ if let address, LocalMediaPolicy.canReuseHotspot(requestedSSID: ssid, currentSSID: network?.ssid,
71
+ address: address, gateway: gateway)
72
+ {
73
+ self.localAddress = address
74
+ self.logger.info("HOTSPOT_JOIN reuse_verified_macos_connection")
75
+ self.startMonitor(generation: gen)
76
+ self.waitForLocalAccess(address: address, generation: gen)
77
+ } else {
78
+ self.applyConfiguration(ssid: ssid, passphrase: passphrase, generation: gen)
79
+ }
80
+ }
81
+ }
82
+ } else {
83
+ self.applyConfiguration(ssid: ssid, passphrase: passphrase, generation: gen)
84
+ }
40
85
  self.queue.asyncAfter(deadline: .now() + 60) {
41
86
  // Once associated, the user may still be reading the Local Network alert.
42
87
  // Its response time is not a hotspot association timeout.
@@ -45,7 +90,7 @@ public final class GlassesHotspotNetwork {
45
90
  self.finishJoin(.failure(LocalMediaError("Hotspot join timed out")))
46
91
  // apply() cannot be cancelled. Retain the reservation until its callback and remove the
47
92
  // late configuration before another call is permitted to acquire the network.
48
- NEHotspotConfigurationManager.shared.removeConfiguration(forSSID: ssid)
93
+ if self.ownsConfiguration { NEHotspotConfigurationManager.shared.removeConfiguration(forSSID: ssid) }
49
94
  if !self.applying { self.finishLeave() }
50
95
  }
51
96
  }
@@ -56,7 +101,7 @@ public final class GlassesHotspotNetwork {
56
101
  self.cancelled = true
57
102
  self.leaveReplies.append(completion)
58
103
  self.finishJoin(.failure(LocalMediaError("Hotspot join cancelled")))
59
- if let ssid = self.ssid { NEHotspotConfigurationManager.shared.removeConfiguration(forSSID: ssid) }
104
+ if let ssid = self.ssid, self.ownsConfiguration { NEHotspotConfigurationManager.shared.removeConfiguration(forSSID: ssid) }
60
105
  if !self.applying { self.finishLeave() }
61
106
  }
62
107
  }
@@ -147,6 +192,7 @@ public final class GlassesHotspotNetwork {
147
192
  let config = NEHotspotConfiguration(ssid: ssid, passphrase: passphrase, isWEP: false)
148
193
  config.joinOnce = false
149
194
  applying = true
195
+ ownsConfiguration = true
150
196
  logger.info("HOTSPOT_JOIN apply_start ios_on_mac=\(ProcessInfo.processInfo.isiOSAppOnMac)")
151
197
  NEHotspotConfigurationManager.shared.apply(config) { error in
152
198
  self.queue.async {
@@ -236,7 +282,8 @@ public final class GlassesHotspotNetwork {
236
282
  generation += 1
237
283
  localAccess?.cancel(); localAccess = nil
238
284
  monitor?.cancel(); monitor = nil
239
- if let ssid { NEHotspotConfigurationManager.shared.removeConfiguration(forSSID: ssid) }
285
+ if let ssid, ownsConfiguration { NEHotspotConfigurationManager.shared.removeConfiguration(forSSID: ssid) }
286
+ ownsConfiguration = false
240
287
  ssid = nil
241
288
  localAddress = nil
242
289
  gatewayAddress = nil
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mentra/glasses-media",
3
- "version": "3.2.1-dev.294",
3
+ "version": "3.2.1-dev.295",
4
4
  "description": "Shared native glasses media receivers and local network transport",
5
5
  "license": "MIT",
6
6
  "author": "Mentra Community",