@mentra/bluetooth-sdk 3.2.0-dev.200 → 3.2.0-dev.206

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.
Files changed (60) hide show
  1. package/README.md +46 -45
  2. package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +5 -9
  3. package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +63 -0
  4. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +5 -4
  5. package/android/src/main/java/com/mentra/bluetoothsdk/GeneratedChangelogCatalog.kt +1 -1
  6. package/android/src/main/java/com/mentra/bluetoothsdk/GeneratedReleaseMetadata.kt +5 -5
  7. package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +298 -181
  8. package/android/src/main/java/com/mentra/bluetoothsdk/MessageAckPolicy.kt +4 -0
  9. package/android/src/main/java/com/mentra/bluetoothsdk/WifiRequestResolution.kt +246 -0
  10. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/ClassicAudioConnectionTracker.kt +104 -0
  11. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.kt +326 -45
  12. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +2 -0
  13. package/android/src/main/java/com/mentra/bluetoothsdk/status/DeviceStatus.kt +14 -0
  14. package/android/src/main/java/com/mentra/bluetoothsdk/streaming/StreamControllerProbe.kt +14 -0
  15. package/android/src/main/java/com/mentra/bluetoothsdk/streaming/StreamModels.kt +16 -0
  16. package/android/src/main/java/com/mentra/bluetoothsdk/streaming/StreamSessionState.kt +32 -0
  17. package/android/src/test/java/com/mentra/bluetoothsdk/GlassesStatusClassicAudioTest.kt +14 -0
  18. package/android/src/test/java/com/mentra/bluetoothsdk/MessageAckPolicyTest.kt +16 -0
  19. package/android/src/test/java/com/mentra/bluetoothsdk/WifiRequestResolutionTest.kt +271 -0
  20. package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/ClassicAudioConnectionTrackerTest.kt +131 -0
  21. package/android/src/test/java/com/mentra/bluetoothsdk/streaming/StreamControllerProbeTest.kt +24 -0
  22. package/android/src/test/java/com/mentra/bluetoothsdk/streaming/StreamSessionStateTest.kt +43 -0
  23. package/build/BluetoothSdk.types.d.ts +68 -2
  24. package/build/BluetoothSdk.types.d.ts.map +1 -1
  25. package/build/BluetoothSdk.types.js.map +1 -1
  26. package/build/_private/BluetoothSdkModule.d.ts +3 -4
  27. package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
  28. package/build/_private/BluetoothSdkModule.js.map +1 -1
  29. package/build/generated/changelogCatalog.d.ts +1 -1
  30. package/build/generated/changelogCatalog.js +1 -1
  31. package/build/generated/changelogCatalog.js.map +1 -1
  32. package/build/generated/releaseMetadata.js +5 -5
  33. package/build/generated/releaseMetadata.js.map +1 -1
  34. package/build/index.d.ts +1 -1
  35. package/build/index.d.ts.map +1 -1
  36. package/build/index.js +3 -0
  37. package/build/index.js.map +1 -1
  38. package/ios/BluetoothSdkModule.swift +8 -13
  39. package/ios/Source/BluetoothSdkDefaults.swift +1 -1
  40. package/ios/Source/Bridge.swift +63 -0
  41. package/ios/Source/DeviceManager.swift +6 -3
  42. package/ios/Source/GeneratedChangelogCatalog.swift +1 -1
  43. package/ios/Source/GeneratedReleaseMetadata.swift +5 -5
  44. package/ios/Source/MentraBluetoothSDK.swift +372 -220
  45. package/ios/Source/WifiRequestResolution.swift +251 -0
  46. package/ios/Source/sgcs/MentraLive.swift +216 -70
  47. package/ios/Source/sgcs/SGCManager.swift +13 -1
  48. package/ios/Source/status/DeviceStatus.swift +28 -0
  49. package/ios/Source/streaming/StreamControllerProbe.swift +18 -0
  50. package/ios/Source/streaming/StreamModels.swift +12 -0
  51. package/ios/Source/streaming/StreamSessionState.swift +29 -0
  52. package/ios/Tests/StreamControllerProbeTests.swift +27 -0
  53. package/ios/Tests/StreamSessionStateTests.swift +38 -0
  54. package/ios/Tests/WifiRequestResolutionTests.swift +320 -0
  55. package/package.json +1 -1
  56. package/src/BluetoothSdk.types.ts +82 -2
  57. package/src/_private/BluetoothSdkModule.ts +4 -4
  58. package/src/generated/changelogCatalog.ts +1 -1
  59. package/src/generated/releaseMetadata.ts +5 -5
  60. package/src/index.ts +11 -0
@@ -0,0 +1,251 @@
1
+ import CoreFoundation
2
+ import Foundation
3
+
4
+ private func isWifiProtocolV1(_ raw: Any?) -> Bool {
5
+ guard let number = raw as? NSNumber,
6
+ CFGetTypeID(number) != CFBooleanGetTypeID()
7
+ else { return false }
8
+ return number.doubleValue == 1
9
+ }
10
+
11
+ /// Validate raw fields before convenience conversions erase malformed or partial tuples.
12
+ func wifiResponseEnvelopeIsValid(_ values: [String: Any], allowLegacy: Bool) -> Bool {
13
+ for key in ["connected", "dispatched"] where values[key] != nil {
14
+ guard let number = values[key] as? NSNumber, CFGetTypeID(number) == CFBooleanGetTypeID()
15
+ else { return false }
16
+ }
17
+ let hasTuple = ["protocol_version", "requestId", "sid"].contains { values[$0] != nil }
18
+ if !hasTuple { return allowLegacy && values["outcome"] == nil }
19
+ guard let requestId = values["requestId"] as? String,
20
+ let sid = values["sid"] as? String
21
+ else { return false }
22
+ return isWifiProtocolV1(values["protocol_version"]) && wifiSsidIsValid(requestId) && wifiSsidIsValid(sid)
23
+ && values["dispatched"] == nil
24
+ }
25
+
26
+ func wifiSsidIsValid(_ ssid: String) -> Bool {
27
+ !ssid.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
28
+ }
29
+
30
+ let wifiCapabilityNegotiationTimeoutCode = "capability_negotiation_timeout"
31
+
32
+ enum WifiProtocolCapability: Equatable {
33
+ case unknown
34
+ case supported(version: Int)
35
+ case unsupported
36
+ case legacy
37
+ }
38
+
39
+ enum WifiRequestMode: Equatable {
40
+ case discovering
41
+ case modern
42
+ case legacy
43
+ case unsupported
44
+ }
45
+
46
+ final class WifiSessionCapabilities {
47
+ private(set) var sessionId = ""
48
+ private(set) var epoch: UInt64 = 0
49
+ private(set) var forgetResult: WifiProtocolCapability = .unknown
50
+ private(set) var savedNetworks: WifiProtocolCapability = .unknown
51
+
52
+ func reset(sessionId: String = "") {
53
+ epoch &+= 1
54
+ self.sessionId = sessionId
55
+ forgetResult = .unknown
56
+ savedNetworks = .unknown
57
+ }
58
+
59
+ func applyVersionInfo1(_ values: [String: Any]) {
60
+ if let sid = values["sid"] as? String, !sid.isEmpty {
61
+ sessionId = sid
62
+ }
63
+ forgetResult = capability(values["wifiForgetResultVersion"])
64
+ savedNetworks = capability(values["savedWifiNetworksVersion"])
65
+ }
66
+
67
+ func forgetMode() -> WifiRequestMode {
68
+ Self.requestMode(forgetResult)
69
+ }
70
+
71
+ func savedNetworksMode() -> WifiRequestMode {
72
+ Self.requestMode(savedNetworks)
73
+ }
74
+
75
+ private func capability(_ raw: Any?) -> WifiProtocolCapability {
76
+ guard let raw else { return .legacy }
77
+ return isWifiProtocolV1(raw) && !sessionId.isEmpty ? .supported(version: 1) : .unsupported
78
+ }
79
+
80
+ private static func requestMode(_ capability: WifiProtocolCapability) -> WifiRequestMode {
81
+ switch capability {
82
+ case .unknown: .discovering
83
+ case .supported: .modern
84
+ case .legacy: .legacy
85
+ case .unsupported: .unsupported
86
+ }
87
+ }
88
+ }
89
+
90
+ public enum WifiForgetOutcome: String {
91
+ case confirmed
92
+ case dispatched
93
+ case notFound = "not_found"
94
+ case unsupported
95
+ case failed
96
+ case legacyUnverified = "legacy_unverified"
97
+ }
98
+
99
+ func normalizeWifiForgetResultEvent(
100
+ requestId: String,
101
+ sid: String,
102
+ ssid: String,
103
+ protocolVersion: Int,
104
+ outcome: String,
105
+ legacyDispatched: Bool?,
106
+ connected: Bool?,
107
+ currentSsid: String,
108
+ localIp: String,
109
+ error: String?
110
+ ) -> [String: Any]? {
111
+ guard wifiSsidIsValid(ssid) else { return nil }
112
+ if !requestId.isEmpty, !sid.isEmpty,
113
+ protocolVersion == 1,
114
+ let modernOutcome = WifiForgetOutcome(rawValue: outcome),
115
+ modernOutcome != .legacyUnverified
116
+ {
117
+ var body: [String: Any] = [
118
+ "mode": "modern",
119
+ "requestId": requestId,
120
+ "sid": sid,
121
+ "ssid": ssid,
122
+ "protocolVersion": protocolVersion,
123
+ "outcome": modernOutcome.rawValue,
124
+ ]
125
+ if let connected { body["connected"] = connected }
126
+ if !currentSsid.isEmpty { body["currentSsid"] = currentSsid }
127
+ if !localIp.isEmpty { body["localIp"] = localIp }
128
+ if let error { body["error"] = error }
129
+ return body
130
+ }
131
+ if requestId.isEmpty, sid.isEmpty, protocolVersion == 0, outcome.isEmpty, let legacyDispatched {
132
+ var body: [String: Any] = [
133
+ "mode": "legacy",
134
+ "ssid": ssid,
135
+ "dispatched": legacyDispatched,
136
+ ]
137
+ if let connected { body["connected"] = connected }
138
+ if !currentSsid.isEmpty { body["currentSsid"] = currentSsid }
139
+ if !localIp.isEmpty { body["localIp"] = localIp }
140
+ if let error { body["error"] = error }
141
+ return body
142
+ }
143
+ return nil
144
+ }
145
+
146
+ public struct WifiForgetResult {
147
+ public let ssid: String
148
+ public let outcome: WifiForgetOutcome
149
+ public let connected: Bool?
150
+ public let currentSsid: String?
151
+ public let localIp: String?
152
+ public let error: String?
153
+
154
+ public var values: [String: Any] {
155
+ var result: [String: Any] = [
156
+ "ssid": ssid,
157
+ "outcome": outcome.rawValue,
158
+ ]
159
+ if let connected { result["connected"] = connected }
160
+ if let currentSsid { result["currentSsid"] = currentSsid }
161
+ if let localIp { result["localIp"] = localIp }
162
+ if let error { result["error"] = error }
163
+ return result
164
+ }
165
+ }
166
+
167
+ public enum SavedWifiNetworksOutcome: String {
168
+ case confirmed
169
+ case unsupported
170
+ case failed
171
+ }
172
+
173
+ public struct SavedWifiNetworksResult {
174
+ public let outcome: SavedWifiNetworksOutcome
175
+ public let networks: [String]
176
+ public let error: String?
177
+
178
+ public var values: [String: Any] {
179
+ var result: [String: Any] = [
180
+ "outcome": outcome.rawValue,
181
+ "networks": networks,
182
+ ]
183
+ if let error { result["error"] = error }
184
+ return result
185
+ }
186
+ }
187
+
188
+ func parseWifiForgetResult(
189
+ expectedRequestId: String,
190
+ expectedSid: String,
191
+ expectedSsid: String,
192
+ capabilityVersion: Int,
193
+ data: [String: Any]
194
+ ) -> WifiForgetResult? {
195
+ guard capabilityVersion == 1, !expectedRequestId.isEmpty, !expectedSid.isEmpty,
196
+ data["requestId"] as? String == expectedRequestId,
197
+ data["sid"] as? String == expectedSid,
198
+ data["ssid"] as? String == expectedSsid,
199
+ isWifiProtocolV1(data["protocolVersion"]),
200
+ let rawOutcome = data["outcome"] as? String,
201
+ let outcome = WifiForgetOutcome(rawValue: rawOutcome),
202
+ outcome != .legacyUnverified
203
+ else { return nil }
204
+ return WifiForgetResult(
205
+ ssid: expectedSsid,
206
+ outcome: outcome,
207
+ connected: data["connected"] as? Bool,
208
+ currentSsid: data["currentSsid"] as? String,
209
+ localIp: data["localIp"] as? String,
210
+ error: (data["error"] as? String).flatMap { $0.isEmpty ? nil : $0 }
211
+ )
212
+ }
213
+
214
+ func parseSavedWifiNetworks(
215
+ expectedRequestId: String,
216
+ expectedSid: String,
217
+ capabilityVersion: Int,
218
+ data: [String: Any]
219
+ ) -> SavedWifiNetworksResult? {
220
+ guard capabilityVersion == 1, !expectedRequestId.isEmpty, !expectedSid.isEmpty,
221
+ data["requestId"] as? String == expectedRequestId,
222
+ data["sid"] as? String == expectedSid,
223
+ isWifiProtocolV1(data["protocolVersion"]),
224
+ let rawOutcome = data["outcome"] as? String,
225
+ let outcome = SavedWifiNetworksOutcome(rawValue: rawOutcome)
226
+ else { return nil }
227
+ guard let rawNetworks = data["networks"] as? [String], outcome == .confirmed || rawNetworks.isEmpty else { return nil }
228
+ var seen = Set<String>()
229
+ let networks = rawNetworks.filter { network in
230
+ !network.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
231
+ && seen.insert(network).inserted
232
+ }
233
+ return SavedWifiNetworksResult(
234
+ outcome: outcome,
235
+ networks: networks,
236
+ error: (data["error"] as? String).flatMap { $0.isEmpty ? nil : $0 }
237
+ )
238
+ }
239
+
240
+ func legacyWifiForgetResult(
241
+ ssid: String
242
+ ) -> WifiForgetResult {
243
+ return WifiForgetResult(
244
+ ssid: ssid,
245
+ outcome: .legacyUnverified,
246
+ connected: nil,
247
+ currentSsid: nil,
248
+ localIp: nil,
249
+ error: nil
250
+ )
251
+ }