@mentra/bluetooth-sdk 3.2.0-dev.191 → 3.2.0-dev.193

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 (61) hide show
  1. package/README.md +18 -0
  2. package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +1 -3
  3. package/android/src/main/java/com/mentra/bluetoothsdk/GeneratedReleaseMetadata.kt +5 -5
  4. package/android/src/main/java/com/mentra/bluetoothsdk/MentraLocalNetworkModule.kt +1 -1
  5. package/android/src/main/java/com/mentra/bluetoothsdk/debug/BleTraceLogger.kt +1 -1
  6. package/android/src/main/java/com/mentra/bluetoothsdk/otaserver/MentraOtaServerModule.kt +1 -1
  7. package/android/src/main/java/com/mentra/bluetoothsdk/photoreceiver/MentraPhotoReceiverModule.kt +1 -1
  8. package/android/src/main/java/com/mentra/bluetoothsdk/services/PhoneMic.kt +1 -1
  9. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Ar99.java +8 -8
  10. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1.kt +1 -1
  11. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Mach1.kt +2 -2
  12. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.kt +1 -1
  13. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLiveL2capChannel.kt +1 -1
  14. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +1 -1
  15. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/ar99/ota/Ar99OtaManager.java +2 -2
  16. package/android/src/main/java/com/mentra/bluetoothsdk/stt/STTTools.kt +2 -2
  17. package/android/src/main/java/com/mentra/bluetoothsdk/stt/SherpaOnnxTranscriber.kt +1 -1
  18. package/android/src/main/java/com/mentra/bluetoothsdk/stt/VadGateSpeechPolicy.kt +1 -1
  19. package/android/src/main/java/com/mentra/bluetoothsdk/tts/TTSTools.kt +1 -1
  20. package/android/src/main/java/com/mentra/bluetoothsdk/utils/AES.java +7 -8
  21. package/android/src/main/java/com/mentra/bluetoothsdk/utils/BitmapJavaUtils.java +2 -2
  22. package/android/src/main/java/com/mentra/bluetoothsdk/utils/BlePhotoUploadService.java +34 -34
  23. package/android/src/main/java/com/mentra/bluetoothsdk/utils/G1Text.kt +1 -1
  24. package/android/src/main/java/com/mentra/bluetoothsdk/utils/IncidentLogBleUploadService.java +4 -4
  25. package/android/src/main/java/com/mentra/bluetoothsdk/utils/K900ProtocolUtils.java +34 -34
  26. package/android/src/main/java/com/mentra/bluetoothsdk/utils/MessageChunkReassembler.java +10 -10
  27. package/android/src/main/java/com/mentra/bluetoothsdk/utils/MessageChunker.java +6 -6
  28. package/android/src/main/java/com/mentra/bluetoothsdk/utils/NativeLog.java +89 -0
  29. package/android/src/main/java/com/mentra/bluetoothsdk/utils/NexSGCUtils.kt +2 -2
  30. package/android/src/main/java/com/mentra/bluetoothsdk/utils/audio/Ar99OpusPcmDecoder.java +10 -10
  31. package/android/src/main/java/com/mentra/bluetoothsdk/utils/audio/ByteUtilAudioPlayer.java +7 -7
  32. package/android/src/main/java/com/mentra/bluetoothsdk/utils/audio/Lc3Player.java +16 -16
  33. package/android/src/main/java/com/mentra/bluetoothsdk/utils/audio/PCMAudioPlayer.java +26 -26
  34. package/android/src/main/java/com/mentra/bluetoothsdk/utils/audio/PcmStreamPlayer.kt +1 -1
  35. package/android/src/test/java/com/mentra/bluetoothsdk/utils/NativeLogTest.kt +61 -0
  36. package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
  37. package/build/_private/BluetoothSdkModule.js +3 -0
  38. package/build/_private/BluetoothSdkModule.js.map +1 -1
  39. package/build/_private/nativeLogConsole.d.ts +17 -0
  40. package/build/_private/nativeLogConsole.d.ts.map +1 -0
  41. package/build/_private/nativeLogConsole.js +16 -0
  42. package/build/_private/nativeLogConsole.js.map +1 -0
  43. package/build/generated/releaseMetadata.js +5 -5
  44. package/build/generated/releaseMetadata.js.map +1 -1
  45. package/ios/Source/BluetoothSdkDefaults.swift +1 -1
  46. package/ios/Source/Bridge.swift +3 -2
  47. package/ios/Source/GeneratedReleaseMetadata.swift +5 -5
  48. package/ios/Source/internal/BleTraceLogger.swift +3 -0
  49. package/ios/Source/sgcs/G1.swift +1 -1
  50. package/ios/Source/sgcs/MentraLive.swift +8 -8
  51. package/ios/Source/stt/STTTools.swift +1 -1
  52. package/ios/Source/utils/G1Text.swift +1 -1
  53. package/ios/Source/utils/JSCExperiment.swift +1 -2
  54. package/ios/Source/utils/MessageChunkReassembler.swift +6 -6
  55. package/ios/Source/utils/MessageChunker.swift +8 -8
  56. package/ios/Source/utils/Models.swift +1 -1
  57. package/ios/Tests/NativeLogTests.swift +34 -0
  58. package/package.json +1 -1
  59. package/src/_private/BluetoothSdkModule.ts +4 -0
  60. package/src/_private/nativeLogConsole.ts +24 -0
  61. package/src/generated/releaseMetadata.ts +5 -5
@@ -0,0 +1,16 @@
1
+ // The native module survives Fast Refresh. Replace its previous subscription
2
+ // when JS reloads this module, rather than printing each event more than once.
3
+ const subscriptionKey = Symbol.for("mentra.bluetooth-sdk.native-log-console");
4
+ const credentialPattern = /(?<![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;
5
+ export function nativeLogMessage(message) {
6
+ return credentialPattern.test(message) ? "[REDACTED]" : message.slice(0, 16_384);
7
+ }
8
+ export function installNativeLogConsole(source, platform) {
9
+ source[subscriptionKey]?.remove();
10
+ source[subscriptionKey] = source.addListener("log", ({ message }) => {
11
+ // Resolve console at delivery time so host console interception receives
12
+ // native diagnostics in the same stream as ordinary JS logs.
13
+ console.log(`[native:${platform}]`, nativeLogMessage(message));
14
+ });
15
+ }
16
+ //# sourceMappingURL=nativeLogConsole.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nativeLogConsole.js","sourceRoot":"","sources":["../../src/_private/nativeLogConsole.ts"],"names":[],"mappings":"AAIA,6EAA6E;AAC7E,+EAA+E;AAC/E,MAAM,eAAe,GAAG,MAAM,CAAC,GAAG,CAAC,yCAAyC,CAAC,CAAA;AAG7E,MAAM,iBAAiB,GACrB,kSAAkS,CAAA;AAEpS,MAAM,UAAU,gBAAgB,CAAC,OAAe;IAC9C,OAAO,iBAAiB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAA;AAClF,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,MAA2B,EAAE,QAAgB;IACnF,MAAM,CAAC,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;IACjC,MAAM,CAAC,eAAe,CAAC,GAAG,MAAM,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC,EAAC,OAAO,EAAC,EAAE,EAAE;QAChE,yEAAyE;QACzE,6DAA6D;QAC7D,OAAO,CAAC,GAAG,CAAC,WAAW,QAAQ,GAAG,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAA;IAChE,CAAC,CAAC,CAAA;AACJ,CAAC","sourcesContent":["type LogSource = {\n addListener(event: \"log\", listener: (event: {message: string}) => void): {remove(): void}\n}\n\n// The native module survives Fast Refresh. Replace its previous subscription\n// when JS reloads this module, rather than printing each event more than once.\nconst subscriptionKey = Symbol.for(\"mentra.bluetooth-sdk.native-log-console\")\ntype SubscribedLogSource = LogSource & {[subscriptionKey]?: {remove(): void}}\n\nconst credentialPattern =\n /(?<![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\n\nexport function nativeLogMessage(message: string): string {\n return credentialPattern.test(message) ? \"[REDACTED]\" : message.slice(0, 16_384)\n}\n\nexport function installNativeLogConsole(source: SubscribedLogSource, platform: string): void {\n source[subscriptionKey]?.remove()\n source[subscriptionKey] = source.addListener(\"log\", ({message}) => {\n // Resolve console at delivery time so host console interception receives\n // native diagnostics in the same stream as ordinary JS logs.\n console.log(`[native:${platform}]`, nativeLogMessage(message))\n })\n}\n"]}
@@ -1,10 +1,10 @@
1
1
  export const BLUETOOTH_SDK_RELEASE_METADATA = Object.freeze({
2
2
  "schemaVersion": 1,
3
3
  "familyBaseVersion": "3.2.0",
4
- "releaseIdentity": "3.2.0-dev.191",
5
- "releaseSetId": "mentra-3.2.0-dev.191",
6
- "sourceCommit": "3516939c2afd92634e7f3b3f1983f0671c8e1e27",
7
- "otaManifestUrl": "https://github.com/Mentra-Community/MentraOS/releases/download/mentra-builds-v3.2.0/mentra-live-ota-3.2.0-dev.191.json",
8
- "otaManifestSha256": "95911977308516fa70d367a18af66f7075440aa219285ac23d5aa72164d475c4"
4
+ "releaseIdentity": "3.2.0-dev.193",
5
+ "releaseSetId": "mentra-3.2.0-dev.193",
6
+ "sourceCommit": "ca3b4bd4109d80253a3369cbf1ca8f9e86236fba",
7
+ "otaManifestUrl": "https://github.com/Mentra-Community/MentraOS/releases/download/mentra-builds-v3.2.0/mentra-live-ota-3.2.0-dev.193.json",
8
+ "otaManifestSha256": "b5201e1a9d926df220adfbf26cba934948d3f09ed81453c3600770a7618dd997"
9
9
  });
10
10
  //# sourceMappingURL=releaseMetadata.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"releaseMetadata.js","sourceRoot":"","sources":["../../src/generated/releaseMetadata.ts"],"names":[],"mappings":"AAWA,MAAM,CAAC,MAAM,8BAA8B,GAA0C,MAAM,CAAC,MAAM,CAAC;IACjG,eAAe,EAAE,CAAC;IAClB,mBAAmB,EAAE,OAAO;IAC5B,iBAAiB,EAAE,eAAe;IAClC,cAAc,EAAE,sBAAsB;IACtC,cAAc,EAAE,0CAA0C;IAC1D,gBAAgB,EAAE,wHAAwH;IAC1I,mBAAmB,EAAE,kEAAkE;CACxF,CAAC,CAAA","sourcesContent":["/** Generated by release CI. Do not edit in a release checkout. */\nexport interface BluetoothSdkReleaseMetadata {\n schemaVersion: 1\n familyBaseVersion: string | null\n releaseIdentity: string | null\n releaseSetId: string | null\n sourceCommit: string | null\n otaManifestUrl: string | null\n otaManifestSha256: string | null\n}\n\nexport const BLUETOOTH_SDK_RELEASE_METADATA: Readonly<BluetoothSdkReleaseMetadata> = Object.freeze({\n \"schemaVersion\": 1,\n \"familyBaseVersion\": \"3.2.0\",\n \"releaseIdentity\": \"3.2.0-dev.191\",\n \"releaseSetId\": \"mentra-3.2.0-dev.191\",\n \"sourceCommit\": \"3516939c2afd92634e7f3b3f1983f0671c8e1e27\",\n \"otaManifestUrl\": \"https://github.com/Mentra-Community/MentraOS/releases/download/mentra-builds-v3.2.0/mentra-live-ota-3.2.0-dev.191.json\",\n \"otaManifestSha256\": \"95911977308516fa70d367a18af66f7075440aa219285ac23d5aa72164d475c4\"\n})\n"]}
1
+ {"version":3,"file":"releaseMetadata.js","sourceRoot":"","sources":["../../src/generated/releaseMetadata.ts"],"names":[],"mappings":"AAWA,MAAM,CAAC,MAAM,8BAA8B,GAA0C,MAAM,CAAC,MAAM,CAAC;IACjG,eAAe,EAAE,CAAC;IAClB,mBAAmB,EAAE,OAAO;IAC5B,iBAAiB,EAAE,eAAe;IAClC,cAAc,EAAE,sBAAsB;IACtC,cAAc,EAAE,0CAA0C;IAC1D,gBAAgB,EAAE,wHAAwH;IAC1I,mBAAmB,EAAE,kEAAkE;CACxF,CAAC,CAAA","sourcesContent":["/** Generated by release CI. Do not edit in a release checkout. */\nexport interface BluetoothSdkReleaseMetadata {\n schemaVersion: 1\n familyBaseVersion: string | null\n releaseIdentity: string | null\n releaseSetId: string | null\n sourceCommit: string | null\n otaManifestUrl: string | null\n otaManifestSha256: string | null\n}\n\nexport const BLUETOOTH_SDK_RELEASE_METADATA: Readonly<BluetoothSdkReleaseMetadata> = Object.freeze({\n \"schemaVersion\": 1,\n \"familyBaseVersion\": \"3.2.0\",\n \"releaseIdentity\": \"3.2.0-dev.193\",\n \"releaseSetId\": \"mentra-3.2.0-dev.193\",\n \"sourceCommit\": \"ca3b4bd4109d80253a3369cbf1ca8f9e86236fba\",\n \"otaManifestUrl\": \"https://github.com/Mentra-Community/MentraOS/releases/download/mentra-builds-v3.2.0/mentra-live-ota-3.2.0-dev.193.json\",\n \"otaManifestSha256\": \"b5201e1a9d926df220adfbf26cba934948d3f09ed81453c3600770a7618dd997\"\n})\n"]}
@@ -11,7 +11,7 @@ enum BluetoothSdkDefaults {
11
11
  static let voiceActivityDetectionEnabled = false
12
12
  static let loudnessGateEnabled = false
13
13
  private static let infoSdkVersionKey = "MentraBluetoothSdkVersion"
14
- private static let swiftPackageSdkVersion = "3.2.0-dev.191"
14
+ private static let swiftPackageSdkVersion = "3.2.0-dev.193"
15
15
  private static let swiftPackageSdkVersionPlaceholder = "__MENTRA" + "_BLUETOOTH_SDK_VERSION__"
16
16
 
17
17
  private static func normalizedSdkVersion(_ value: String?) -> String? {
@@ -74,6 +74,9 @@ class Bridge {
74
74
  }
75
75
 
76
76
  static func log(_ message: String) {
77
+ // Native diagnostics and the JS console share this event. Do not capture
78
+ // stdout: React Native can write the forwarded message back to stdout.
79
+ NSLog("%@", message)
77
80
  let data = ["message": message]
78
81
  Bridge.sendTypedMessage("log", body: data)
79
82
  }
@@ -664,5 +667,3 @@ class Bridge {
664
667
  return payload
665
668
  }
666
669
  }
667
-
668
-
@@ -3,9 +3,9 @@ import Foundation
3
3
  /// Generated by release CI. Do not edit in a release checkout.
4
4
  enum GeneratedReleaseMetadata {
5
5
  static let familyBaseVersion = "3.2.0"
6
- static let releaseIdentity = "3.2.0-dev.191"
7
- static let releaseSetId = "mentra-3.2.0-dev.191"
8
- static let sourceCommit = "3516939c2afd92634e7f3b3f1983f0671c8e1e27"
9
- static let otaManifestUrl = "https://github.com/Mentra-Community/MentraOS/releases/download/mentra-builds-v3.2.0/mentra-live-ota-3.2.0-dev.191.json"
10
- static let otaManifestSha256 = "95911977308516fa70d367a18af66f7075440aa219285ac23d5aa72164d475c4"
6
+ static let releaseIdentity = "3.2.0-dev.193"
7
+ static let releaseSetId = "mentra-3.2.0-dev.193"
8
+ static let sourceCommit = "ca3b4bd4109d80253a3369cbf1ca8f9e86236fba"
9
+ static let otaManifestUrl = "https://github.com/Mentra-Community/MentraOS/releases/download/mentra-builds-v3.2.0/mentra-live-ota-3.2.0-dev.193.json"
10
+ static let otaManifestSha256 = "b5201e1a9d926df220adfbf26cba934948d3f09ed81453c3600770a7618dd997"
11
11
  }
@@ -84,6 +84,9 @@ enum BleTraceLogger {
84
84
 
85
85
  private static func emit(_ line: String) {
86
86
  os_log("%{public}@", log: log, type: .info, line)
87
+ // Log events are excluded from tracing in Bridge, so forwarding a trace
88
+ // cannot recursively generate another trace.
89
+ Bridge.sendTypedMessage("log", body: ["message": line])
87
90
  }
88
91
 
89
92
  private static func format(
@@ -1677,7 +1677,7 @@ extension G1 {
1677
1677
  Task {
1678
1678
  let success = await setBrightnessRaw(brightnessLevel, autoMode: autoMode)
1679
1679
  if !success {
1680
- NSLog("Failed to set brightness to level \(level)% (mapped to \(mappedLevel))")
1680
+ Bridge.log("Failed to set brightness to level \(level)% (mapped to \(mappedLevel))")
1681
1681
  }
1682
1682
  }
1683
1683
  }
@@ -497,7 +497,7 @@ class BlePhotoUploadService {
497
497
 
498
498
  request.httpBody = body
499
499
 
500
- print("LIVE: Uploading photo to webhook: \(webhookUrl)")
500
+ Bridge.log("LIVE: Uploading photo to webhook: \(webhookUrl)")
501
501
 
502
502
  do {
503
503
  let (data, response) = try await URLSession.shared.data(for: request)
@@ -513,7 +513,7 @@ class BlePhotoUploadService {
513
513
  )
514
514
  }
515
515
 
516
- print("LIVE: Upload successful. Response code: \(httpResponse.statusCode)")
516
+ Bridge.log("LIVE: Upload successful. Response code: \(httpResponse.statusCode)")
517
517
  return String(data: data, encoding: .utf8) ?? ""
518
518
 
519
519
  } catch {
@@ -627,7 +627,7 @@ enum K900ProtocolUtils {
627
627
  // Verify packet has enough data
628
628
  let requiredLength = pos + Int(info.packSize) + LENGTH_FILE_VERIFY + LENGTH_FILE_END
629
629
  if protocolData.count < requiredLength {
630
- print(
630
+ Bridge.log(
631
631
  "K900ProtocolUtils: File packet too short for data. Need: \(requiredLength), Have: \(protocolData.count), packSize=\(info.packSize), pos=\(pos)"
632
632
  )
633
633
  return nil
@@ -656,11 +656,11 @@ enum K900ProtocolUtils {
656
656
  info.isValid = (calculatedVerify == info.verifyCode)
657
657
 
658
658
  if !info.isValid {
659
- print(
659
+ Bridge.log(
660
660
  "K900ProtocolUtils: File packet checksum failed. Expected: \(String(format: "%02X", info.verifyCode)), Calculated: \(String(format: "%02X", calculatedVerify))"
661
661
  )
662
662
  } else if shouldLogFilePacket(info) {
663
- print(
663
+ Bridge.log(
664
664
  "K900ProtocolUtils: File packet extracted successfully: index=\(info.packIndex), size=\(info.packSize), fileName=\(info.fileName)"
665
665
  )
666
666
  }
@@ -732,7 +732,7 @@ private struct FileTransferSession {
732
732
  if isBesLie {
733
733
  // BES lie detected: totalPackets = fileSize / 400
734
734
  newTotalPackets = fileSize / Self.BES_HARDCODED_PACK_SIZE
735
- print(
735
+ Bridge.log(
736
736
  "📦 BES Lie detected! fakeFileSize=\(fileSize), totalPackets=\(newTotalPackets), actualPackSize=\(actualPackSize)"
737
737
  )
738
738
  } else {
@@ -741,7 +741,7 @@ private struct FileTransferSession {
741
741
  }
742
742
 
743
743
  if newTotalPackets != totalPackets {
744
- print(
744
+ Bridge.log(
745
745
  "📦 Recalculating totalPackets: \(totalPackets) -> \(newTotalPackets) (packSize=\(actualPackSize), fileSize=\(fileSize))"
746
746
  )
747
747
  totalPackets = newTotalPackets
@@ -793,7 +793,7 @@ private struct FileTransferSession {
793
793
  // Calculate actual file size by summing all received packet sizes
794
794
  let actualFileSize = receivedPackets.values.reduce(0) { $0 + $1.count }
795
795
 
796
- print(
796
+ Bridge.log(
797
797
  "📦 Assembling file: headerFileSize=\(fileSize), actualFileSize=\(actualFileSize), totalPackets=\(totalPackets)"
798
798
  )
799
799
 
@@ -143,7 +143,7 @@ class STTTools {
143
143
  )
144
144
 
145
145
  if !success || extractionError != nil {
146
- print(
146
+ Bridge.log(
147
147
  "EXTRACTION_ERROR: \(extractionError?.localizedDescription ?? "Failed to extract tar.bz2")"
148
148
  )
149
149
  return false
@@ -2077,7 +2077,7 @@ class G1FontLoader {
2077
2077
  fontMap[character] = FontGlyph(width: width, height: height)
2078
2078
  }
2079
2079
 
2080
- print("Hardcoded font data loaded successfully! \(fontMap.count) glyphs mapped.")
2080
+ Bridge.log("Hardcoded font data loaded successfully! \(fontMap.count) glyphs mapped.")
2081
2081
  }
2082
2082
 
2083
2083
  func getGlyph(_ character: Character) -> FontGlyph {
@@ -62,8 +62,7 @@ private func jlog(_ message: String) {
62
62
  /// is fully booted (RN bridge up, Metro pull done if dev, etc).
63
63
  @objc static func maybeAutoBenchmark() {
64
64
  guard ProcessInfo.processInfo.environment["MENTRA_RUN_JSC_BENCH"] != nil else { return }
65
- os_log("🧪 MENTRA_RUN_JSC_BENCH set — auto-running benchmark in 5s",
66
- log: jscLog, type: .info)
65
+ jlog("🧪 MENTRA_RUN_JSC_BENCH set — auto-running benchmark in 5s")
67
66
  DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + 5.0) {
68
67
  runBenchmark()
69
68
  }
@@ -12,7 +12,7 @@ final class MessageChunkReassembler {
12
12
  cleanupTimedOutSessions()
13
13
 
14
14
  guard fragCount > 0, fragIdx >= 0, fragIdx < fragCount else {
15
- print("MessageChunkReassembler: Dropping invalid binary fragment for msgId \(msgId)")
15
+ Bridge.log("MessageChunkReassembler: Dropping invalid binary fragment for msgId \(msgId)")
16
16
  return nil
17
17
  }
18
18
 
@@ -42,7 +42,7 @@ final class MessageChunkReassembler {
42
42
 
43
43
  let reassembled = session.reassemble()
44
44
  activeBinarySessions.removeValue(forKey: msgId)
45
- print("MessageChunkReassembler: Reassembled \(reassembled.count) bytes from \(fragCount) binary fragments")
45
+ Bridge.log("MessageChunkReassembler: Reassembled \(reassembled.count) bytes from \(fragCount) binary fragments")
46
46
  return reassembled
47
47
  }
48
48
 
@@ -54,12 +54,12 @@ final class MessageChunkReassembler {
54
54
  info.chunkIndex >= 0,
55
55
  info.chunkIndex < info.totalChunks
56
56
  else {
57
- print("MessageChunkReassembler: Dropping invalid chunk metadata for \(info.chunkId)")
57
+ Bridge.log("MessageChunkReassembler: Dropping invalid chunk metadata for \(info.chunkId)")
58
58
  return nil
59
59
  }
60
60
 
61
61
  if let existing = activeSessions[info.chunkId], existing.totalChunks != info.totalChunks {
62
- print(
62
+ Bridge.log(
63
63
  "MessageChunkReassembler: totalChunks mismatch for \(info.chunkId) (expected \(existing.totalChunks), got \(info.totalChunks)); resetting session"
64
64
  )
65
65
  activeSessions.removeValue(forKey: info.chunkId)
@@ -78,7 +78,7 @@ final class MessageChunkReassembler {
78
78
  )
79
79
 
80
80
  guard session.addChunk(index: info.chunkIndex, data: info.data) else {
81
- print("MessageChunkReassembler: Failed to add chunk \(info.chunkIndex) for \(info.chunkId)")
81
+ Bridge.log("MessageChunkReassembler: Failed to add chunk \(info.chunkIndex) for \(info.chunkId)")
82
82
  return nil
83
83
  }
84
84
  if isNewSession {
@@ -91,7 +91,7 @@ final class MessageChunkReassembler {
91
91
 
92
92
  let reassembled = session.reassemble()
93
93
  activeSessions.removeValue(forKey: info.chunkId)
94
- print("MessageChunkReassembler: Reassembled \(reassembled.count) bytes from \(info.totalChunks) chunks")
94
+ Bridge.log("MessageChunkReassembler: Reassembled \(reassembled.count) bytes from \(info.totalChunks) chunks")
95
95
  return reassembled
96
96
  }
97
97
 
@@ -41,7 +41,7 @@ class MessageChunker {
41
41
  let needsChunking = messageBytes > MESSAGE_SIZE_THRESHOLD
42
42
 
43
43
  if needsChunking {
44
- print("MessageChunker: Message size \(messageBytes) exceeds threshold \(MESSAGE_SIZE_THRESHOLD), will chunk")
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
- print("MessageChunker: Failed to convert message to data")
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
- print("MessageChunker: Creating \(chunks.count) chunks for message of size \(totalBytes) bytes using \(chunkSize)-byte UTF-8 slices")
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
- print("MessageChunker: Unable to create K900 chunks within \(MAX_PACKED_CHUNK_SIZE) bytes")
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
- print("MessageChunker: Created chunk \(i)/\(totalChunks - 1) with \(chunkString.data(using: .utf8)?.count ?? 0) bytes")
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
- print("MessageChunker: Chunk \(index) packed to \(packedLength ?? 0) bytes, exceeding \(MAX_PACKED_CHUNK_SIZE)")
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
- print("MessageChunker: Failed to extract chunk info from JSON")
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
- print("MessageChunker: Created \(fragments.count) binary fragments for \(payload.count) bytes")
258
+ Bridge.log("MessageChunker: Created \(fragments.count) binary fragments for \(payload.count) bytes")
259
259
  return fragments
260
260
  }
261
261
 
@@ -13,7 +13,7 @@ struct AiResponseToG1Model {
13
13
  var newScreen: Bool
14
14
  var currentPage: UInt8 {
15
15
  didSet {
16
- print("SET : currentPage :\(currentPage)")
16
+ Bridge.log("SET : currentPage :\(currentPage)")
17
17
  }
18
18
  }
19
19
 
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mentra/bluetooth-sdk",
3
- "version": "3.2.0-dev.191",
3
+ "version": "3.2.0-dev.193",
4
4
  "description": "SDK for communicating with smart glasses",
5
5
  "main": "build/index.js",
6
6
  "react-native": "src/index.ts",
@@ -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,6 +290,7 @@ 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,
@@ -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.191",
16
- "releaseSetId": "mentra-3.2.0-dev.191",
17
- "sourceCommit": "3516939c2afd92634e7f3b3f1983f0671c8e1e27",
18
- "otaManifestUrl": "https://github.com/Mentra-Community/MentraOS/releases/download/mentra-builds-v3.2.0/mentra-live-ota-3.2.0-dev.191.json",
19
- "otaManifestSha256": "95911977308516fa70d367a18af66f7075440aa219285ac23d5aa72164d475c4"
15
+ "releaseIdentity": "3.2.0-dev.193",
16
+ "releaseSetId": "mentra-3.2.0-dev.193",
17
+ "sourceCommit": "ca3b4bd4109d80253a3369cbf1ca8f9e86236fba",
18
+ "otaManifestUrl": "https://github.com/Mentra-Community/MentraOS/releases/download/mentra-builds-v3.2.0/mentra-live-ota-3.2.0-dev.193.json",
19
+ "otaManifestSha256": "b5201e1a9d926df220adfbf26cba934948d3f09ed81453c3600770a7618dd997"
20
20
  })