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

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 (50) hide show
  1. package/README.md +46 -45
  2. package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +5 -1
  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 +285 -29
  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 +312 -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/test/java/com/mentra/bluetoothsdk/GlassesStatusClassicAudioTest.kt +14 -0
  15. package/android/src/test/java/com/mentra/bluetoothsdk/MessageAckPolicyTest.kt +16 -0
  16. package/android/src/test/java/com/mentra/bluetoothsdk/WifiRequestResolutionTest.kt +271 -0
  17. package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/ClassicAudioConnectionTrackerTest.kt +131 -0
  18. package/build/BluetoothSdk.types.d.ts +62 -1
  19. package/build/BluetoothSdk.types.d.ts.map +1 -1
  20. package/build/BluetoothSdk.types.js.map +1 -1
  21. package/build/_private/BluetoothSdkModule.d.ts +3 -2
  22. package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
  23. package/build/_private/BluetoothSdkModule.js.map +1 -1
  24. package/build/generated/changelogCatalog.d.ts +1 -1
  25. package/build/generated/changelogCatalog.js +1 -1
  26. package/build/generated/changelogCatalog.js.map +1 -1
  27. package/build/generated/releaseMetadata.js +5 -5
  28. package/build/generated/releaseMetadata.js.map +1 -1
  29. package/build/index.d.ts +1 -1
  30. package/build/index.d.ts.map +1 -1
  31. package/build/index.js +3 -0
  32. package/build/index.js.map +1 -1
  33. package/ios/BluetoothSdkModule.swift +8 -2
  34. package/ios/Source/BluetoothSdkDefaults.swift +1 -1
  35. package/ios/Source/Bridge.swift +63 -0
  36. package/ios/Source/DeviceManager.swift +6 -3
  37. package/ios/Source/GeneratedChangelogCatalog.swift +1 -1
  38. package/ios/Source/GeneratedReleaseMetadata.swift +5 -5
  39. package/ios/Source/MentraBluetoothSDK.swift +365 -93
  40. package/ios/Source/WifiRequestResolution.swift +251 -0
  41. package/ios/Source/sgcs/MentraLive.swift +170 -35
  42. package/ios/Source/sgcs/SGCManager.swift +13 -1
  43. package/ios/Source/status/DeviceStatus.swift +28 -0
  44. package/ios/Tests/WifiRequestResolutionTests.swift +320 -0
  45. package/package.json +1 -1
  46. package/src/BluetoothSdk.types.ts +76 -1
  47. package/src/_private/BluetoothSdkModule.ts +4 -1
  48. package/src/generated/changelogCatalog.ts +1 -1
  49. package/src/generated/releaseMetadata.ts +5 -5
  50. package/src/index.ts +11 -0
@@ -376,6 +376,10 @@ public struct VersionInfoResult: CustomStringConvertible {
376
376
  public let appVersion: String
377
377
  public let packageName: String
378
378
  public let hotspotOtaVersion: Int
379
+ public let wifiForgetResultVersion: Int?
380
+ public let savedWifiNetworksVersion: Int?
381
+ public let versionInfoType: String?
382
+ public let sid: String?
379
383
 
380
384
  init(status: GlassesStatus) {
381
385
  androidVersion = status.androidVersion
@@ -388,6 +392,10 @@ public struct VersionInfoResult: CustomStringConvertible {
388
392
  appVersion = status.appVersion
389
393
  packageName = status.packageName
390
394
  hotspotOtaVersion = status.hotspotOtaVersion
395
+ wifiForgetResultVersion = nil
396
+ savedWifiNetworksVersion = nil
397
+ versionInfoType = nil
398
+ sid = nil
391
399
  }
392
400
 
393
401
  init(values: [String: Any]) {
@@ -404,6 +412,14 @@ public struct VersionInfoResult: CustomStringConvertible {
404
412
  intValue(values["hotspotOtaVersion"])
405
413
  ?? intValue(values["hotspot_ota_version"])
406
414
  ?? 0
415
+ wifiForgetResultVersion =
416
+ intValue(values["wifiForgetResultVersion"])
417
+ ?? intValue(values["wifi_forget_result_version"])
418
+ savedWifiNetworksVersion =
419
+ intValue(values["savedWifiNetworksVersion"])
420
+ ?? intValue(values["saved_wifi_networks_version"])
421
+ versionInfoType = stringValue(values, "versionInfoType", "version_info_type")
422
+ sid = stringValue(values, "sid")
407
423
  }
408
424
 
409
425
  public var dictionary: [String: Any] {
@@ -425,6 +441,18 @@ public struct VersionInfoResult: CustomStringConvertible {
425
441
  if !packageName.isEmpty {
426
442
  values["packageName"] = packageName
427
443
  }
444
+ if let wifiForgetResultVersion {
445
+ values["wifiForgetResultVersion"] = wifiForgetResultVersion
446
+ }
447
+ if let savedWifiNetworksVersion {
448
+ values["savedWifiNetworksVersion"] = savedWifiNetworksVersion
449
+ }
450
+ if let versionInfoType {
451
+ values["versionInfoType"] = versionInfoType
452
+ }
453
+ if let sid {
454
+ values["sid"] = sid
455
+ }
428
456
  return values
429
457
  }
430
458
 
@@ -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.203",
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 & {
@@ -930,6 +998,8 @@ export type BluetoothSdkModuleEvents = {
930
998
  phone_notification_dismissed: (event: PhoneNotificationDismissedEvent) => void
931
999
  wifi_status_change: (event: WifiStatusChangeEvent) => void
932
1000
  wifi_scan_result: (event: WifiScanResultEvent) => void
1001
+ wifi_forget_result: (event: WifiForgetResultEvent) => void
1002
+ saved_wifi_networks: (event: SavedWifiNetworksEvent) => void
933
1003
  hotspot_status_change: (event: HotspotStatusChangeEvent) => void
934
1004
  hotspot_error: (event: HotspotErrorEvent) => void
935
1005
  photo_response: (event: PhotoResponseEvent) => void
@@ -1090,6 +1160,8 @@ export type BluetoothSdkEventMap = {
1090
1160
  local_transcription: LocalTranscriptionEvent
1091
1161
  wifi_status_change: WifiStatusChangeEvent
1092
1162
  wifi_scan_result: WifiScanResultEvent
1163
+ wifi_forget_result: WifiForgetResultEvent
1164
+ saved_wifi_networks: SavedWifiNetworksEvent
1093
1165
  hotspot_status_change: HotspotStatusChangeEvent
1094
1166
  hotspot_error: HotspotErrorEvent
1095
1167
  photo_response: PhotoResponseEvent
@@ -1182,8 +1254,11 @@ export interface BluetoothSdkPublicModule {
1182
1254
  ping(): Promise<void>
1183
1255
 
1184
1256
  requestWifiScan(): Promise<WifiSearchResult[]>
1257
+ /** List exact WiFi SSIDs from glasses that support reliable saved-network enumeration. */
1258
+ getSavedWifiNetworks(): Promise<SavedWifiNetworksResult>
1185
1259
  sendWifiCredentials(ssid: string, password: string): Promise<WifiStatusChangeEvent>
1186
- forgetWifiNetwork(ssid: string): Promise<WifiStatusChangeEvent>
1260
+ /** Forget a saved network and return its correlated semantic outcome. */
1261
+ forgetWifiNetwork(ssid: string): Promise<WifiForgetResult>
1187
1262
  setHotspotState(enabled: boolean): Promise<HotspotStatusChangeEvent>
1188
1263
  /** Set the glasses clock from the phone after an OTA clock-skew failure. */
1189
1264
  setSystemTime(timestampMs: number): Promise<void>
@@ -55,6 +55,8 @@ import {
55
55
  VideoRecordingStatusEvent,
56
56
  VersionInfoResult,
57
57
  WarmUpCameraParams,
58
+ SavedWifiNetworksResult,
59
+ WifiForgetResult,
58
60
  WifiSearchResult,
59
61
  WifiStatusChangeEvent,
60
62
  } from "../BluetoothSdk.types"
@@ -125,8 +127,9 @@ declare class BluetoothSdkNativeModule extends NativeModule<BluetoothSdkModuleEv
125
127
 
126
128
  // WiFi Commands
127
129
  requestWifiScan(): Promise<WifiSearchResult[]>
130
+ getSavedWifiNetworks(): Promise<SavedWifiNetworksResult>
128
131
  sendWifiCredentials(ssid: string, password: string): Promise<WifiStatusChangeEvent>
129
- forgetWifiNetwork(ssid: string): Promise<WifiStatusChangeEvent>
132
+ forgetWifiNetwork(ssid: string): Promise<WifiForgetResult>
130
133
  setHotspotState(enabled: boolean): Promise<HotspotStatusChangeEvent>
131
134
  /** Enable or disable Wi-Fi ADB on Mentra Live (no-op on other devices). */
132
135
  setWifiAdbState(enabled: 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.203",
16
+ "releaseSetId": "mentra-3.2.0-dev.203",
17
+ "sourceCommit": "7eb4deefff27d498c2b4562d628607f399aa1f40",
18
+ "otaManifestUrl": "https://github.com/Mentra-Community/MentraOS/releases/download/mentra-builds-v3.2.0/mentra-live-ota-3.2.0-dev.203.json",
19
+ "otaManifestSha256": "0ae026238c8e70970be4e181cc1ae73dd905418292edebddb610012f4f0a9b6f"
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,