@mentra/glasses-media 3.2.1 → 3.3.0-beta.954

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.
@@ -14,7 +14,7 @@ Pod::Spec.new do |s|
14
14
  s.dependency 'ExpoModulesCore'
15
15
  # Keep in sync with the iOS-only @livekit/react-native-webrtc podspec patch.
16
16
  s.dependency 'WebRTC-SDK', '144.7559.15'
17
- s.frameworks = 'AVFoundation', 'CoreMedia', 'CoreVideo', 'Network', 'NetworkExtension'
17
+ s.frameworks = 'AVFoundation', 'CoreMedia', 'CoreVideo', 'CoreLocation', 'Network', 'NetworkExtension'
18
18
  s.source_files = '*.{swift,h,m,mm}', 'CoreKit/Sources/GlassesMediaCore/*.swift'
19
19
  s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' }
20
20
  end
@@ -4,11 +4,49 @@ import Foundation
4
4
  public final class GlassesMediaRelayModule: Module {
5
5
  private let queue = DispatchQueue(label: "com.mentra.glassesmedia.relay")
6
6
  private var session: ManagedRelaySession?
7
+ private let localHotspot = GlassesHotspotNetwork()
8
+ private var localHotspotOwner: String?
7
9
 
8
10
  public func definition() -> ModuleDefinition {
9
11
  Name("MentraGlassesMediaRelay")
10
12
  Events("onRelayState")
11
13
 
14
+ AsyncFunction("joinLocalHotspot") { (id: String, ssid: String, password: String, gateway: String?, purpose: String, promise: Promise) in
15
+ self.queue.async {
16
+ guard self.localHotspotOwner == nil else {
17
+ promise.reject(LocalMediaError("Previous hotspot session has not been released")); return
18
+ }
19
+ guard let accessPurpose = HotspotAccessPurpose(rawValue: purpose) else {
20
+ promise.reject(LocalMediaError("Invalid hotspot access purpose")); return
21
+ }
22
+ self.localHotspotOwner = id
23
+ self.localHotspot.join(ssid: ssid, passphrase: password, gateway: gateway, purpose: accessPurpose) { result in
24
+ switch result {
25
+ case let .success(address): promise.resolve(address)
26
+ case let .failure(error):
27
+ let failure = HotspotJoinFailure(error)
28
+ promise.reject(Exception(name: "HotspotJoinFailure", description: failure.message, code: failure.code))
29
+ }
30
+ }
31
+ }
32
+ }
33
+
34
+ AsyncFunction("leaveLocalHotspot") { (id: String, promise: Promise) in
35
+ self.queue.async {
36
+ guard self.localHotspotOwner == id else { promise.resolve(nil); return }
37
+ self.localHotspot.leave { result in
38
+ self.queue.async {
39
+ switch result {
40
+ case .success:
41
+ if self.localHotspotOwner == id { self.localHotspotOwner = nil }
42
+ promise.resolve(nil)
43
+ case let .failure(error): promise.reject(error)
44
+ }
45
+ }
46
+ }
47
+ }
48
+ }
49
+
12
50
  AsyncFunction("prepare") { (options: [String: Any], promise: Promise) in
13
51
  guard let id = options["attemptId"] as? String,
14
52
  let ingest = options["ingestUrl"] as? String, let url = URL(string: ingest), url.scheme == "https",
@@ -38,16 +76,23 @@ public final class GlassesMediaRelayModule: Module {
38
76
  AsyncFunction("stop") { (id: String, promise: Promise) in
39
77
  self.queue.async {
40
78
  guard let session = self.session, session.id == id else { promise.resolve(nil); return }
41
- session.stop {
42
- if self.session === session { self.session = nil }
43
- promise.resolve(nil)
79
+ session.stop { result in
80
+ switch result {
81
+ case .success:
82
+ if self.session === session { self.session = nil }
83
+ promise.resolve(nil)
84
+ case let .failure(error): promise.reject(error)
85
+ }
44
86
  }
45
87
  }
46
88
  }
47
89
 
48
90
  OnDestroy {
49
91
  self.queue.async {
50
- self.session?.stop { self.session = nil }
92
+ self.session?.stop { result in
93
+ if case .success = result { self.session = nil }
94
+ }
95
+ self.localHotspot.leave { _ in }
51
96
  }
52
97
  }
53
98
  }
@@ -62,7 +107,7 @@ private final class ManagedRelaySession {
62
107
  private var publisher: PhoneWhipPublisher?
63
108
  private var stopped = false
64
109
  private var stopFinished = false
65
- private var stopReplies: [() -> Void] = []
110
+ private var stopReplies: [(Result<Void, Error>) -> Void] = []
66
111
  private var ready: ((Result<String, Error>) -> Void)?
67
112
 
68
113
  init(id: String, queue: DispatchQueue) {
@@ -116,10 +161,11 @@ private final class ManagedRelaySession {
116
161
  let callback = ready; ready = nil; callback?(result)
117
162
  }
118
163
 
119
- func stop(completion: @escaping () -> Void) {
120
- if stopFinished { completion(); return }
164
+ func stop(completion: @escaping (Result<Void, Error>) -> Void) {
165
+ if stopFinished { completion(.success(())); return }
166
+ let stopping = !stopReplies.isEmpty
121
167
  stopReplies.append(completion)
122
- guard !stopped else { return }
168
+ guard !stopping else { return }
123
169
  stopped = true
124
170
  finish(.failure(LocalMediaError("Relay cancelled")))
125
171
  hotspot.onLost = nil
@@ -131,10 +177,14 @@ private final class ManagedRelaySession {
131
177
  if let publisher {
132
178
  group.enter(); publisher.stop { group.leave() }
133
179
  }
134
- group.enter(); hotspot.leave { group.leave() }
180
+ var cleanupResult: Result<Void, Error> = .success(())
181
+ group.enter(); hotspot.leave { result in
182
+ self.queue.async { cleanupResult = result; group.leave() }
183
+ }
135
184
  group.notify(queue: queue) {
136
- self.receiver = nil; self.publisher = nil; self.stopFinished = true
137
- let replies = self.stopReplies; self.stopReplies.removeAll(); replies.forEach { $0() }
185
+ self.receiver = nil; self.publisher = nil
186
+ if case .success = cleanupResult { self.stopFinished = true }
187
+ let replies = self.stopReplies; self.stopReplies.removeAll(); replies.forEach { $0(cleanupResult) }
138
188
  }
139
189
  }
140
190
  }
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@mentra/glasses-media",
3
- "version": "3.2.1",
3
+ "version": "3.3.0-beta.954",
4
4
  "description": "Shared native glasses media receivers and local network transport",
5
5
  "license": "MIT",
6
6
  "author": "Mentra Community",
7
7
  "homepage": "https://github.com/Mentra-Community/MentraOS",
8
8
  "main": "index.js",
9
+ "types": "index.d.ts",
9
10
  "repository": {
10
11
  "type": "git",
11
12
  "url": "git+https://github.com/Mentra-Community/MentraOS.git",
@@ -14,8 +15,12 @@
14
15
  "publishConfig": {
15
16
  "access": "public"
16
17
  },
18
+ "peerDependencies": {
19
+ "expo-modules-core": "~55.0.25"
20
+ },
17
21
  "files": [
18
22
  "index.js",
23
+ "index.d.ts",
19
24
  "expo-module.config.json",
20
25
  "android/build.gradle",
21
26
  "android/src/main",
@@ -1,20 +0,0 @@
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