@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,38 @@
1
+ @testable import MentraBluetoothSDK
2
+ import XCTest
3
+
4
+ final class StreamSessionStateTests: XCTestCase {
5
+ func testReadinessRequiresKnownProtocolAndProcessIdentity() {
6
+ let state = StreamSessionState()
7
+ state.ready(sessionId: "asg", controlVersion: nil)
8
+ XCTAssertFalse(state.supported)
9
+ state.ready(sessionId: "asg", controlVersion: 2)
10
+ XCTAssertFalse(state.supported)
11
+ state.ready(sessionId: "", controlVersion: 1)
12
+ XCTAssertFalse(state.supported)
13
+ state.ready(sessionId: "asg", controlVersion: 1)
14
+ XCTAssertTrue(state.supported)
15
+ }
16
+
17
+ func testReconnectionReconcilesTerminalStateWithoutHeartbeatTimeouts() {
18
+ let state = StreamSessionState()
19
+ state.ready(sessionId: "asg", controlVersion: 1)
20
+ XCTAssertTrue(state.accept(["sid": "asg", "revision": 1, "streamId": "stream", "terminal": false]))
21
+ state.ready(sessionId: "asg", controlVersion: 1)
22
+ XCTAssertEqual(state.currentStreamId, "stream")
23
+ XCTAssertTrue(state.accept(["sid": "asg", "revision": 3, "streamId": "stream", "terminal": true]))
24
+ XCTAssertNil(state.currentStreamId)
25
+ XCTAssertFalse(state.accept(["sid": "asg", "revision": 2, "streamId": "stream", "terminal": false]))
26
+ XCTAssertNil(state.currentStreamId)
27
+ }
28
+
29
+ func testProcessRestartRejectsOldStatusAndAcceptsNewStoppedSnapshot() {
30
+ let state = StreamSessionState()
31
+ state.ready(sessionId: "old", controlVersion: 1)
32
+ XCTAssertTrue(state.accept(["sid": "old", "revision": 8, "streamId": "stream", "terminal": false]))
33
+ state.ready(sessionId: "new", controlVersion: 1)
34
+ XCTAssertFalse(state.accept(["sid": "old", "revision": 9, "streamId": "stream", "terminal": false]))
35
+ XCTAssertTrue(state.accept(["sid": "new", "revision": 0, "terminal": true]))
36
+ XCTAssertNil(state.currentStreamId)
37
+ }
38
+ }
@@ -0,0 +1,320 @@
1
+ @testable import MentraBluetoothSDK
2
+ import XCTest
3
+
4
+ final class WifiRequestResolutionTests: XCTestCase {
5
+ @MainActor
6
+ func testUnsupportedControllerDoesNotClaimWifiDispatch() {
7
+ let keys = ["fullyBooted", "connected", "connectionState", "micEnabled", "voiceActivityDetectionEnabled", "bluetoothClassicConnected"]
8
+ let snapshot = keys.compactMap { key in DeviceStore.shared.get("glasses", key).map { (key, $0) } }
9
+ defer { for (key, value) in snapshot {
10
+ DeviceStore.shared.apply("glasses", key, value)
11
+ } }
12
+ let controller: SGCManager = Simulated()
13
+ XCTAssertFalse(controller.forgetWifiNetwork("AP", requestId: nil, sid: nil))
14
+ XCTAssertFalse(controller.forgetWifiNetwork("AP", requestId: "request", sid: "sid"))
15
+ XCTAssertFalse(controller.requestSavedWifiNetworks(requestId: "request", sid: "sid"))
16
+ }
17
+
18
+ func testFutureFractionalAndMalformedCapabilitiesAreUnsupportedNotLegacy() {
19
+ for version in [0, 2, -1, 1.5, "1", true, NSNull()] as [Any] {
20
+ let capabilities = WifiSessionCapabilities()
21
+ capabilities.reset(sessionId: "sid")
22
+ capabilities.applyVersionInfo1(["wifiForgetResultVersion": version, "savedWifiNetworksVersion": version])
23
+ XCTAssertEqual(capabilities.forgetMode(), .unsupported)
24
+ XCTAssertEqual(capabilities.savedNetworksMode(), .unsupported)
25
+ }
26
+ let missingSession = WifiSessionCapabilities()
27
+ missingSession.applyVersionInfo1(["wifiForgetResultVersion": 1])
28
+ XCTAssertEqual(missingSession.forgetMode(), .unsupported)
29
+ }
30
+
31
+ func testRawResponseRejectsPartialTuplesAndCoercionBeforeNormalization() {
32
+ let tuple: [String: Any] = ["protocol_version": 1, "requestId": "request", "sid": "sid"]
33
+ XCTAssertTrue(wifiResponseEnvelopeIsValid(tuple, allowLegacy: false))
34
+ for key in tuple.keys {
35
+ XCTAssertFalse(wifiResponseEnvelopeIsValid(tuple.filter { $0.key != key }, allowLegacy: true))
36
+ }
37
+ for version in [0, 2, 1.5, "1", true, NSNull()] as [Any] {
38
+ var malformed = tuple
39
+ malformed["protocol_version"] = version
40
+ XCTAssertFalse(wifiResponseEnvelopeIsValid(malformed, allowLegacy: true))
41
+ }
42
+ XCTAssertFalse(wifiResponseEnvelopeIsValid(tuple.merging(["connected": 0]) { _, new in new }, allowLegacy: true))
43
+ XCTAssertFalse(wifiResponseEnvelopeIsValid(tuple.merging(["dispatched": true]) { _, new in new }, allowLegacy: true))
44
+ XCTAssertTrue(wifiResponseEnvelopeIsValid(["dispatched": true], allowLegacy: true))
45
+ XCTAssertFalse(wifiResponseEnvelopeIsValid(["requestId": "", "dispatched": true], allowLegacy: true))
46
+ XCTAssertFalse(wifiResponseEnvelopeIsValid([:], allowLegacy: false))
47
+ }
48
+
49
+ func testSemanticResultsOmitTransportMetadata() throws {
50
+ let data: [String: Any] = ["requestId": "request", "sid": "sid", "protocolVersion": 1,
51
+ "ssid": "AP", "outcome": "confirmed", "networks": ["AP"]]
52
+ let forbidden: Set = ["mode", "capabilityVersion", "requestId", "sid", "protocolVersion"]
53
+ let forget = try XCTUnwrap(parseWifiForgetResult(expectedRequestId: "request", expectedSid: "sid", expectedSsid: "AP", capabilityVersion: 1, data: data))
54
+ let saved = try XCTUnwrap(parseSavedWifiNetworks(expectedRequestId: "request", expectedSid: "sid", capabilityVersion: 1, data: data))
55
+ XCTAssertTrue(Set(forget.values.keys).isDisjoint(with: forbidden))
56
+ XCTAssertTrue(Set(saved.values.keys).isDisjoint(with: forbidden))
57
+ for version in [1.5, true] as [Any] {
58
+ var malformed = data
59
+ malformed["protocolVersion"] = version
60
+ XCTAssertNil(parseWifiForgetResult(expectedRequestId: "request", expectedSid: "sid", expectedSsid: "AP", capabilityVersion: 1, data: malformed))
61
+ XCTAssertNil(parseSavedWifiNetworks(expectedRequestId: "request", expectedSid: "sid", capabilityVersion: 1, data: malformed))
62
+ }
63
+ }
64
+
65
+ @MainActor
66
+ func testPendingResponseCancellationResumesExactlyOnceWithoutTimeout() async {
67
+ let pending = PendingResponse<Int>(operation: "WiFi capability negotiation")
68
+ let waiter = Task { @MainActor in
69
+ try await pending.wait(timeoutMs: nil)
70
+ }
71
+ await Task.yield()
72
+
73
+ waiter.cancel()
74
+
75
+ do {
76
+ _ = try await waiter.value
77
+ XCTFail("Expected cancellation")
78
+ } catch let error as BluetoothSdkError {
79
+ XCTAssertEqual(error.code, "request_cancelled")
80
+ } catch {
81
+ XCTFail("Unexpected error: \(error)")
82
+ }
83
+ pending.resolve(42)
84
+ do {
85
+ _ = try await pending.wait(timeoutMs: nil)
86
+ XCTFail("Late resolve must not replace the cancellation result")
87
+ } catch let error as BluetoothSdkError {
88
+ XCTAssertEqual(error.code, "request_cancelled")
89
+ } catch {
90
+ XCTFail("Unexpected error: \(error)")
91
+ }
92
+ }
93
+
94
+ func testCapabilitiesAreUnknownPerSessionUntilVersionInfoFinalizesThem() {
95
+ let capabilities = WifiSessionCapabilities()
96
+ capabilities.reset(sessionId: "sid-1")
97
+
98
+ XCTAssertEqual(capabilities.forgetResult, .unknown)
99
+ XCTAssertEqual(capabilities.savedNetworks, .unknown)
100
+ capabilities.applyVersionInfo1(["sid": "sid-1", "wifiForgetResultVersion": 1])
101
+
102
+ XCTAssertEqual(capabilities.forgetResult, .supported(version: 1))
103
+ XCTAssertEqual(capabilities.savedNetworks, .legacy)
104
+ XCTAssertEqual(capabilities.forgetMode(), .modern)
105
+ XCTAssertEqual(capabilities.savedNetworksMode(), .legacy)
106
+ }
107
+
108
+ func testNewSessionResetsCapabilitiesAndAdvancesEpoch() {
109
+ let capabilities = WifiSessionCapabilities()
110
+ capabilities.reset(sessionId: "sid-1")
111
+ capabilities.applyVersionInfo1([
112
+ "wifiForgetResultVersion": 1,
113
+ "savedWifiNetworksVersion": 1,
114
+ ])
115
+ let oldEpoch = capabilities.epoch
116
+
117
+ capabilities.reset(sessionId: "sid-2")
118
+
119
+ XCTAssertGreaterThan(capabilities.epoch, oldEpoch)
120
+ XCTAssertEqual(capabilities.sessionId, "sid-2")
121
+ XCTAssertEqual(capabilities.forgetResult, .unknown)
122
+ XCTAssertEqual(capabilities.savedNetworks, .unknown)
123
+ }
124
+
125
+ func testSsidValidationRejectsBlankInputWithoutChangingIdentity() {
126
+ XCTAssertFalse(wifiSsidIsValid(" "))
127
+ XCTAssertTrue(wifiSsidIsValid(" Field AP "))
128
+ }
129
+
130
+ func testForgetResultRequiresExactIdSessionSsidAndProtocol() {
131
+ let exact: [String: Any] = [
132
+ "requestId": "forget-1",
133
+ "sid": "sid-1",
134
+ "ssid": " Field AP ",
135
+ "protocolVersion": 1,
136
+ "outcome": "dispatched",
137
+ "connected": false,
138
+ ]
139
+
140
+ XCTAssertNil(parseWifiForgetResult(
141
+ expectedRequestId: "other", expectedSid: "sid-1", expectedSsid: " Field AP ",
142
+ capabilityVersion: 1, data: exact
143
+ ))
144
+ XCTAssertNil(parseWifiForgetResult(
145
+ expectedRequestId: "forget-1", expectedSid: "sid-1", expectedSsid: " Field AP ",
146
+ capabilityVersion: 1, data: exact.filter { $0.key != "requestId" }
147
+ ))
148
+ XCTAssertNil(parseWifiForgetResult(
149
+ expectedRequestId: "forget-1", expectedSid: "other", expectedSsid: " Field AP ",
150
+ capabilityVersion: 1, data: exact
151
+ ))
152
+ XCTAssertNil(parseWifiForgetResult(
153
+ expectedRequestId: "forget-1", expectedSid: "sid-1", expectedSsid: "Field AP",
154
+ capabilityVersion: 1, data: exact
155
+ ))
156
+ XCTAssertNil(parseWifiForgetResult(
157
+ expectedRequestId: "forget-1", expectedSid: "sid-1", expectedSsid: " Field AP ",
158
+ capabilityVersion: 2, data: exact
159
+ ))
160
+ XCTAssertEqual(
161
+ parseWifiForgetResult(
162
+ expectedRequestId: "forget-1", expectedSid: "sid-1", expectedSsid: " Field AP ",
163
+ capabilityVersion: 1, data: exact
164
+ )?.outcome,
165
+ .dispatched
166
+ )
167
+ }
168
+
169
+ func testForgetParserPreservesEveryHonestTerminalOutcome() {
170
+ for outcome in ["confirmed", "dispatched", "not_found", "unsupported", "failed"] {
171
+ let parsed = parseWifiForgetResult(
172
+ expectedRequestId: "forget-1",
173
+ expectedSid: "sid-1",
174
+ expectedSsid: "AP",
175
+ capabilityVersion: 1,
176
+ data: [
177
+ "requestId": "forget-1",
178
+ "sid": "sid-1",
179
+ "ssid": "AP",
180
+ "protocolVersion": 1,
181
+ "outcome": outcome,
182
+ ]
183
+ )
184
+ XCTAssertEqual(parsed?.outcome.rawValue, outcome)
185
+ }
186
+ }
187
+
188
+ func testMissingConnectivitySnapshotRemainsUnknown() {
189
+ let parsed = parseWifiForgetResult(
190
+ expectedRequestId: "forget-1",
191
+ expectedSid: "sid-1",
192
+ expectedSsid: "AP",
193
+ capabilityVersion: 1,
194
+ data: [
195
+ "requestId": "forget-1",
196
+ "sid": "sid-1",
197
+ "ssid": "AP",
198
+ "protocolVersion": 1,
199
+ "outcome": "dispatched",
200
+ ]
201
+ )
202
+
203
+ XCTAssertNil(parsed?.connected)
204
+ XCTAssertNil(parsed?.values["connected"])
205
+ }
206
+
207
+ func testLegacyForgetIsExplicitlyUnverified() {
208
+ let result = legacyWifiForgetResult(
209
+ ssid: "AP"
210
+ )
211
+
212
+ XCTAssertTrue(Set(result.values.keys).isDisjoint(with: ["mode", "capabilityVersion", "requestId", "sid"]))
213
+ XCTAssertEqual(result.outcome, .legacyUnverified)
214
+ XCTAssertNil(result.connected)
215
+ XCTAssertNil(result.currentSsid)
216
+ XCTAssertNil(result.localIp)
217
+ }
218
+
219
+ func testSavedListPreservesIdentityAndRequiresCorrelationTuple() {
220
+ let exact: [String: Any] = [
221
+ "requestId": "saved-1",
222
+ "sid": "sid-1",
223
+ "protocolVersion": 1,
224
+ "outcome": "confirmed",
225
+ "networks": [" Field AP ", "", "Field AP", " Field AP "],
226
+ ]
227
+
228
+ XCTAssertNil(parseSavedWifiNetworks(
229
+ expectedRequestId: "other", expectedSid: "sid-1", capabilityVersion: 1, data: exact
230
+ ))
231
+ XCTAssertNil(parseSavedWifiNetworks(
232
+ expectedRequestId: "saved-1", expectedSid: "sid-1", capabilityVersion: 1,
233
+ data: exact.filter { $0.key != "requestId" }
234
+ ))
235
+ XCTAssertNil(parseSavedWifiNetworks(
236
+ expectedRequestId: "saved-1", expectedSid: "other", capabilityVersion: 1, data: exact
237
+ ))
238
+ XCTAssertNil(parseSavedWifiNetworks(
239
+ expectedRequestId: "saved-1", expectedSid: "sid-1", capabilityVersion: 2, data: exact
240
+ ))
241
+ XCTAssertEqual(
242
+ parseSavedWifiNetworks(
243
+ expectedRequestId: "saved-1", expectedSid: "sid-1", capabilityVersion: 1, data: exact
244
+ )?.networks,
245
+ [" Field AP ", "Field AP"]
246
+ )
247
+
248
+ var failure = exact
249
+ failure["outcome"] = "failed"
250
+ failure["error"] = "backend_failed"
251
+ failure["networks"] = [String]()
252
+ let parsedFailure = parseSavedWifiNetworks(
253
+ expectedRequestId: "saved-1",
254
+ expectedSid: "sid-1",
255
+ capabilityVersion: 1,
256
+ data: failure
257
+ )
258
+ XCTAssertEqual(parsedFailure?.outcome, .failed)
259
+ XCTAssertEqual(parsedFailure?.error, "backend_failed")
260
+ }
261
+
262
+
263
+ func testRawForgetEventPreservesModernAndLegacyWireTruth() {
264
+ let modern = normalizeWifiForgetResultEvent(
265
+ requestId: "forget-1",
266
+ sid: "sid-1",
267
+ ssid: "AP",
268
+ protocolVersion: 1,
269
+ outcome: "dispatched",
270
+ legacyDispatched: nil,
271
+ connected: false,
272
+ currentSsid: "",
273
+ localIp: "",
274
+ error: nil
275
+ )
276
+ let legacy = normalizeWifiForgetResultEvent(
277
+ requestId: "",
278
+ sid: "",
279
+ ssid: "AP",
280
+ protocolVersion: 0,
281
+ outcome: "",
282
+ legacyDispatched: true,
283
+ connected: false,
284
+ currentSsid: "",
285
+ localIp: "",
286
+ error: nil
287
+ )
288
+ let withoutSnapshot = normalizeWifiForgetResultEvent(
289
+ requestId: "forget-unknown",
290
+ sid: "sid-1",
291
+ ssid: "AP",
292
+ protocolVersion: 1,
293
+ outcome: "dispatched",
294
+ legacyDispatched: nil,
295
+ connected: nil,
296
+ currentSsid: "",
297
+ localIp: "",
298
+ error: nil
299
+ )
300
+
301
+ XCTAssertEqual(modern?["mode"] as? String, "modern")
302
+ XCTAssertEqual(modern?["outcome"] as? String, "dispatched")
303
+ XCTAssertEqual(legacy?["mode"] as? String, "legacy")
304
+ XCTAssertEqual(legacy?["dispatched"] as? Bool, true)
305
+ XCTAssertNil(legacy?["sid"])
306
+ XCTAssertNil(withoutSnapshot?["connected"])
307
+ XCTAssertNil(normalizeWifiForgetResultEvent(
308
+ requestId: "bad",
309
+ sid: "",
310
+ ssid: "AP",
311
+ protocolVersion: 0,
312
+ outcome: "",
313
+ legacyDispatched: nil,
314
+ connected: false,
315
+ currentSsid: "",
316
+ localIp: "",
317
+ error: nil
318
+ ))
319
+ }
320
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mentra/bluetooth-sdk",
3
- "version": "3.2.0-dev.200",
3
+ "version": "3.2.0-dev.206",
4
4
  "description": "SDK for communicating with smart glasses",
5
5
  "main": "build/index.js",
6
6
  "react-native": "src/index.ts",
@@ -128,6 +128,68 @@ export type WifiStatusChangeEvent = WifiStatus & {
128
128
  error?: string
129
129
  }
130
130
 
131
+ export type WifiForgetOutcome =
132
+ | "confirmed"
133
+ | "dispatched"
134
+ | "not_found"
135
+ | "unsupported"
136
+ | "failed"
137
+ | "legacy_unverified"
138
+
139
+ export type WifiForgetResult = {
140
+ ssid: string
141
+ outcome: WifiForgetOutcome
142
+ connected?: boolean
143
+ currentSsid?: string
144
+ localIp?: string
145
+ error?: string
146
+ }
147
+
148
+ export type ModernWifiForgetResultEvent = {
149
+ type: "wifi_forget_result"
150
+ mode: "modern"
151
+ requestId: string
152
+ sid: string
153
+ ssid: string
154
+ protocolVersion: number
155
+ outcome: Exclude<WifiForgetOutcome, "legacy_unverified">
156
+ connected?: boolean
157
+ currentSsid?: string
158
+ localIp?: string
159
+ error?: string
160
+ }
161
+
162
+ export type LegacyWifiForgetResultEvent = {
163
+ type: "wifi_forget_result"
164
+ mode: "legacy"
165
+ ssid: string
166
+ dispatched: boolean
167
+ connected?: boolean
168
+ currentSsid?: string
169
+ localIp?: string
170
+ error?: string
171
+ }
172
+
173
+ export type WifiForgetResultEvent = ModernWifiForgetResultEvent | LegacyWifiForgetResultEvent
174
+
175
+ export type SavedWifiNetworksOutcome = "confirmed" | "unsupported" | "failed"
176
+
177
+ export type SavedWifiNetworksResult = {
178
+ outcome: SavedWifiNetworksOutcome
179
+ networks: string[]
180
+ error?: string
181
+ }
182
+
183
+ export type SavedWifiNetworksEvent = {
184
+ type: "saved_wifi_networks"
185
+ requestId: string
186
+ sid: string
187
+ protocolVersion: number
188
+ outcome: SavedWifiNetworksOutcome
189
+ networks: string[]
190
+ error?: string
191
+ }
192
+
131
193
  export type HotspotStatus = {state: "disabled"} | {state: "enabled"; ssid: string; password: string; localIp: string}
132
194
 
133
195
  export type EnabledHotspotStatus = Extract<HotspotStatus, {state: "enabled"}>
@@ -147,6 +209,8 @@ export type HotspotErrorEvent = {
147
209
  }
148
210
 
149
211
  export type VersionInfoResult = {
212
+ versionInfoType?: string
213
+ sid?: string
150
214
  androidVersion: string
151
215
  firmwareVersion: string
152
216
  besFirmwareVersion: string
@@ -169,6 +233,10 @@ export type VersionInfoResult = {
169
233
  packageName?: string
170
234
  /** Phone-served hotspot OTA protocol version; 0 means unsupported/legacy glasses. */
171
235
  hotspotOtaVersion: number
236
+ /** Session-correlated WiFi forget result protocol; absent/0 means unsupported. */
237
+ wifiForgetResultVersion?: number
238
+ /** Session-correlated saved-network listing protocol; absent/0 means unsupported. */
239
+ savedWifiNetworksVersion?: number
172
240
  }
173
241
 
174
242
  export type VersionInfoEvent = VersionInfoResult & {
@@ -796,6 +864,11 @@ export type StreamLiveStats = {
796
864
  type StreamStatusCommon = {
797
865
  type: "stream_status"
798
866
  streamId?: string
867
+ /** ASG process identity and monotonic revision for reconnect reconciliation. */
868
+ sid?: string
869
+ revision?: number
870
+ terminal?: boolean
871
+ errorDetails?: string
799
872
  timestamp?: number
800
873
  resolvedConfig?: StreamResolvedConfig
801
874
  stats?: StreamLiveStats
@@ -830,7 +903,7 @@ export type StreamStatusEvent =
830
903
  })
831
904
  | (StreamStatusCommon & {
832
905
  kind: "snapshot"
833
- status: "streaming" | "reconnecting" | "stopped"
906
+ status: StreamStatusState
834
907
  streaming: boolean
835
908
  reconnecting: boolean
836
909
  attempt?: number
@@ -930,6 +1003,8 @@ export type BluetoothSdkModuleEvents = {
930
1003
  phone_notification_dismissed: (event: PhoneNotificationDismissedEvent) => void
931
1004
  wifi_status_change: (event: WifiStatusChangeEvent) => void
932
1005
  wifi_scan_result: (event: WifiScanResultEvent) => void
1006
+ wifi_forget_result: (event: WifiForgetResultEvent) => void
1007
+ saved_wifi_networks: (event: SavedWifiNetworksEvent) => void
933
1008
  hotspot_status_change: (event: HotspotStatusChangeEvent) => void
934
1009
  hotspot_error: (event: HotspotErrorEvent) => void
935
1010
  photo_response: (event: PhotoResponseEvent) => void
@@ -1090,6 +1165,8 @@ export type BluetoothSdkEventMap = {
1090
1165
  local_transcription: LocalTranscriptionEvent
1091
1166
  wifi_status_change: WifiStatusChangeEvent
1092
1167
  wifi_scan_result: WifiScanResultEvent
1168
+ wifi_forget_result: WifiForgetResultEvent
1169
+ saved_wifi_networks: SavedWifiNetworksEvent
1093
1170
  hotspot_status_change: HotspotStatusChangeEvent
1094
1171
  hotspot_error: HotspotErrorEvent
1095
1172
  photo_response: PhotoResponseEvent
@@ -1182,8 +1259,11 @@ export interface BluetoothSdkPublicModule {
1182
1259
  ping(): Promise<void>
1183
1260
 
1184
1261
  requestWifiScan(): Promise<WifiSearchResult[]>
1262
+ /** List exact WiFi SSIDs from glasses that support reliable saved-network enumeration. */
1263
+ getSavedWifiNetworks(): Promise<SavedWifiNetworksResult>
1185
1264
  sendWifiCredentials(ssid: string, password: string): Promise<WifiStatusChangeEvent>
1186
- forgetWifiNetwork(ssid: string): Promise<WifiStatusChangeEvent>
1265
+ /** Forget a saved network and return its correlated semantic outcome. */
1266
+ forgetWifiNetwork(ssid: string): Promise<WifiForgetResult>
1187
1267
  setHotspotState(enabled: boolean): Promise<HotspotStatusChangeEvent>
1188
1268
  /** Set the glasses clock from the phone after an OTA clock-skew failure. */
1189
1269
  setSystemTime(timestampMs: number): Promise<void>
@@ -46,7 +46,6 @@ import {
46
46
  ScanModelOptions,
47
47
  ScanOptions,
48
48
  SettingsAckSuccessEvent,
49
- StreamKeepAliveRequest,
50
49
  StreamStartRequest,
51
50
  StreamStatusEvent,
52
51
  VideoRecordingStartedStatusEvent,
@@ -55,6 +54,8 @@ import {
55
54
  VideoRecordingStatusEvent,
56
55
  VersionInfoResult,
57
56
  WarmUpCameraParams,
57
+ SavedWifiNetworksResult,
58
+ WifiForgetResult,
58
59
  WifiSearchResult,
59
60
  WifiStatusChangeEvent,
60
61
  } from "../BluetoothSdk.types"
@@ -125,8 +126,9 @@ declare class BluetoothSdkNativeModule extends NativeModule<BluetoothSdkModuleEv
125
126
 
126
127
  // WiFi Commands
127
128
  requestWifiScan(): Promise<WifiSearchResult[]>
129
+ getSavedWifiNetworks(): Promise<SavedWifiNetworksResult>
128
130
  sendWifiCredentials(ssid: string, password: string): Promise<WifiStatusChangeEvent>
129
- forgetWifiNetwork(ssid: string): Promise<WifiStatusChangeEvent>
131
+ forgetWifiNetwork(ssid: string): Promise<WifiForgetResult>
130
132
  setHotspotState(enabled: boolean): Promise<HotspotStatusChangeEvent>
131
133
  /** Enable or disable Wi-Fi ADB on Mentra Live (no-op on other devices). */
132
134
  setWifiAdbState(enabled: boolean): Promise<void>
@@ -202,9 +204,7 @@ declare class BluetoothSdkNativeModule extends NativeModule<BluetoothSdkModuleEv
202
204
 
203
205
  // Stream Commands
204
206
  startStream(params: StreamStartRequest): Promise<StreamStatusEvent>
205
- startExternallyManagedStream(params: StreamStartRequest): Promise<StreamStatusEvent>
206
207
  stopStream(): Promise<StreamStatusEvent>
207
- sendExternallyManagedStreamKeepAlive(params: StreamKeepAliveRequest): Promise<void>
208
208
 
209
209
  // Microphone Commands
210
210
  setMicState(enabled: boolean, useGlassesMic?: boolean, sendTranscript?: boolean, sendLc3Data?: boolean): Promise<void>
@@ -3,7 +3,7 @@ export const GENERATED_RELEASE_CHANGELOGS = Object.freeze(
3
3
  [
4
4
  {
5
5
  "version": "3.2.0",
6
- "markdown": "This release is under active development. User-facing changes will be documented as they land."
6
+ "markdown": "This release is under active development. User-facing changes will be documented as they land.\n\n- Breaking Bluetooth SDK change: `forgetWifiNetwork(ssid)` now returns `WifiForgetResult` instead of `WifiStatusChangeEvent`. Migrate status-only consumers to the semantic `outcome`; the requested network remains `ssid`, while optional post-command connectivity is reported by `connected`, `currentSsid`, and `localIp`."
7
7
  },
8
8
  {
9
9
  "version": "3.1.0",
@@ -12,9 +12,9 @@ export interface BluetoothSdkReleaseMetadata {
12
12
  export const BLUETOOTH_SDK_RELEASE_METADATA: Readonly<BluetoothSdkReleaseMetadata> = Object.freeze({
13
13
  "schemaVersion": 1,
14
14
  "familyBaseVersion": "3.2.0",
15
- "releaseIdentity": "3.2.0-dev.200",
16
- "releaseSetId": "mentra-3.2.0-dev.200",
17
- "sourceCommit": "1cdd911f596eba7b2b2f0e9ba5655c7ff16b5362",
18
- "otaManifestUrl": "https://github.com/Mentra-Community/MentraOS/releases/download/mentra-builds-v3.2.0/mentra-live-ota-3.2.0-dev.200.json",
19
- "otaManifestSha256": "f0b5cca2fa4d1d43acf5933fb4de629819d36a145a162c763f4faa27a7c8ff79"
15
+ "releaseIdentity": "3.2.0-dev.206",
16
+ "releaseSetId": "mentra-3.2.0-dev.206",
17
+ "sourceCommit": "3c22b92a5a53ce608c087503417ca5b3d16cc926",
18
+ "otaManifestUrl": "https://github.com/Mentra-Community/MentraOS/releases/download/mentra-builds-v3.2.0/mentra-live-ota-3.2.0-dev.206.json",
19
+ "otaManifestSha256": "7bc5e09f2757b2c11da58c93459bb0a554f406dd0ae5b50b58db28b64b92c838"
20
20
  })
package/src/index.ts CHANGED
@@ -26,6 +26,8 @@ const PUBLIC_EVENT_NAMES = new Set<BluetoothSdkEventName>([
26
26
  "local_transcription",
27
27
  "wifi_status_change",
28
28
  "wifi_scan_result",
29
+ "wifi_forget_result",
30
+ "saved_wifi_networks",
29
31
  "hotspot_status_change",
30
32
  "hotspot_error",
31
33
  "photo_response",
@@ -117,6 +119,7 @@ export const BluetoothSdk: BluetoothSdkPublicModule = Object.freeze({
117
119
  setScreenDisabled: bindPublicMethod("setScreenDisabled"),
118
120
  ping: bindPublicMethod("ping"),
119
121
  requestWifiScan: bindPublicMethod("requestWifiScan"),
122
+ getSavedWifiNetworks: bindPublicMethod("getSavedWifiNetworks"),
120
123
  sendWifiCredentials: bindPublicMethod("sendWifiCredentials"),
121
124
  forgetWifiNetwork: bindPublicMethod("forgetWifiNetwork"),
122
125
  setHotspotState: bindPublicMethod("setHotspotState"),
@@ -308,6 +311,14 @@ export type {
308
311
  VideoRecordingSuccessStatusEvent,
309
312
  VersionInfoEvent,
310
313
  VersionInfoResult,
314
+ SavedWifiNetworksEvent,
315
+ SavedWifiNetworksOutcome,
316
+ SavedWifiNetworksResult,
317
+ WifiForgetOutcome,
318
+ WifiForgetResult,
319
+ WifiForgetResultEvent,
320
+ LegacyWifiForgetResultEvent,
321
+ ModernWifiForgetResultEvent,
311
322
  WifiScanResultEvent,
312
323
  VoiceActivityDetectionStatusEvent,
313
324
  WifiSearchResult,