@mentra/bluetooth-sdk 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.
- package/README.md +68 -6
- package/android/build.gradle +15 -0
- package/android/lc3Lib/build.gradle +6 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkAnalytics.kt +63 -14
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkAnalyticsTracker.kt +55 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkDefaults.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkInstallId.kt +23 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +2 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkUpdateCheck.kt +184 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkUsageTracker.kt +388 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +4 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/GeneratedChangelogCatalog.kt +2 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/GeneratedReleaseMetadata.kt +6 -6
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +223 -36
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraLocalNetworkModule.kt +24 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/NetworkDiagnostics.kt +25 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/debug/BleTraceLogger.kt +3 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/ClassicAudioConnectionTracker.kt +4 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/ClassicAudioServiceDiscovery.kt +70 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1TextSanitizer.kt +11 -32
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/LatinTextSanitizer.kt +39 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.kt +93 -18
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/NimoCanvasCodec.kt +3 -1
- package/android/src/test/java/com/mentra/bluetoothsdk/BluetoothSdkAnalyticsTrackerTest.kt +58 -3
- package/android/src/test/java/com/mentra/bluetoothsdk/BluetoothSdkUpdateCheckTest.kt +124 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/BluetoothSdkUsageTrackerTest.kt +263 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/NetworkDiagnosticsTest.kt +55 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/ScanCompletionTest.kt +5 -1
- package/android/src/test/java/com/mentra/bluetoothsdk/debug/BleTraceLoggerTest.kt +69 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/ClassicAudioServiceDiscoveryTest.kt +124 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/G1TextSanitizerTest.kt +29 -4
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/MentraLiveClassicDisconnectTest.kt +133 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/MentraLiveIncidentDispatchTest.kt +126 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/MentraLiveWifiCredentialLogTest.kt +55 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/NimoCanvasCodecTest.kt +20 -7
- package/build/BluetoothSdk.types.d.ts +3 -0
- package/build/BluetoothSdk.types.d.ts.map +1 -1
- package/build/BluetoothSdk.types.js.map +1 -1
- package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
- package/build/_private/BluetoothSdkModule.js +2 -1
- package/build/_private/BluetoothSdkModule.js.map +1 -1
- package/build/_private/MentraLocalNetworkModule.d.ts +23 -0
- package/build/_private/MentraLocalNetworkModule.d.ts.map +1 -1
- package/build/_private/MentraLocalNetworkModule.js.map +1 -1
- package/build/_private/nativeLogConsole.d.ts +19 -0
- package/build/_private/nativeLogConsole.d.ts.map +1 -1
- package/build/_private/nativeLogConsole.js +12 -0
- package/build/_private/nativeLogConsole.js.map +1 -1
- package/build/generated/changelogCatalog.d.ts +4 -1
- package/build/generated/changelogCatalog.d.ts.map +1 -1
- package/build/generated/changelogCatalog.js +5 -1
- package/build/generated/changelogCatalog.js.map +1 -1
- package/build/generated/releaseMetadata.js +6 -6
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/ota-transport/index.d.ts +4 -0
- package/build/ota-transport/index.d.ts.map +1 -1
- package/build/ota-transport/index.js +2 -0
- package/build/ota-transport/index.js.map +1 -1
- package/ios/BluetoothSdkModule.swift +8 -4
- package/ios/Source/BluetoothSdkDefaults.swift +2 -2
- package/ios/Source/DeviceManager.swift +6 -3
- package/ios/Source/GeneratedChangelogCatalog.swift +2 -1
- package/ios/Source/GeneratedReleaseMetadata.swift +6 -6
- package/ios/Source/MentraBluetoothSDK.swift +295 -54
- package/ios/Source/internal/BluetoothAvailability.swift +6 -0
- package/ios/Source/internal/BluetoothSdkAnalytics.swift +87 -13
- package/ios/Source/internal/BluetoothSdkAnalyticsTracker.swift +54 -1
- package/ios/Source/internal/BluetoothSdkInstallId.swift +21 -0
- package/ios/Source/internal/BluetoothSdkUpdateCheck.swift +223 -0
- package/ios/Source/internal/BluetoothSdkUsageTracker.swift +428 -0
- package/ios/Source/sgcs/MentraLive.swift +71 -41
- package/ios/Source/sgcs/NimoCanvasCodec.swift +4 -1
- package/ios/Source/sgcs/SGCManager.swift +1 -1
- package/ios/Source/utils/AudioSessionMonitor.swift +44 -8
- package/ios/Source/utils/G1Text.swift +2 -24
- package/ios/Source/utils/G1TextSanitizer.swift +27 -0
- package/ios/Source/utils/LatinTextSanitizer.swift +48 -0
- package/ios/Tests/BluetoothAudioRouteTests.swift +26 -0
- package/ios/Tests/BluetoothSdkAnalyticsTrackerTests.swift +54 -3
- package/ios/Tests/BluetoothSdkUpdateCheckTests.swift +153 -0
- package/ios/Tests/BluetoothSdkUsageTrackerTests.swift +296 -0
- package/ios/Tests/G1TextSanitizerTests.swift +32 -0
- package/ios/Tests/IncidentLogDispatchTests.swift +111 -0
- package/ios/Tests/MentraLiveSessionReadinessTests.swift +163 -0
- package/ios/Tests/NimoCanvasCodecTests.swift +17 -4
- package/ios/Tests/VersionInfoCommandTests.swift +142 -1
- package/ios/Tests/WifiCredentialLoggingTests.swift +42 -0
- package/package.json +2 -2
- package/src/BluetoothSdk.types.ts +1 -0
- package/src/_private/BluetoothSdkModule.ts +2 -1
- package/src/_private/MentraLocalNetworkModule.ts +28 -0
- package/src/_private/nativeLogConsole.ts +19 -0
- package/src/generated/changelogCatalog.ts +5 -1
- package/src/generated/releaseMetadata.ts +6 -6
- package/src/ota-transport/index.ts +10 -1
|
@@ -4,6 +4,9 @@ import XCTest
|
|
|
4
4
|
@MainActor
|
|
5
5
|
private final class VersionInfoCommandTransport: MentraLive {
|
|
6
6
|
var requestId: String?
|
|
7
|
+
var reply = true
|
|
8
|
+
var includeResponseId = true
|
|
9
|
+
var metadataOverrides: [String: Any] = [:]
|
|
7
10
|
|
|
8
11
|
override func sendJson(_ jsonOriginal: [String: Any], wakeUp: Bool, requireAck: Bool) {
|
|
9
12
|
XCTAssertEqual(jsonOriginal["type"] as? String, "request_version")
|
|
@@ -14,7 +17,9 @@ private final class VersionInfoCommandTransport: MentraLive {
|
|
|
14
17
|
let command = try XCTUnwrap(JSONSerialization.jsonObject(with: bytes) as? [String: Any])
|
|
15
18
|
let id = try XCTUnwrap(command["request_id"] as? String)
|
|
16
19
|
requestId = id
|
|
17
|
-
|
|
20
|
+
guard reply else { return }
|
|
21
|
+
var common: [String: Any] = ["sid": "asg-1", "chunkCount": 2]
|
|
22
|
+
if includeResponseId { common["request_id"] = id }
|
|
18
23
|
for chunk: [String: Any] in [
|
|
19
24
|
["type": "version_info_1", "chunkIndex": 1, "final": false,
|
|
20
25
|
"build_number": "303000008", "app_version": "3.3.0"],
|
|
@@ -22,6 +27,7 @@ private final class VersionInfoCommandTransport: MentraLive {
|
|
|
22
27
|
"bes_fw_version": "26.9.23.0", "mtk_fw_version": "MentraLive_20260921.0"],
|
|
23
28
|
] {
|
|
24
29
|
let response = common.merging(chunk) { _, value in value }
|
|
30
|
+
.merging(metadataOverrides) { _, value in value }
|
|
25
31
|
try processReceivedData(JSONSerialization.data(withJSONObject: response))
|
|
26
32
|
}
|
|
27
33
|
} catch {
|
|
@@ -37,7 +43,12 @@ final class VersionInfoCommandTests: XCTestCase {
|
|
|
37
43
|
let transport = VersionInfoCommandTransport()
|
|
38
44
|
DeviceManager.shared.sgc = transport
|
|
39
45
|
let sdk = MentraBluetoothSDK()
|
|
46
|
+
var diagnostics: [[String: Any]] = []
|
|
47
|
+
let sink = Bridge.addEventSink { event, body in
|
|
48
|
+
if let diagnostic = self.diagnostic(event, body) { diagnostics.append(diagnostic) }
|
|
49
|
+
}
|
|
40
50
|
defer {
|
|
51
|
+
Bridge.removeEventSink(sink)
|
|
41
52
|
sdk.invalidate()
|
|
42
53
|
DeviceManager.shared.sgc = previous
|
|
43
54
|
}
|
|
@@ -49,5 +60,135 @@ final class VersionInfoCommandTests: XCTestCase {
|
|
|
49
60
|
XCTAssertEqual(result.appVersion, "3.3.0")
|
|
50
61
|
XCTAssertEqual(result.besFirmwareVersion, "26.9.23.0")
|
|
51
62
|
XCTAssertEqual(result.mtkFirmwareVersion, "MentraLive_20260921.0")
|
|
63
|
+
XCTAssertEqual(diagnostics.compactMap { $0["stage"] as? String }, [
|
|
64
|
+
"registered", "response-waiting", "response-complete", "resolved",
|
|
65
|
+
])
|
|
66
|
+
XCTAssertTrue(diagnostics.allSatisfy { $0["requestId"] as? String == transport.requestId })
|
|
67
|
+
XCTAssertEqual(diagnostics[1]["responseRequestId"] as? String, transport.requestId)
|
|
68
|
+
XCTAssertEqual(diagnostics[2]["responseChunk"] as? String, "version_info_3")
|
|
69
|
+
XCTAssertEqual(diagnostics[1]["_responseChunkIndex"] as? String, "number:1")
|
|
70
|
+
XCTAssertEqual(diagnostics[2]["_responseChunkIndex"] as? String, "number:2")
|
|
71
|
+
XCTAssertEqual(diagnostics[1]["_responseFinal"] as? String, "boolean:false")
|
|
72
|
+
XCTAssertFalse(diagnostics.contains { $0["besFirmwareVersion"] != nil || $0["serialNumber"] != nil })
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
func testUncorrelatedResponseAndCancellationRetainThePendingUuidAndOriginalError() async throws {
|
|
76
|
+
let previous = DeviceManager.shared.sgc
|
|
77
|
+
let transport = VersionInfoCommandTransport()
|
|
78
|
+
transport.includeResponseId = false
|
|
79
|
+
DeviceManager.shared.sgc = transport
|
|
80
|
+
let sdk = MentraBluetoothSDK()
|
|
81
|
+
var diagnostics: [[String: Any]] = []
|
|
82
|
+
let sink = Bridge.addEventSink { event, body in
|
|
83
|
+
if let diagnostic = self.diagnostic(event, body) { diagnostics.append(diagnostic) }
|
|
84
|
+
}
|
|
85
|
+
defer {
|
|
86
|
+
Bridge.removeEventSink(sink)
|
|
87
|
+
sdk.invalidate()
|
|
88
|
+
DeviceManager.shared.sgc = previous
|
|
89
|
+
}
|
|
90
|
+
let task = Task { try await sdk.requestVersionInfo() }
|
|
91
|
+
while transport.requestId == nil {
|
|
92
|
+
await Task.yield()
|
|
93
|
+
}
|
|
94
|
+
do {
|
|
95
|
+
_ = try await sdk.requestVersionInfo()
|
|
96
|
+
XCTFail("The original pending request must retain its slot")
|
|
97
|
+
} catch let error as BluetoothSdkError {
|
|
98
|
+
XCTAssertEqual(error.code, "request_in_flight")
|
|
99
|
+
}
|
|
100
|
+
task.cancel()
|
|
101
|
+
do {
|
|
102
|
+
_ = try await task.value
|
|
103
|
+
XCTFail("Uncorrelated responses must not complete the request")
|
|
104
|
+
} catch let error as BluetoothSdkError {
|
|
105
|
+
XCTAssertEqual(error.code, "request_cancelled")
|
|
106
|
+
let rejected = try XCTUnwrap(diagnostics.last)
|
|
107
|
+
XCTAssertEqual(rejected["stage"] as? String, "rejected")
|
|
108
|
+
XCTAssertEqual(rejected["code"] as? String, error.code)
|
|
109
|
+
XCTAssertEqual(rejected["message"] as? String, error.message)
|
|
110
|
+
}
|
|
111
|
+
XCTAssertEqual(diagnostics.compactMap { $0["stage"] as? String }, [
|
|
112
|
+
"registered", "response-ignored", "response-ignored", "refused", "rejected",
|
|
113
|
+
])
|
|
114
|
+
XCTAssertTrue(diagnostics.allSatisfy { $0["requestId"] as? String == transport.requestId })
|
|
115
|
+
XCTAssertEqual(diagnostics[1]["responseRequestId"] as? String, "")
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
func testNoResponseRetainsTheOriginalTimeoutAndElapsedTime() async throws {
|
|
119
|
+
let previous = DeviceManager.shared.sgc
|
|
120
|
+
let transport = VersionInfoCommandTransport()
|
|
121
|
+
transport.reply = false
|
|
122
|
+
DeviceManager.shared.sgc = transport
|
|
123
|
+
let sdk = MentraBluetoothSDK()
|
|
124
|
+
var diagnostics: [[String: Any]] = []
|
|
125
|
+
let sink = Bridge.addEventSink { event, body in
|
|
126
|
+
if let diagnostic = self.diagnostic(event, body) { diagnostics.append(diagnostic) }
|
|
127
|
+
}
|
|
128
|
+
defer {
|
|
129
|
+
Bridge.removeEventSink(sink)
|
|
130
|
+
sdk.invalidate()
|
|
131
|
+
DeviceManager.shared.sgc = previous
|
|
132
|
+
}
|
|
133
|
+
do {
|
|
134
|
+
_ = try await sdk.requestVersionInfo()
|
|
135
|
+
XCTFail("An absent response must time out")
|
|
136
|
+
} catch let error as BluetoothSdkError {
|
|
137
|
+
XCTAssertEqual(error.code, "request_timeout")
|
|
138
|
+
let rejected = try XCTUnwrap(diagnostics.last)
|
|
139
|
+
XCTAssertEqual(rejected["code"] as? String, error.code)
|
|
140
|
+
XCTAssertEqual(rejected["message"] as? String, error.message)
|
|
141
|
+
XCTAssertGreaterThanOrEqual(try XCTUnwrap(rejected["elapsedMs"] as? Int), 15000)
|
|
142
|
+
}
|
|
143
|
+
XCTAssertEqual(diagnostics.compactMap { $0["stage"] as? String }, ["registered", "rejected"])
|
|
144
|
+
XCTAssertTrue(diagnostics.allSatisfy { $0["requestId"] as? String == transport.requestId })
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
func testMalformedMetadataRetainsItsTypeThroughTheActualBridgeLog() async throws {
|
|
148
|
+
let previous = DeviceManager.shared.sgc
|
|
149
|
+
let transport = VersionInfoCommandTransport()
|
|
150
|
+
DeviceManager.shared.sgc = transport
|
|
151
|
+
let sdk = MentraBluetoothSDK()
|
|
152
|
+
var diagnostics: [[String: Any]] = []
|
|
153
|
+
let sink = Bridge.addEventSink { event, body in
|
|
154
|
+
if let diagnostic = self.diagnostic(event, body) { diagnostics.append(diagnostic) }
|
|
155
|
+
}
|
|
156
|
+
defer {
|
|
157
|
+
Bridge.removeEventSink(sink)
|
|
158
|
+
sdk.invalidate()
|
|
159
|
+
DeviceManager.shared.sgc = previous
|
|
160
|
+
}
|
|
161
|
+
for (wireKey, logKey, value, expected) in [
|
|
162
|
+
("chunkIndex", "_responseChunkIndex", true as Any, "boolean:true"),
|
|
163
|
+
("chunkCount", "_responseChunkCount", true as Any, "boolean:true"),
|
|
164
|
+
("final", "_responseFinal", 1 as Any, "number:1"),
|
|
165
|
+
] {
|
|
166
|
+
diagnostics.removeAll()
|
|
167
|
+
transport.requestId = nil
|
|
168
|
+
transport.metadataOverrides = [wireKey: value]
|
|
169
|
+
let task = Task { try await sdk.requestVersionInfo() }
|
|
170
|
+
while transport.requestId == nil {
|
|
171
|
+
await Task.yield()
|
|
172
|
+
}
|
|
173
|
+
task.cancel()
|
|
174
|
+
do {
|
|
175
|
+
_ = try await task.value
|
|
176
|
+
XCTFail("Malformed metadata must not resolve")
|
|
177
|
+
} catch let error as BluetoothSdkError {
|
|
178
|
+
XCTAssertEqual(error.code, "request_cancelled")
|
|
179
|
+
}
|
|
180
|
+
let ignored = diagnostics.filter { $0["stage"] as? String == "response-ignored" }
|
|
181
|
+
XCTAssertEqual(ignored.count, 2)
|
|
182
|
+
XCTAssertTrue(ignored.allSatisfy { $0[logKey] as? String == expected })
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private func diagnostic(_ event: String, _ body: [String: Any]) -> [String: Any]? {
|
|
187
|
+
guard event == "log", let message = body["message"] as? String,
|
|
188
|
+
message.contains("layer=sdk_version_request"),
|
|
189
|
+
let payload = message.range(of: " payload="),
|
|
190
|
+
let data = String(message[payload.upperBound...]).data(using: .utf8)
|
|
191
|
+
else { return nil }
|
|
192
|
+
return try? JSONSerialization.jsonObject(with: data) as? [String: Any]
|
|
52
193
|
}
|
|
53
194
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
@testable import MentraBluetoothSDK
|
|
2
|
+
import XCTest
|
|
3
|
+
|
|
4
|
+
/// Captures packed BLE writes instead of sending them, so the real device dispatch,
|
|
5
|
+
/// command serialization and transport logging run without a radio.
|
|
6
|
+
@MainActor
|
|
7
|
+
private final class CapturingMentraLive: MentraLive {
|
|
8
|
+
var writes: [Data] = []
|
|
9
|
+
|
|
10
|
+
override func queueSend(_ data: Data, id _: String, trace _: BleWriteTrace?) {
|
|
11
|
+
writes.append(data)
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@MainActor
|
|
16
|
+
final class WifiCredentialLoggingTests: XCTestCase {
|
|
17
|
+
func testWifiCredentialsReachTheBleQueueWithoutBeingLogged() {
|
|
18
|
+
let ssid = "synthetic-\(UUID().uuidString)"
|
|
19
|
+
let password = UUID().uuidString
|
|
20
|
+
var logs: [String] = []
|
|
21
|
+
let sink = Bridge.addEventSink { event, body in
|
|
22
|
+
if event == "log", let message = body["message"] as? String {
|
|
23
|
+
logs.append(message)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
defer { Bridge.removeEventSink(sink) }
|
|
27
|
+
let manager = DeviceManager()
|
|
28
|
+
let live = CapturingMentraLive()
|
|
29
|
+
manager.sgc = live
|
|
30
|
+
|
|
31
|
+
manager.sendWifiCredentials(ssid, password)
|
|
32
|
+
|
|
33
|
+
XCTAssertFalse(logs.contains { $0.contains(password) }, "password logged")
|
|
34
|
+
XCTAssertTrue(logs.contains("MAN: Sending wifi credentials: \(ssid)"))
|
|
35
|
+
XCTAssertTrue(logs.contains("LIVE: Sending data to glasses: <set_wifi_credentials with credentials omitted>"))
|
|
36
|
+
// The glasses still receive the unchanged credentials.
|
|
37
|
+
let wire = live.writes.map { String(decoding: $0, as: UTF8.self) }.joined()
|
|
38
|
+
XCTAssertTrue(wire.contains("set_wifi_credentials"))
|
|
39
|
+
XCTAssertTrue(wire.contains(ssid))
|
|
40
|
+
XCTAssertTrue(wire.contains(password))
|
|
41
|
+
}
|
|
42
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mentra/bluetooth-sdk",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0-beta.954",
|
|
4
4
|
"description": "SDK for communicating with smart glasses",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"react-native": "src/index.ts",
|
|
@@ -114,7 +114,7 @@
|
|
|
114
114
|
"registry": "https://registry.npmjs.org/"
|
|
115
115
|
},
|
|
116
116
|
"dependencies": {
|
|
117
|
-
"@mentra/cloud-protocol": "3.
|
|
117
|
+
"@mentra/cloud-protocol": "3.3.0-beta.954"
|
|
118
118
|
},
|
|
119
119
|
"devDependencies": {
|
|
120
120
|
"@types/node": "^25.9.3",
|
|
@@ -1125,6 +1125,7 @@ export type BluetoothSdkModuleEvents = {
|
|
|
1125
1125
|
glasses_status: (changed: Partial<GlassesStatus>) => void
|
|
1126
1126
|
bluetooth_status: (changed: Partial<BluetoothStatus>) => void
|
|
1127
1127
|
log: (event: LogEvent) => void
|
|
1128
|
+
sdk_update_notice: (event: {message: string}) => void
|
|
1128
1129
|
device_discovered: (device: Device) => void
|
|
1129
1130
|
default_device_changed: (event: {device?: Device}) => void
|
|
1130
1131
|
// Individual event handlers
|
|
@@ -2,7 +2,7 @@ import {createScanSession} from "./scanSession"
|
|
|
2
2
|
import {NativeModule, requireNativeModule} from "expo"
|
|
3
3
|
import {Platform} from "react-native"
|
|
4
4
|
|
|
5
|
-
import {installNativeLogConsole} from "./nativeLogConsole"
|
|
5
|
+
import {installNativeLogConsole, installSdkUpdateNotice} from "./nativeLogConsole"
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
8
|
BluetoothSettingsUpdate,
|
|
@@ -350,6 +350,7 @@ export type BluetoothSdkInternalModule = BluetoothSdkNativeModule
|
|
|
350
350
|
// NativeModule<BluetoothSdkModuleEvents> already extends EventEmitter<BluetoothSdkModuleEvents>
|
|
351
351
|
const NativeBluetoothSdkModule = requireNativeModule<BluetoothSdkNativeModule>("BluetoothSdk")
|
|
352
352
|
installNativeLogConsole(NativeBluetoothSdkModule, Platform.OS)
|
|
353
|
+
installSdkUpdateNotice(NativeBluetoothSdkModule)
|
|
353
354
|
|
|
354
355
|
const DEFAULT_CONNECT_OPTIONS: Required<ConnectOptions> = {
|
|
355
356
|
saveAsDefault: true,
|
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
import {NativeModule, requireOptionalNativeModule} from "expo"
|
|
2
2
|
|
|
3
|
+
export type NetworkCapabilitiesDiagnostic = {
|
|
4
|
+
wifi: boolean
|
|
5
|
+
cellular: boolean
|
|
6
|
+
ethernet: boolean
|
|
7
|
+
vpn: boolean
|
|
8
|
+
internet: boolean
|
|
9
|
+
validated: boolean
|
|
10
|
+
captivePortal: boolean
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type NetworkObservation = {
|
|
14
|
+
present: boolean
|
|
15
|
+
// A present network with null capabilities has disappeared or cannot be inspected.
|
|
16
|
+
capabilities: NetworkCapabilitiesDiagnostic | null
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type NetworkDiagnostics = {
|
|
20
|
+
sampledAtMs: number
|
|
21
|
+
processBound: NetworkObservation
|
|
22
|
+
// ConnectivityManager.activeNetwork is the caller's active default, not a privileged global read.
|
|
23
|
+
activeNetwork: NetworkObservation
|
|
24
|
+
glassesLocal: NetworkObservation
|
|
25
|
+
processBoundMatchesActive: boolean
|
|
26
|
+
processBoundMatchesGlassesLocal: boolean
|
|
27
|
+
activeNetworkMatchesGlassesLocal: boolean
|
|
28
|
+
}
|
|
29
|
+
|
|
3
30
|
export type LocalNetworkResponse = {
|
|
4
31
|
status: number
|
|
5
32
|
headers: Record<string, string>
|
|
@@ -26,6 +53,7 @@ type LocalNetworkEvents = {
|
|
|
26
53
|
}
|
|
27
54
|
|
|
28
55
|
declare class MentraLocalNetworkNativeModule extends NativeModule<LocalNetworkEvents> {
|
|
56
|
+
getNetworkDiagnostics(): NetworkDiagnostics
|
|
29
57
|
connect(ssid: string, password: string): Promise<{connected: boolean; ssid: string; localAddress?: string}>
|
|
30
58
|
request(
|
|
31
59
|
requestId: string,
|
|
@@ -22,3 +22,22 @@ export function installNativeLogConsole(source: SubscribedLogSource, platform: s
|
|
|
22
22
|
console.log(`[native:${platform}]`, nativeLogMessage(message))
|
|
23
23
|
})
|
|
24
24
|
}
|
|
25
|
+
|
|
26
|
+
type UpdateNoticeSource = {
|
|
27
|
+
addListener(event: "sdk_update_notice", listener: (event: {message: string}) => void): {remove(): void}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const updateNoticeKey = Symbol.for("mentra.bluetooth-sdk.update-notice")
|
|
31
|
+
type SubscribedUpdateNoticeSource = UpdateNoticeSource & {[updateNoticeKey]?: {remove(): void}}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The native SDK checks for a newer Mentra Bluetooth SDK at startup and emits
|
|
35
|
+
* its banner when this app is behind. A console warning reaches the developer
|
|
36
|
+
* in Metro and, in debug builds, as a LogBox warning; release builds show no UI.
|
|
37
|
+
*/
|
|
38
|
+
export function installSdkUpdateNotice(source: SubscribedUpdateNoticeSource): void {
|
|
39
|
+
source[updateNoticeKey]?.remove()
|
|
40
|
+
source[updateNoticeKey] = source.addListener("sdk_update_notice", ({message}) => {
|
|
41
|
+
console.warn(message)
|
|
42
|
+
})
|
|
43
|
+
}
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
/** Generated from /changelogs. Do not edit directly. */
|
|
2
2
|
export const GENERATED_RELEASE_CHANGELOGS = Object.freeze(
|
|
3
3
|
[
|
|
4
|
+
{
|
|
5
|
+
"version": "3.3.0",
|
|
6
|
+
"markdown": "This release is under active development. User-facing changes will be documented as they land."
|
|
7
|
+
},
|
|
4
8
|
{
|
|
5
9
|
"version": "3.2.1",
|
|
6
10
|
"markdown": "- **Livestreamer:** \"Stream Here\" now works without a Wi-Fi network. Improved stream startup and connection reliability.\n- **Photos and gallery:** More reliable photo capture and gallery syncing.\n- **Mentra AI:** Reliability improvements and bug fixes.\n- **Miniapps:** Smoother transitions, with back buttons and gestures that take you to the previous screen inside a miniapp.\n- **Pairing and connections:** Improved G1 pairing and G2 background reconnection.\n- **Glasses updates:** Fixed repeated update checks and improved recovery after interrupted updates.\n- **Mentra Maps:** Bug fixes.\n- **NIMO:** Improved text and map layouts.\n- **Self-hosting:** Connect the Mentra App to your own self-hosted Mentra backend."
|
|
7
11
|
},
|
|
8
12
|
{
|
|
9
13
|
"version": "3.2.0",
|
|
10
|
-
"markdown": "- Improved glasses connectivity and Wi-Fi setup
|
|
14
|
+
"markdown": "- Improved glasses connectivity and Wi-Fi setup.\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`."
|
|
11
15
|
},
|
|
12
16
|
{
|
|
13
17
|
"version": "3.1.1",
|
|
@@ -11,10 +11,10 @@ export interface BluetoothSdkReleaseMetadata {
|
|
|
11
11
|
|
|
12
12
|
export const BLUETOOTH_SDK_RELEASE_METADATA: Readonly<BluetoothSdkReleaseMetadata> = Object.freeze({
|
|
13
13
|
"schemaVersion": 1,
|
|
14
|
-
"familyBaseVersion": "3.
|
|
15
|
-
"releaseIdentity": "3.
|
|
16
|
-
"releaseSetId": "mentra-3.
|
|
17
|
-
"sourceCommit": "
|
|
18
|
-
"otaManifestUrl": "https://artifactscdn.mentraglass.com/Mentra-Community/MentraOS/releases/mentra-builds-v3.
|
|
19
|
-
"otaManifestSha256": "
|
|
14
|
+
"familyBaseVersion": "3.3.0",
|
|
15
|
+
"releaseIdentity": "3.3.0-beta.954",
|
|
16
|
+
"releaseSetId": "mentra-3.3.0-beta.954",
|
|
17
|
+
"sourceCommit": "25f6b1d70ce39dfe5d454021542f1627e256775a",
|
|
18
|
+
"otaManifestUrl": "https://artifactscdn.mentraglass.com/Mentra-Community/MentraOS/releases/mentra-builds-v3.3.0/mentra-live-ota-3.3.0-beta.954.json",
|
|
19
|
+
"otaManifestSha256": "03d976cb897215b97b5c40a53dabbc8356a045d1fbb57d5ad2c95bbc21203e21"
|
|
20
20
|
})
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
// This public adapter is the package-owned boundary over its native implementation.
|
|
2
2
|
// eslint-disable-next-line no-restricted-imports
|
|
3
|
-
import MentraLocalNetwork from "../_private/MentraLocalNetworkModule"
|
|
3
|
+
import MentraLocalNetwork, {type NetworkDiagnostics} from "../_private/MentraLocalNetworkModule"
|
|
4
4
|
// eslint-disable-next-line no-restricted-imports
|
|
5
5
|
import MentraOtaServer from "../ota-server"
|
|
6
6
|
|
|
7
|
+
// eslint-disable-next-line no-restricted-imports
|
|
8
|
+
export type {
|
|
9
|
+
NetworkCapabilitiesDiagnostic,
|
|
10
|
+
NetworkDiagnostics,
|
|
11
|
+
NetworkObservation,
|
|
12
|
+
} from "../_private/MentraLocalNetworkModule"
|
|
13
|
+
|
|
7
14
|
export type OtaTransportSubscription = {remove(): void}
|
|
8
15
|
|
|
9
16
|
export type OtaLocalNetworkConnection = {
|
|
@@ -55,6 +62,8 @@ export type OtaServerResult = {
|
|
|
55
62
|
*/
|
|
56
63
|
export const otaLocalNetwork = Object.freeze({
|
|
57
64
|
isAvailable: (): boolean => MentraLocalNetwork != null,
|
|
65
|
+
/** Synchronous local observation; no network I/O, route changes or network identities. */
|
|
66
|
+
getNetworkDiagnostics: (): NetworkDiagnostics | null => MentraLocalNetwork?.getNetworkDiagnostics() ?? null,
|
|
58
67
|
connect: async (ssid: string, password: string): Promise<OtaLocalNetworkConnection> => {
|
|
59
68
|
if (!MentraLocalNetwork) throw new Error("Scoped local networking is not available in this native build")
|
|
60
69
|
return MentraLocalNetwork.connect(ssid, password)
|