@mentra/bluetooth-sdk 3.2.0-dev.191 → 3.2.0-dev.195
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 +29 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +1 -3
- package/android/src/main/java/com/mentra/bluetoothsdk/GeneratedReleaseMetadata.kt +5 -5
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraLocalNetworkModule.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/debug/BleTraceLogger.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/otaserver/MentraOtaServerModule.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/photoreceiver/MentraPhotoReceiverModule.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/services/PhoneMic.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Ar99.java +8 -8
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Mach1.kt +2 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLiveL2capChannel.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/ar99/ota/Ar99OtaManager.java +2 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/stt/STTTools.kt +2 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/stt/SherpaOnnxTranscriber.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/stt/VadGateSpeechPolicy.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/tts/TTSTools.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/AES.java +7 -8
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/BitmapJavaUtils.java +2 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/BlePhotoUploadService.java +34 -34
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/G1Text.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/IncidentLogBleUploadService.java +4 -4
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/K900ProtocolUtils.java +34 -34
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/MessageChunkReassembler.java +10 -10
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/MessageChunker.java +6 -6
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/NativeLog.java +89 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/NexSGCUtils.kt +2 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/audio/Ar99OpusPcmDecoder.java +10 -10
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/audio/ByteUtilAudioPlayer.java +7 -7
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/audio/Lc3Player.java +16 -16
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/audio/PCMAudioPlayer.java +26 -26
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/audio/PcmStreamPlayer.kt +1 -1
- package/android/src/test/java/com/mentra/bluetoothsdk/utils/NativeLogTest.kt +61 -0
- package/build/BluetoothSdk.types.d.ts +7 -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 +4 -0
- package/build/_private/BluetoothSdkModule.js.map +1 -1
- package/build/_private/nativeLogConsole.d.ts +17 -0
- package/build/_private/nativeLogConsole.d.ts.map +1 -0
- package/build/_private/nativeLogConsole.js +16 -0
- package/build/_private/nativeLogConsole.js.map +1 -0
- package/build/generated/releaseMetadata.js +5 -5
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/react/useGlassesConnection.d.ts +5 -0
- package/build/react/useGlassesConnection.d.ts.map +1 -1
- package/build/react/useGlassesConnection.js +2 -2
- package/build/react/useGlassesConnection.js.map +1 -1
- package/build/react/useMentraBluetooth.d.ts +5 -0
- package/build/react/useMentraBluetooth.d.ts.map +1 -1
- package/build/react/useMentraBluetooth.js +1 -0
- package/build/react/useMentraBluetooth.js.map +1 -1
- package/ios/BluetoothSdkModule.swift +2 -2
- package/ios/Source/BluetoothSdkDefaults.swift +1 -1
- package/ios/Source/Bridge.swift +3 -2
- package/ios/Source/DeviceManager.swift +4 -2
- package/ios/Source/GeneratedReleaseMetadata.swift +5 -5
- package/ios/Source/MentraBluetoothSDK.swift +10 -3
- package/ios/Source/internal/BleTraceLogger.swift +3 -0
- package/ios/Source/sgcs/G1.swift +1 -1
- package/ios/Source/sgcs/MentraLive.swift +28 -11
- package/ios/Source/stt/STTTools.swift +1 -1
- package/ios/Source/types/DeviceModels.swift +9 -1
- package/ios/Source/utils/G1Text.swift +1 -1
- package/ios/Source/utils/JSCExperiment.swift +1 -2
- package/ios/Source/utils/MessageChunkReassembler.swift +6 -6
- package/ios/Source/utils/MessageChunker.swift +8 -8
- package/ios/Source/utils/Models.swift +1 -1
- package/ios/Tests/MentraLiveConnectionOptionsTests.swift +30 -0
- package/ios/Tests/NativeLogTests.swift +34 -0
- package/package.json +1 -1
- package/src/BluetoothSdk.types.ts +7 -0
- package/src/_private/BluetoothSdkModule.ts +5 -0
- package/src/_private/nativeLogConsole.ts +24 -0
- package/src/generated/releaseMetadata.ts +5 -5
- package/src/react/useGlassesConnection.ts +7 -2
- package/src/react/useMentraBluetooth.ts +6 -0
|
@@ -41,7 +41,7 @@ class MessageChunker {
|
|
|
41
41
|
let needsChunking = messageBytes > MESSAGE_SIZE_THRESHOLD
|
|
42
42
|
|
|
43
43
|
if needsChunking {
|
|
44
|
-
|
|
44
|
+
Bridge.log("MessageChunker: Message size \(messageBytes) exceeds threshold \(MESSAGE_SIZE_THRESHOLD), will chunk")
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
return needsChunking
|
|
@@ -56,7 +56,7 @@ class MessageChunker {
|
|
|
56
56
|
*/
|
|
57
57
|
static func createChunks(originalJson: String, messageId: Int64 = -1, wakeUp: Bool = false) -> [[String: Any]] {
|
|
58
58
|
guard let messageData = originalJson.data(using: .utf8) else {
|
|
59
|
-
|
|
59
|
+
Bridge.log("MessageChunker: Failed to convert message to data")
|
|
60
60
|
return []
|
|
61
61
|
}
|
|
62
62
|
|
|
@@ -68,12 +68,12 @@ class MessageChunker {
|
|
|
68
68
|
for chunkSize in stride(from: INITIAL_CHUNK_DATA_SIZE, through: MIN_CHUNK_DATA_SIZE, by: -1) {
|
|
69
69
|
let chunks = buildChunks(messageData, chunkId: chunkId, messageId: messageId, chunkSize: chunkSize)
|
|
70
70
|
if allChunksFit(chunks, wakeUp: wakeUp) {
|
|
71
|
-
|
|
71
|
+
Bridge.log("MessageChunker: Creating \(chunks.count) chunks for message of size \(totalBytes) bytes using \(chunkSize)-byte UTF-8 slices")
|
|
72
72
|
return chunks
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
Bridge.log("MessageChunker: Unable to create K900 chunks within \(MAX_PACKED_CHUNK_SIZE) bytes")
|
|
77
77
|
return []
|
|
78
78
|
}
|
|
79
79
|
|
|
@@ -101,7 +101,7 @@ class MessageChunker {
|
|
|
101
101
|
|
|
102
102
|
chunks.append(chunk)
|
|
103
103
|
|
|
104
|
-
|
|
104
|
+
Bridge.log("MessageChunker: Created chunk \(i)/\(totalChunks - 1) with \(chunkString.data(using: .utf8)?.count ?? 0) bytes")
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
return chunks
|
|
@@ -111,7 +111,7 @@ class MessageChunker {
|
|
|
111
111
|
for (index, chunk) in chunks.enumerated() {
|
|
112
112
|
let packedLength = packedK900Length(chunk, wakeUp: wakeUp && index == 0)
|
|
113
113
|
if packedLength == nil || packedLength! > MAX_PACKED_CHUNK_SIZE {
|
|
114
|
-
|
|
114
|
+
Bridge.log("MessageChunker: Chunk \(index) packed to \(packedLength ?? 0) bytes, exceeding \(MAX_PACKED_CHUNK_SIZE)")
|
|
115
115
|
return false
|
|
116
116
|
}
|
|
117
117
|
}
|
|
@@ -191,7 +191,7 @@ class MessageChunker {
|
|
|
191
191
|
let totalChunks = (json["total"] as? Int) ?? (json["n"] as? Int),
|
|
192
192
|
let data = (json["data"] as? String) ?? (json["d"] as? String)
|
|
193
193
|
else {
|
|
194
|
-
|
|
194
|
+
Bridge.log("MessageChunker: Failed to extract chunk info from JSON")
|
|
195
195
|
return nil
|
|
196
196
|
}
|
|
197
197
|
|
|
@@ -255,7 +255,7 @@ class MessageChunker {
|
|
|
255
255
|
)
|
|
256
256
|
}
|
|
257
257
|
|
|
258
|
-
|
|
258
|
+
Bridge.log("MessageChunker: Created \(fragments.count) binary fragments for \(payload.count) bytes")
|
|
259
259
|
return fragments
|
|
260
260
|
}
|
|
261
261
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import CoreBluetooth
|
|
2
|
+
@testable import MentraBluetoothSDK
|
|
3
|
+
import XCTest
|
|
4
|
+
|
|
5
|
+
final class MentraLiveConnectionOptionsTests: XCTestCase {
|
|
6
|
+
func testConnectOptionsRequireAncsByDefault() {
|
|
7
|
+
XCTAssertTrue(ConnectOptions().requiresAncs)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
func testConnectOptionsCanDisableAncsRequirement() {
|
|
11
|
+
XCTAssertFalse(ConnectOptions(requiresAncs: false).requiresAncs)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
#if os(macOS)
|
|
15
|
+
func testCoreBluetoothOptionsOmitAncsOnMacOS() {
|
|
16
|
+
XCTAssertNil(MentraLiveConnectionOptions.coreBluetoothOptions(requiresAncs: true))
|
|
17
|
+
}
|
|
18
|
+
#else
|
|
19
|
+
func testCoreBluetoothOptionsRequireAncsWhenEnabled() {
|
|
20
|
+
let options = MentraLiveConnectionOptions.coreBluetoothOptions(requiresAncs: true)
|
|
21
|
+
|
|
22
|
+
XCTAssertEqual(options?.count, 1)
|
|
23
|
+
XCTAssertEqual(options?[CBConnectPeripheralOptionRequiresANCS] as? Bool, true)
|
|
24
|
+
}
|
|
25
|
+
#endif
|
|
26
|
+
|
|
27
|
+
func testCoreBluetoothOptionsAreOmittedWhenAncsIsDisabled() {
|
|
28
|
+
XCTAssertNil(MentraLiveConnectionOptions.coreBluetoothOptions(requiresAncs: false))
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
@testable import MentraBluetoothSDK
|
|
2
|
+
import XCTest
|
|
3
|
+
|
|
4
|
+
final class NativeLogTests: XCTestCase {
|
|
5
|
+
func testTracingAnEventDoesNotRecursivelyTraceTheLog() {
|
|
6
|
+
let delivered = expectation(description: "Trace and original event")
|
|
7
|
+
delivered.expectedFulfillmentCount = 2
|
|
8
|
+
delivered.assertForOverFulfill = true
|
|
9
|
+
var events: [String] = []
|
|
10
|
+
let sink = Bridge.addEventSink { event, _ in
|
|
11
|
+
events.append(event)
|
|
12
|
+
delivered.fulfill()
|
|
13
|
+
}
|
|
14
|
+
defer { Bridge.removeEventSink(sink) }
|
|
15
|
+
Bridge.sendTypedMessage("head_up", body: ["up": true])
|
|
16
|
+
wait(for: [delivered], timeout: 2)
|
|
17
|
+
XCTAssertEqual(events, ["log", "head_up"])
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
func testNativeDiagnosticsReachTheLogEventOnce() {
|
|
21
|
+
let delivered = expectation(description: "Native log event")
|
|
22
|
+
delivered.assertForOverFulfill = true
|
|
23
|
+
let sink = Bridge.addEventSink { event, body in
|
|
24
|
+
guard event == "log" else { return }
|
|
25
|
+
XCTAssertEqual(body["message"] as? String, "MessageChunker: diagnostic")
|
|
26
|
+
delivered.fulfill()
|
|
27
|
+
}
|
|
28
|
+
defer { Bridge.removeEventSink(sink) }
|
|
29
|
+
DispatchQueue.global().async {
|
|
30
|
+
Bridge.log("MessageChunker: diagnostic")
|
|
31
|
+
}
|
|
32
|
+
wait(for: [delivered], timeout: 2)
|
|
33
|
+
}
|
|
34
|
+
}
|
package/package.json
CHANGED
|
@@ -1485,6 +1485,13 @@ export interface Device {
|
|
|
1485
1485
|
export interface ConnectOptions {
|
|
1486
1486
|
saveAsDefault?: boolean
|
|
1487
1487
|
cancelExistingConnectionAttempt?: boolean
|
|
1488
|
+
/**
|
|
1489
|
+
* iOS Mentra Live only. When true, CoreBluetooth requires ANCS authorization
|
|
1490
|
+
* as part of the connection. Set false when the app does not use notification
|
|
1491
|
+
* relay and must avoid the system ANCS authorization flow. Defaults to true.
|
|
1492
|
+
* Android accepts this option as a no-op.
|
|
1493
|
+
*/
|
|
1494
|
+
requiresAncs?: boolean
|
|
1488
1495
|
}
|
|
1489
1496
|
|
|
1490
1497
|
export type ScanResultsCallback = (devices: Device[]) => void
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import {createScanSession} from "./scanSession"
|
|
2
2
|
import {NativeModule, requireNativeModule} from "expo"
|
|
3
|
+
import {Platform} from "react-native"
|
|
4
|
+
|
|
5
|
+
import {installNativeLogConsole} from "./nativeLogConsole"
|
|
3
6
|
|
|
4
7
|
import {
|
|
5
8
|
BluetoothSettingsUpdate,
|
|
@@ -287,10 +290,12 @@ export type BluetoothSdkInternalModule = BluetoothSdkNativeModule
|
|
|
287
290
|
// This call loads the native module object from the JSI.
|
|
288
291
|
// NativeModule<BluetoothSdkModuleEvents> already extends EventEmitter<BluetoothSdkModuleEvents>
|
|
289
292
|
const NativeBluetoothSdkModule = requireNativeModule<BluetoothSdkNativeModule>("BluetoothSdk")
|
|
293
|
+
installNativeLogConsole(NativeBluetoothSdkModule, Platform.OS)
|
|
290
294
|
|
|
291
295
|
const DEFAULT_CONNECT_OPTIONS: Required<ConnectOptions> = {
|
|
292
296
|
saveAsDefault: true,
|
|
293
297
|
cancelExistingConnectionAttempt: true,
|
|
298
|
+
requiresAncs: true,
|
|
294
299
|
}
|
|
295
300
|
|
|
296
301
|
const DEFAULT_SCAN_TIMEOUT_MS = 15_000
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
type LogSource = {
|
|
2
|
+
addListener(event: "log", listener: (event: {message: string}) => void): {remove(): void}
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
// The native module survives Fast Refresh. Replace its previous subscription
|
|
6
|
+
// when JS reloads this module, rather than printing each event more than once.
|
|
7
|
+
const subscriptionKey = Symbol.for("mentra.bluetooth-sdk.native-log-console")
|
|
8
|
+
type SubscribedLogSource = LogSource & {[subscriptionKey]?: {remove(): void}}
|
|
9
|
+
|
|
10
|
+
const credentialPattern =
|
|
11
|
+
/(?<![A-Za-z0-9])(?:access[_-]?token|refresh[_-]?token|auth[_-]?token|id[_-]?token|client[_-]?secret|api[_-]?key)(?![A-Za-z0-9])|(?<![A-Za-z0-9])(?:token|password|secret|authorization|auth|key)(?![A-Za-z0-9])\s*["']?\s*(?:is\s*)?[:=]\s*["']?\S+|(?<![A-Za-z0-9])bearer(?![A-Za-z0-9])\s+\S+/i
|
|
12
|
+
|
|
13
|
+
export function nativeLogMessage(message: string): string {
|
|
14
|
+
return credentialPattern.test(message) ? "[REDACTED]" : message.slice(0, 16_384)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function installNativeLogConsole(source: SubscribedLogSource, platform: string): void {
|
|
18
|
+
source[subscriptionKey]?.remove()
|
|
19
|
+
source[subscriptionKey] = source.addListener("log", ({message}) => {
|
|
20
|
+
// Resolve console at delivery time so host console interception receives
|
|
21
|
+
// native diagnostics in the same stream as ordinary JS logs.
|
|
22
|
+
console.log(`[native:${platform}]`, nativeLogMessage(message))
|
|
23
|
+
})
|
|
24
|
+
}
|
|
@@ -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.
|
|
16
|
-
"releaseSetId": "mentra-3.2.0-dev.
|
|
17
|
-
"sourceCommit": "
|
|
18
|
-
"otaManifestUrl": "https://github.com/Mentra-Community/MentraOS/releases/download/mentra-builds-v3.2.0/mentra-live-ota-3.2.0-dev.
|
|
19
|
-
"otaManifestSha256": "
|
|
15
|
+
"releaseIdentity": "3.2.0-dev.195",
|
|
16
|
+
"releaseSetId": "mentra-3.2.0-dev.195",
|
|
17
|
+
"sourceCommit": "321202ca96ec1852be19a7230ebcc3593fe87e54",
|
|
18
|
+
"otaManifestUrl": "https://github.com/Mentra-Community/MentraOS/releases/download/mentra-builds-v3.2.0/mentra-live-ota-3.2.0-dev.195.json",
|
|
19
|
+
"otaManifestSha256": "70f26063b2c281b9b046965f6f6d7ca22f8b53242c6c5be02030c5a04c7115e4"
|
|
20
20
|
})
|
|
@@ -22,6 +22,11 @@ export type UseGlassesConnectionOptions = {
|
|
|
22
22
|
autoConnectDefault?: boolean
|
|
23
23
|
defaultDeviceStorage?: DefaultDeviceStorage
|
|
24
24
|
onError?: (error: unknown) => void
|
|
25
|
+
/**
|
|
26
|
+
* Whether iOS should require ANCS authorization when automatically connecting.
|
|
27
|
+
* Defaults to true. Android accepts this option as a no-op.
|
|
28
|
+
*/
|
|
29
|
+
requiresAncs?: boolean
|
|
25
30
|
scanModel?: DeviceModel
|
|
26
31
|
scanTimeoutMs?: number
|
|
27
32
|
}
|
|
@@ -117,8 +122,8 @@ export function useGlassesConnection(
|
|
|
117
122
|
}
|
|
118
123
|
|
|
119
124
|
autoConnectAttemptedRef.current = true
|
|
120
|
-
void connectDefault().catch(() => undefined)
|
|
121
|
-
}, [defaultDevice, options.autoConnectDefault, status.connected])
|
|
125
|
+
void connectDefault({requiresAncs: options.requiresAncs ?? true}).catch(() => undefined)
|
|
126
|
+
}, [defaultDevice, options.autoConnectDefault, options.requiresAncs, status.connected])
|
|
122
127
|
|
|
123
128
|
async function runConnectionAction<T>(
|
|
124
129
|
nextAction: Exclude<GlassesConnectionAction, "idle" | "scanning">,
|
|
@@ -121,6 +121,11 @@ export type UseMentraBluetoothOptions = {
|
|
|
121
121
|
defaultDeviceStorage?: DefaultDeviceStorage
|
|
122
122
|
defaultModel?: DeviceModel
|
|
123
123
|
onError?: (error: unknown) => void
|
|
124
|
+
/**
|
|
125
|
+
* Whether iOS should require ANCS authorization when automatically connecting.
|
|
126
|
+
* Defaults to true. Android accepts this option as a no-op.
|
|
127
|
+
*/
|
|
128
|
+
requiresAncs?: boolean
|
|
124
129
|
scanTimeoutMs?: number
|
|
125
130
|
}
|
|
126
131
|
|
|
@@ -269,6 +274,7 @@ export function useMentraBluetooth(options: UseMentraBluetoothOptions = {}): Men
|
|
|
269
274
|
autoConnectDefault: options.autoConnectDefault,
|
|
270
275
|
defaultDeviceStorage: options.defaultDeviceStorage,
|
|
271
276
|
onError: options.onError,
|
|
277
|
+
requiresAncs: options.requiresAncs,
|
|
272
278
|
scanModel: options.defaultModel ?? DeviceModels.MentraLive,
|
|
273
279
|
scanTimeoutMs: options.scanTimeoutMs,
|
|
274
280
|
})
|