@mentra/bluetooth-sdk 0.1.21-beta.3 → 3.1.0-dev.4
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 +38 -2
- package/android/build.gradle +7 -4
- package/android/lc3Lib/build.gradle +18 -4
- package/android/silero/build.gradle +9 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +16 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +39 -3
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +209 -21
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +2 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/GeneratedReleaseMetadata.kt +11 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +112 -8
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraLocalNetworkModule.kt +93 -12
- package/android/src/main/java/com/mentra/bluetoothsdk/OtaManifest.kt +5 -7
- package/android/src/main/java/com/mentra/bluetoothsdk/ScopedNetworkReadiness.kt +17 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/audio/AudioModels.kt +15 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/events/BluetoothEvents.kt +24 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/net/LocalIpv4.kt +57 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/otaserver/LocalOtaServer.kt +304 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/otaserver/MentraOtaServerModule.kt +120 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/photoreceiver/MentraPhotoReceiverModule.kt +2 -49
- package/android/src/main/java/com/mentra/bluetoothsdk/services/Foreground.kt +61 -30
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/BesOtaHeartbeatGuard.kt +49 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/BesOtaProgressMapper.kt +27 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.kt +1381 -284
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +8 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/status/DeviceStatus.kt +17 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/types/DeviceModels.kt +12 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/BleJsonCompact.java +74 -79
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/K900ProtocolUtils.java +3 -3
- package/android/src/test/java/com/mentra/bluetoothsdk/OtaStartResponsePolicyTest.kt +30 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/ScopedNetworkReadinessTest.kt +39 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/otaserver/LocalOtaServerTest.kt +82 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/services/ForegroundServiceTypeTest.kt +69 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/BesOtaHeartbeatGuardTest.kt +73 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/BesOtaProgressMapperTest.kt +67 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/MentraLiveGattCharacteristicsTest.kt +39 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/utils/BleJsonCompactTest.java +148 -7
- package/build/BluetoothSdk.types.d.ts +63 -2
- package/build/BluetoothSdk.types.d.ts.map +1 -1
- package/build/BluetoothSdk.types.js.map +1 -1
- package/build/_internal.d.ts +2 -0
- package/build/_internal.d.ts.map +1 -1
- package/build/_internal.js +2 -0
- package/build/_internal.js.map +1 -1
- package/build/_private/BluetoothSdkModule.d.ts +4 -1
- package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
- package/build/_private/BluetoothSdkModule.js +1 -1
- package/build/_private/BluetoothSdkModule.js.map +1 -1
- package/build/_private/MentraLocalNetworkModule.d.ts +1 -0
- package/build/_private/MentraLocalNetworkModule.d.ts.map +1 -1
- package/build/_private/MentraLocalNetworkModule.js.map +1 -1
- package/build/_private/sdkOtaManifest.d.ts +3 -12
- package/build/_private/sdkOtaManifest.d.ts.map +1 -1
- package/build/_private/sdkOtaManifest.js +5 -17
- package/build/_private/sdkOtaManifest.js.map +1 -1
- package/build/generated/releaseMetadata.d.ts +12 -0
- package/build/generated/releaseMetadata.d.ts.map +1 -0
- package/build/generated/releaseMetadata.js +10 -0
- package/build/generated/releaseMetadata.js.map +1 -0
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +10 -2
- package/build/index.js.map +1 -1
- package/build/ota-server/MentraOtaServer.types.d.ts +14 -0
- package/build/ota-server/MentraOtaServer.types.d.ts.map +1 -0
- package/build/ota-server/MentraOtaServer.types.js +2 -0
- package/build/ota-server/MentraOtaServer.types.js.map +1 -0
- package/build/ota-server/MentraOtaServerModule.d.ts +21 -0
- package/build/ota-server/MentraOtaServerModule.d.ts.map +1 -0
- package/build/ota-server/MentraOtaServerModule.js +3 -0
- package/build/ota-server/MentraOtaServerModule.js.map +1 -0
- package/build/ota-server/MentraOtaServerModule.web.d.ts +10 -0
- package/build/ota-server/MentraOtaServerModule.web.d.ts.map +1 -0
- package/build/ota-server/MentraOtaServerModule.web.js +12 -0
- package/build/ota-server/MentraOtaServerModule.web.js.map +1 -0
- package/build/ota-server/index.d.ts +3 -0
- package/build/ota-server/index.d.ts.map +1 -0
- package/build/ota-server/index.js +3 -0
- package/build/ota-server/index.js.map +1 -0
- package/expo-module.config.json +3 -2
- package/ios/BackgroundOtaArtifactDownloader.swift +125 -0
- package/ios/BluetoothSdkModule.swift +16 -2
- package/ios/LocalIPv4.swift +150 -0
- package/ios/LocalOtaServer.swift +309 -0
- package/ios/MentraBluetoothSDK.podspec +5 -1
- package/ios/MentraOtaServerModule.swift +147 -0
- package/ios/MentraPhotoReceiverModule.swift +1 -81
- package/ios/Source/BluetoothSdkDefaults.swift +4 -1
- package/ios/Source/Bridge.swift +47 -3
- package/ios/Source/DeviceManager.swift +146 -16
- package/ios/Source/DeviceStore.swift +2 -0
- package/ios/Source/GeneratedReleaseMetadata.swift +11 -0
- package/ios/Source/MentraBluetoothSDK.swift +74 -6
- package/ios/Source/OtaManifest.swift +5 -7
- package/ios/Source/audio/AudioModels.swift +17 -0
- package/ios/Source/camera/CameraModels.swift +1 -0
- package/ios/Source/events/BluetoothEvents.swift +27 -0
- package/ios/Source/sgcs/MentraLive.swift +498 -77
- package/ios/Source/sgcs/SGCManager.swift +11 -0
- package/ios/Source/status/DeviceStatus.swift +23 -0
- package/ios/Source/types/DeviceModels.swift +23 -2
- package/ios/Source/utils/BleJsonCompact.swift +73 -71
- package/package.json +5 -1
- package/plugin/build/withIos.d.ts +3 -1
- package/plugin/build/withIos.js +22 -10
- package/scripts/verify-release-package.mjs +34 -0
- package/scripts/write-release-metadata.mjs +154 -0
- package/scripts/write-release-metadata.test.mjs +29 -0
- package/src/BluetoothSdk.types.ts +74 -3
- package/src/_internal.ts +2 -0
- package/src/_private/BluetoothSdkModule.ts +5 -5
- package/src/_private/MentraLocalNetworkModule.ts +1 -1
- package/src/_private/sdkOtaManifest.ts +5 -17
- package/src/generated/releaseMetadata.ts +20 -0
- package/src/index.ts +19 -2
- package/src/ota-server/MentraOtaServer.types.ts +10 -0
- package/src/ota-server/MentraOtaServerModule.ts +23 -0
- package/src/ota-server/MentraOtaServerModule.web.ts +17 -0
- package/src/ota-server/index.ts +2 -0
|
@@ -68,6 +68,7 @@ protocol SGCManager {
|
|
|
68
68
|
func stopStream()
|
|
69
69
|
func sendStreamKeepAlive(_ message: [String: Any])
|
|
70
70
|
func startVideoRecording(requestId: String, save: Bool, sound: Bool)
|
|
71
|
+
func queryVideoRecordingStatus(requestId: String)
|
|
71
72
|
/// Start video recording with optional per-recording resolution/fps. A width,
|
|
72
73
|
/// height, or fps of 0 means "use the device's saved button-video default".
|
|
73
74
|
/// Defaulted in an extension to delegate to the basic recording path; devices
|
|
@@ -189,6 +190,7 @@ protocol SGCManager {
|
|
|
189
190
|
func sendWifiCredentials(_ ssid: String, _ password: String)
|
|
190
191
|
func forgetWifiNetwork(_ ssid: String)
|
|
191
192
|
func sendHotspotState(_ enabled: Bool)
|
|
193
|
+
func sendWifiAdbState(_ enabled: Bool)
|
|
192
194
|
func sendOtaStart(otaVersionUrl: String?)
|
|
193
195
|
func sendOtaQueryStatus()
|
|
194
196
|
func sendSetSystemTime(_ timestampMs: Int64)
|
|
@@ -327,6 +329,10 @@ extension SGCManager {
|
|
|
327
329
|
startVideoRecording(requestId: requestId, save: save, sound: sound)
|
|
328
330
|
}
|
|
329
331
|
|
|
332
|
+
func queryVideoRecordingStatus(requestId _: String) {
|
|
333
|
+
Bridge.log("SGC: queryVideoRecordingStatus operation not supported")
|
|
334
|
+
}
|
|
335
|
+
|
|
330
336
|
func stopVideoRecording(requestId: String, webhookUrl _: String?, authToken _: String?) {
|
|
331
337
|
stopVideoRecording(requestId: requestId)
|
|
332
338
|
}
|
|
@@ -378,6 +384,11 @@ extension SGCManager {
|
|
|
378
384
|
Bridge.log("SGC: sendSetSystemTime not supported")
|
|
379
385
|
}
|
|
380
386
|
|
|
387
|
+
/// Default no-op; Mentra Live overrides to enable/disable Wi-Fi ADB.
|
|
388
|
+
func sendWifiAdbState(_: Bool) {
|
|
389
|
+
Bridge.log("SGC: sendWifiAdbState not supported")
|
|
390
|
+
}
|
|
391
|
+
|
|
381
392
|
// MARK: - Default DeviceStore-backed property implementations
|
|
382
393
|
|
|
383
394
|
var fullyBooted: Bool {
|
|
@@ -182,6 +182,10 @@ struct GlassesStatus: CustomStringConvertible {
|
|
|
182
182
|
stringValue(values, "bluetoothMacAddress") ?? ""
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
+
var wifiMacAddress: String {
|
|
186
|
+
stringValue(values, "wifiMacAddress") ?? ""
|
|
187
|
+
}
|
|
188
|
+
|
|
185
189
|
var leftMacAddress: String {
|
|
186
190
|
stringValue(values, "leftMacAddress") ?? ""
|
|
187
191
|
}
|
|
@@ -206,6 +210,10 @@ struct GlassesStatus: CustomStringConvertible {
|
|
|
206
210
|
stringValue(values, "appVersion") ?? ""
|
|
207
211
|
}
|
|
208
212
|
|
|
213
|
+
var hotspotOtaVersion: Int {
|
|
214
|
+
intValue(values["hotspotOtaVersion"]) ?? 0
|
|
215
|
+
}
|
|
216
|
+
|
|
209
217
|
var bluetoothName: String {
|
|
210
218
|
stringValue(values, "bluetoothName") ?? ""
|
|
211
219
|
}
|
|
@@ -359,6 +367,7 @@ public struct VersionInfoResult: CustomStringConvertible {
|
|
|
359
367
|
public let systemTimeMs: Int?
|
|
360
368
|
public let otaVersionUrl: String
|
|
361
369
|
public let appVersion: String
|
|
370
|
+
public let hotspotOtaVersion: Int
|
|
362
371
|
|
|
363
372
|
init(status: GlassesStatus) {
|
|
364
373
|
androidVersion = status.androidVersion
|
|
@@ -369,6 +378,7 @@ public struct VersionInfoResult: CustomStringConvertible {
|
|
|
369
378
|
systemTimeMs = intValue(status.values["systemTimeMs"])
|
|
370
379
|
otaVersionUrl = status.otaVersionUrl
|
|
371
380
|
appVersion = status.appVersion
|
|
381
|
+
hotspotOtaVersion = status.hotspotOtaVersion
|
|
372
382
|
}
|
|
373
383
|
|
|
374
384
|
init(values: [String: Any]) {
|
|
@@ -380,6 +390,10 @@ public struct VersionInfoResult: CustomStringConvertible {
|
|
|
380
390
|
systemTimeMs = intValue(values["systemTimeMs"]) ?? intValue(values["system_time_ms"])
|
|
381
391
|
otaVersionUrl = stringValue(values, "otaVersionUrl", "ota_version_url") ?? ""
|
|
382
392
|
appVersion = stringValue(values, "appVersion", "app_version") ?? ""
|
|
393
|
+
hotspotOtaVersion =
|
|
394
|
+
intValue(values["hotspotOtaVersion"])
|
|
395
|
+
?? intValue(values["hotspot_ota_version"])
|
|
396
|
+
?? 0
|
|
383
397
|
}
|
|
384
398
|
|
|
385
399
|
public var dictionary: [String: Any] {
|
|
@@ -391,6 +405,7 @@ public struct VersionInfoResult: CustomStringConvertible {
|
|
|
391
405
|
"buildNumber": buildNumber,
|
|
392
406
|
"otaVersionUrl": otaVersionUrl,
|
|
393
407
|
"appVersion": appVersion,
|
|
408
|
+
"hotspotOtaVersion": hotspotOtaVersion,
|
|
394
409
|
]
|
|
395
410
|
if let systemTimeMs {
|
|
396
411
|
values["systemTimeMs"] = systemTimeMs
|
|
@@ -661,6 +676,10 @@ struct GlassesStatusUpdate: CustomStringConvertible {
|
|
|
661
676
|
optionalStringValue(values, "bluetoothMacAddress")
|
|
662
677
|
}
|
|
663
678
|
|
|
679
|
+
var wifiMacAddress: String? {
|
|
680
|
+
optionalStringValue(values, "wifiMacAddress")
|
|
681
|
+
}
|
|
682
|
+
|
|
664
683
|
var leftMacAddress: String? {
|
|
665
684
|
optionalStringValue(values, "leftMacAddress")
|
|
666
685
|
}
|
|
@@ -685,6 +704,10 @@ struct GlassesStatusUpdate: CustomStringConvertible {
|
|
|
685
704
|
optionalStringValue(values, "appVersion")
|
|
686
705
|
}
|
|
687
706
|
|
|
707
|
+
var hotspotOtaVersion: Int? {
|
|
708
|
+
optionalIntValue(values, "hotspotOtaVersion")
|
|
709
|
+
}
|
|
710
|
+
|
|
688
711
|
var bluetoothName: String? {
|
|
689
712
|
optionalStringValue(values, "bluetoothName")
|
|
690
713
|
}
|
|
@@ -89,6 +89,9 @@ public struct Device: Identifiable, Equatable, CustomStringConvertible {
|
|
|
89
89
|
public let rssi: Int?
|
|
90
90
|
/// Stable app-facing scan-result key. Do not parse; use typed fields instead.
|
|
91
91
|
public let id: String
|
|
92
|
+
public let pairingMode: Bool?
|
|
93
|
+
public let pairingCode: String?
|
|
94
|
+
public let securePairingCapable: Bool?
|
|
92
95
|
|
|
93
96
|
public init(
|
|
94
97
|
model: DeviceModel,
|
|
@@ -96,7 +99,10 @@ public struct Device: Identifiable, Equatable, CustomStringConvertible {
|
|
|
96
99
|
identifier: String? = nil,
|
|
97
100
|
projectName: String? = nil,
|
|
98
101
|
rssi: Int? = nil,
|
|
99
|
-
id: String? = nil
|
|
102
|
+
id: String? = nil,
|
|
103
|
+
pairingMode: Bool? = nil,
|
|
104
|
+
pairingCode: String? = nil,
|
|
105
|
+
securePairingCapable: Bool? = nil
|
|
100
106
|
) {
|
|
101
107
|
self.model = model
|
|
102
108
|
self.name = name
|
|
@@ -104,6 +110,9 @@ public struct Device: Identifiable, Equatable, CustomStringConvertible {
|
|
|
104
110
|
self.projectName = projectName
|
|
105
111
|
self.rssi = rssi
|
|
106
112
|
self.id = id ?? identifier.flatMap { $0.isEmpty ? nil : $0 } ?? "\(model.deviceType):\(name)"
|
|
113
|
+
self.pairingMode = pairingMode
|
|
114
|
+
self.pairingCode = pairingCode
|
|
115
|
+
self.securePairingCapable = securePairingCapable
|
|
107
116
|
}
|
|
108
117
|
|
|
109
118
|
public var description: String {
|
|
@@ -125,6 +134,15 @@ public struct Device: Identifiable, Equatable, CustomStringConvertible {
|
|
|
125
134
|
if let rssi {
|
|
126
135
|
values["rssi"] = rssi
|
|
127
136
|
}
|
|
137
|
+
if let pairingMode {
|
|
138
|
+
values["pairingMode"] = pairingMode
|
|
139
|
+
}
|
|
140
|
+
if let pairingCode, !pairingCode.isEmpty {
|
|
141
|
+
values["pairingCode"] = pairingCode
|
|
142
|
+
}
|
|
143
|
+
if let securePairingCapable {
|
|
144
|
+
values["securePairingCapable"] = securePairingCapable
|
|
145
|
+
}
|
|
128
146
|
return values
|
|
129
147
|
}
|
|
130
148
|
|
|
@@ -140,7 +158,10 @@ public struct Device: Identifiable, Equatable, CustomStringConvertible {
|
|
|
140
158
|
identifier: identifier,
|
|
141
159
|
projectName: projectName,
|
|
142
160
|
rssi: rssi,
|
|
143
|
-
id: stringValue(values, "id")
|
|
161
|
+
id: stringValue(values, "id"),
|
|
162
|
+
pairingMode: boolValue(values, "pairingMode"),
|
|
163
|
+
pairingCode: stringValue(values, "pairingCode").flatMap { $0.isEmpty ? nil : $0 },
|
|
164
|
+
securePairingCapable: boolValue(values, "securePairingCapable")
|
|
144
165
|
)
|
|
145
166
|
}
|
|
146
167
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import Foundation
|
|
2
2
|
|
|
3
|
-
/// BLE Wire Protocol v2 JSON compaction
|
|
3
|
+
/// BLE Wire Protocol v2 JSON compaction with lossless fallback for ambiguous payloads.
|
|
4
4
|
enum BleJsonCompact {
|
|
5
5
|
static let keyResolvedConfigHash = "rch"
|
|
6
6
|
|
|
@@ -64,8 +64,6 @@ enum BleJsonCompact {
|
|
|
64
64
|
}()
|
|
65
65
|
|
|
66
66
|
private static var sessionConnectEpochMs: Int64 = 0
|
|
67
|
-
private static var resolvedConfigSent = false
|
|
68
|
-
private static var currentSessionResolvedConfigHash: String?
|
|
69
67
|
private static var resolvedConfigByHash: [String: [String: Any]] = [:]
|
|
70
68
|
|
|
71
69
|
private static let highRoiMessageTypes: Set<String> = [
|
|
@@ -103,8 +101,6 @@ enum BleJsonCompact {
|
|
|
103
101
|
|
|
104
102
|
static func resetSession() {
|
|
105
103
|
sessionConnectEpochMs = 0
|
|
106
|
-
resolvedConfigSent = false
|
|
107
|
-
currentSessionResolvedConfigHash = nil
|
|
108
104
|
resolvedConfigByHash.removeAll()
|
|
109
105
|
}
|
|
110
106
|
|
|
@@ -125,7 +121,7 @@ enum BleJsonCompact {
|
|
|
125
121
|
return json
|
|
126
122
|
}
|
|
127
123
|
let messageType = extractMessageType(json)
|
|
128
|
-
if !shouldCompactOutbound(messageType) {
|
|
124
|
+
if !shouldCompactOutbound(messageType) || !canCompactLosslessly(json, topLevel: true) {
|
|
129
125
|
return json
|
|
130
126
|
}
|
|
131
127
|
return compactObject(json, topLevel: true)
|
|
@@ -170,48 +166,81 @@ enum BleJsonCompact {
|
|
|
170
166
|
return decode(json)
|
|
171
167
|
}
|
|
172
168
|
|
|
173
|
-
|
|
174
|
-
|
|
169
|
+
/// Compact JSON reserves short keys and enum integers. Fall back to verbose JSON whenever an
|
|
170
|
+
/// otherwise valid future payload already uses one of those wire representations.
|
|
171
|
+
private static func canCompactLosslessly(
|
|
172
|
+
_ input: [String: Any],
|
|
173
|
+
topLevel: Bool
|
|
174
|
+
) -> Bool {
|
|
175
175
|
let messageType = stringValue(input["type"]) ?? stringValue(input["t"]) ?? ""
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
if
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
176
|
+
for (key, value) in input {
|
|
177
|
+
if shortToLong[key] != nil
|
|
178
|
+
|| (topLevel && key == keyResolvedConfigHash)
|
|
179
|
+
|| isAmbiguousEnumValue(key: key, value: value, messageType: messageType)
|
|
180
|
+
{
|
|
181
|
+
return false
|
|
182
|
+
}
|
|
183
|
+
if let nested = value as? [String: Any],
|
|
184
|
+
!canCompactLosslessly(nested, topLevel: false)
|
|
185
|
+
{
|
|
186
|
+
return false
|
|
187
|
+
}
|
|
188
|
+
if let array = value as? [Any], !canCompactArrayLosslessly(array) {
|
|
189
|
+
return false
|
|
189
190
|
}
|
|
190
191
|
}
|
|
192
|
+
return true
|
|
193
|
+
}
|
|
191
194
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
let
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
195
|
+
private static func canCompactArrayLosslessly(_ array: [Any]) -> Bool {
|
|
196
|
+
for value in array {
|
|
197
|
+
if let nested = value as? [String: Any],
|
|
198
|
+
!canCompactLosslessly(nested, topLevel: false)
|
|
199
|
+
{
|
|
200
|
+
return false
|
|
201
|
+
}
|
|
202
|
+
if let nestedArray = value as? [Any],
|
|
203
|
+
!canCompactArrayLosslessly(nestedArray)
|
|
204
|
+
{
|
|
205
|
+
return false
|
|
199
206
|
}
|
|
200
|
-
output[shortKey] = compacted
|
|
201
207
|
}
|
|
208
|
+
return true
|
|
209
|
+
}
|
|
202
210
|
|
|
203
|
-
|
|
204
|
-
|
|
211
|
+
private static func isAmbiguousEnumValue(
|
|
212
|
+
key: String,
|
|
213
|
+
value: Any,
|
|
214
|
+
messageType: String
|
|
215
|
+
) -> Bool {
|
|
216
|
+
guard let number = value as? NSNumber,
|
|
217
|
+
CFGetTypeID(number) != CFBooleanGetTypeID()
|
|
218
|
+
else {
|
|
219
|
+
return false
|
|
205
220
|
}
|
|
206
|
-
return
|
|
221
|
+
return key == "kind"
|
|
222
|
+
|| key == "source"
|
|
223
|
+
|| key == "aeStateName"
|
|
224
|
+
|| (key == "status" && messageType == "photo_status")
|
|
207
225
|
}
|
|
208
226
|
|
|
209
|
-
private static func
|
|
210
|
-
|
|
227
|
+
private static func compactObject(_ input: [String: Any], topLevel: Bool) -> [String: Any] {
|
|
228
|
+
var output: [String: Any] = [:]
|
|
229
|
+
let messageType = stringValue(input["type"]) ?? stringValue(input["t"]) ?? ""
|
|
230
|
+
|
|
231
|
+
for (key, value) in input {
|
|
232
|
+
if let compacted = compactValue(longKey: key, value: value, messageType: messageType) {
|
|
233
|
+
// Legacy peers treat top-level `ts` as a session-relative delta. Preserve the
|
|
234
|
+
// verbose key so an absolute timestamp remains absolute across mixed versions.
|
|
235
|
+
let wireKey = topLevel && key == "timestamp" ? key : (longToShort[key] ?? key)
|
|
236
|
+
output[wireKey] = compacted
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return output
|
|
211
240
|
}
|
|
212
241
|
|
|
213
242
|
private static func compactValue(longKey: String, value: Any, messageType: String) -> Any? {
|
|
214
|
-
if value is NSNull { return
|
|
243
|
+
if value is NSNull { return NSNull() }
|
|
215
244
|
if let nested = value as? [String: Any] {
|
|
216
245
|
return compactObject(nested, topLevel: false)
|
|
217
246
|
}
|
|
@@ -239,35 +268,6 @@ enum BleJsonCompact {
|
|
|
239
268
|
return value
|
|
240
269
|
}
|
|
241
270
|
|
|
242
|
-
private static func shouldOmit(_ value: Any) -> Bool {
|
|
243
|
-
if value is NSNull { return true }
|
|
244
|
-
if let boolValue = value as? Bool, !boolValue { return true }
|
|
245
|
-
if let dict = value as? [String: Any], dict.isEmpty { return true }
|
|
246
|
-
if let array = value as? [Any], array.isEmpty { return true }
|
|
247
|
-
return false
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
private static func compactTimestamp(_ output: inout [String: Any]) {
|
|
251
|
-
let tsKey: String?
|
|
252
|
-
if output["timestamp"] != nil {
|
|
253
|
-
tsKey = "timestamp"
|
|
254
|
-
} else if output["ts"] != nil {
|
|
255
|
-
tsKey = "ts"
|
|
256
|
-
} else {
|
|
257
|
-
tsKey = nil
|
|
258
|
-
}
|
|
259
|
-
guard let key = tsKey else { return }
|
|
260
|
-
let absolute = int64Value(output[key]) ?? 0
|
|
261
|
-
guard absolute > 0 else { return }
|
|
262
|
-
if sessionConnectEpochMs <= 0 {
|
|
263
|
-
sessionConnectEpochMs = absolute
|
|
264
|
-
}
|
|
265
|
-
output["ts"] = absolute - sessionConnectEpochMs
|
|
266
|
-
if key != "ts" {
|
|
267
|
-
output.removeValue(forKey: key)
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
271
|
private static func expandObject(_ input: [String: Any], topLevel: Bool) -> [String: Any] {
|
|
272
272
|
var output: [String: Any] = [:]
|
|
273
273
|
let messageType = stringValue(input["type"]) ?? stringValue(input["t"]) ?? ""
|
|
@@ -278,10 +278,9 @@ enum BleJsonCompact {
|
|
|
278
278
|
continue
|
|
279
279
|
}
|
|
280
280
|
let longKey = shortToLong[key] ?? key
|
|
281
|
-
|
|
282
|
-
|
|
281
|
+
if let expanded = expandValue(longKey: longKey, value: value, messageType: messageType) {
|
|
282
|
+
output[longKey] = expanded
|
|
283
283
|
}
|
|
284
|
-
output[longKey] = expanded
|
|
285
284
|
}
|
|
286
285
|
|
|
287
286
|
if topLevel {
|
|
@@ -296,8 +295,6 @@ enum BleJsonCompact {
|
|
|
296
295
|
guard let resolvedConfig = output["resolvedConfig"] as? [String: Any] else { return }
|
|
297
296
|
let hash = hashConfig(resolvedConfig)
|
|
298
297
|
resolvedConfigByHash[hash] = resolvedConfig
|
|
299
|
-
currentSessionResolvedConfigHash = hash
|
|
300
|
-
resolvedConfigSent = true
|
|
301
298
|
}
|
|
302
299
|
|
|
303
300
|
private static func expandResolvedConfigHash(_ output: inout [String: Any]) {
|
|
@@ -312,7 +309,7 @@ enum BleJsonCompact {
|
|
|
312
309
|
}
|
|
313
310
|
|
|
314
311
|
private static func expandValue(longKey: String, value: Any, messageType: String) -> Any? {
|
|
315
|
-
if value is NSNull { return
|
|
312
|
+
if value is NSNull { return NSNull() }
|
|
316
313
|
if let nested = value as? [String: Any] {
|
|
317
314
|
return expandObject(nested, topLevel: false)
|
|
318
315
|
}
|
|
@@ -340,7 +337,12 @@ enum BleJsonCompact {
|
|
|
340
337
|
}
|
|
341
338
|
|
|
342
339
|
private static func expandTimestamp(_ output: inout [String: Any]) {
|
|
343
|
-
guard output["timestamp"] == nil, let
|
|
340
|
+
guard output["timestamp"] == nil, let timestamp = output["ts"] else { return }
|
|
341
|
+
guard let delta = int64Value(timestamp) else {
|
|
342
|
+
output["timestamp"] = timestamp
|
|
343
|
+
output.removeValue(forKey: "ts")
|
|
344
|
+
return
|
|
345
|
+
}
|
|
344
346
|
let absolute = sessionConnectEpochMs > 0 ? sessionConnectEpochMs + delta : delta
|
|
345
347
|
output["timestamp"] = absolute
|
|
346
348
|
output.removeValue(forKey: "ts")
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mentra/bluetooth-sdk",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.1.0-dev.4",
|
|
4
4
|
"description": "SDK for communicating with smart glasses",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"react-native": "src/index.ts",
|
|
@@ -73,9 +73,12 @@
|
|
|
73
73
|
"build:plugin": "expo-module build plugin",
|
|
74
74
|
"clean": "expo-module clean",
|
|
75
75
|
"test": "expo-module test",
|
|
76
|
+
"test:release-metadata": "node --test scripts/*.test.mjs",
|
|
76
77
|
"prepare": "expo-module prepare",
|
|
77
78
|
"prepack": "node scripts/inject-ios-sdk-version.mjs",
|
|
78
79
|
"postpack": "node scripts/inject-ios-sdk-version.mjs --restore",
|
|
80
|
+
"write:release-metadata": "node scripts/write-release-metadata.mjs",
|
|
81
|
+
"verify:release-package": "node scripts/verify-release-package.mjs",
|
|
79
82
|
"prepublishOnly": "expo-module prepublishOnly",
|
|
80
83
|
"lint": "expo-module lint",
|
|
81
84
|
"expo-module": "expo-module",
|
|
@@ -105,6 +108,7 @@
|
|
|
105
108
|
"access": "public",
|
|
106
109
|
"registry": "https://registry.npmjs.org/"
|
|
107
110
|
},
|
|
111
|
+
"dependencies": {},
|
|
108
112
|
"devDependencies": {
|
|
109
113
|
"@types/node": "^25.9.3",
|
|
110
114
|
"expo-module-scripts": "^55.0.2",
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
import { type ConfigPlugin } from "expo/config-plugins";
|
|
1
|
+
import { IOSConfig, type ConfigPlugin } from "expo/config-plugins";
|
|
2
2
|
import { type BluetoothSdkPluginProps } from "./index";
|
|
3
|
+
export declare const INFO_SDK_VERSION = "MentraBluetoothSdkVersion";
|
|
4
|
+
export declare function applyBluetoothSdkInfoPlist(infoPlist: IOSConfig.InfoPlist, props: BluetoothSdkPluginProps | undefined): IOSConfig.InfoPlist;
|
|
3
5
|
export declare const withIosConfiguration: ConfigPlugin<BluetoothSdkPluginProps>;
|
package/plugin/build/withIos.js
CHANGED
|
@@ -3,7 +3,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.withIosConfiguration = void 0;
|
|
6
|
+
exports.withIosConfiguration = exports.INFO_SDK_VERSION = void 0;
|
|
7
|
+
exports.applyBluetoothSdkInfoPlist = applyBluetoothSdkInfoPlist;
|
|
7
8
|
const child_process_1 = require("child_process");
|
|
8
9
|
const fs_1 = __importDefault(require("fs"));
|
|
9
10
|
const path_1 = __importDefault(require("path"));
|
|
@@ -11,6 +12,7 @@ const config_plugins_1 = require("expo/config-plugins");
|
|
|
11
12
|
const BLUETOOTH_SDK_EXPO_ADAPTER_ENV = "MENTRA_BLUETOOTH_SDK_INCLUDE_EXPO_ADAPTER";
|
|
12
13
|
const BLUETOOTH_SDK_EXPO_ADAPTER_LINE = `ENV['${BLUETOOTH_SDK_EXPO_ADAPTER_ENV}'] ||= '1'`;
|
|
13
14
|
const INFO_ANALYTICS_DISABLED = "MentraBluetoothSdkAnalyticsDisabled";
|
|
15
|
+
exports.INFO_SDK_VERSION = "MentraBluetoothSdkVersion";
|
|
14
16
|
const STALE_INFO_POSTHOG_API_KEY = "MentraBluetoothSdkPostHogApiKey";
|
|
15
17
|
const STALE_INFO_POSTHOG_HOST = "MentraBluetoothSdkPostHogHost";
|
|
16
18
|
const ensureBluetoothSdkExpoAdapterPodEnv = (podfile) => {
|
|
@@ -70,15 +72,25 @@ function resolveAnalyticsProps(props) {
|
|
|
70
72
|
disabled,
|
|
71
73
|
};
|
|
72
74
|
}
|
|
73
|
-
function
|
|
75
|
+
function applyBluetoothSdkInfoPlist(infoPlist, props) {
|
|
76
|
+
const packageJson = require("../../package.json");
|
|
77
|
+
const sdkVersion = typeof packageJson.version === "string" ? packageJson.version.trim() : "";
|
|
78
|
+
if (!sdkVersion) {
|
|
79
|
+
throw new Error("@mentra/bluetooth-sdk package.json is missing a version");
|
|
80
|
+
}
|
|
81
|
+
const analytics = resolveAnalyticsProps(props);
|
|
82
|
+
delete infoPlist[INFO_ANALYTICS_DISABLED];
|
|
83
|
+
delete infoPlist[STALE_INFO_POSTHOG_API_KEY];
|
|
84
|
+
delete infoPlist[STALE_INFO_POSTHOG_HOST];
|
|
85
|
+
infoPlist[exports.INFO_SDK_VERSION] = sdkVersion;
|
|
86
|
+
if (analytics.disabled !== undefined) {
|
|
87
|
+
infoPlist[INFO_ANALYTICS_DISABLED] = analytics.disabled;
|
|
88
|
+
}
|
|
89
|
+
return infoPlist;
|
|
90
|
+
}
|
|
91
|
+
function withBluetoothSdkInfoPlist(config, props) {
|
|
74
92
|
return (0, config_plugins_1.withInfoPlist)(config, (config) => {
|
|
75
|
-
|
|
76
|
-
delete config.modResults[INFO_ANALYTICS_DISABLED];
|
|
77
|
-
delete config.modResults[STALE_INFO_POSTHOG_API_KEY];
|
|
78
|
-
delete config.modResults[STALE_INFO_POSTHOG_HOST];
|
|
79
|
-
if (analytics.disabled !== undefined) {
|
|
80
|
-
config.modResults[INFO_ANALYTICS_DISABLED] = analytics.disabled;
|
|
81
|
-
}
|
|
93
|
+
config.modResults = applyBluetoothSdkInfoPlist(config.modResults, props);
|
|
82
94
|
return config;
|
|
83
95
|
});
|
|
84
96
|
}
|
|
@@ -87,7 +99,7 @@ const withIosConfiguration = (config, props) => {
|
|
|
87
99
|
config.modResults.contents = ensureBluetoothSdkExpoAdapterPodEnv(config.modResults.contents);
|
|
88
100
|
return config;
|
|
89
101
|
});
|
|
90
|
-
config =
|
|
102
|
+
config = withBluetoothSdkInfoPlist(config, props);
|
|
91
103
|
if (props?.node) {
|
|
92
104
|
config = withXcodeEnvLocal(config);
|
|
93
105
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {readFileSync} from "node:fs"
|
|
3
|
+
import path from "node:path"
|
|
4
|
+
|
|
5
|
+
function option(name) {
|
|
6
|
+
const index = process.argv.indexOf(`--${name}`)
|
|
7
|
+
if (index < 0 || !process.argv[index + 1]) throw new Error(`Missing --${name}`)
|
|
8
|
+
return process.argv[index + 1]
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const packageRoot = path.resolve(option("package-root"))
|
|
12
|
+
const releaseIdentity = option("release-identity")
|
|
13
|
+
const otaManifestUrl = option("ota-manifest-url")
|
|
14
|
+
const otaManifestSha256 = option("ota-manifest-sha256")
|
|
15
|
+
const packageJson = JSON.parse(readFileSync(path.join(packageRoot, "package.json"), "utf8"))
|
|
16
|
+
if (packageJson.version !== releaseIdentity) {
|
|
17
|
+
throw new Error(`Packed SDK version ${JSON.stringify(packageJson.version)} does not match ${releaseIdentity}`)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const files = [
|
|
21
|
+
"src/generated/releaseMetadata.ts",
|
|
22
|
+
"build/generated/releaseMetadata.js",
|
|
23
|
+
"android/src/main/java/com/mentra/bluetoothsdk/GeneratedReleaseMetadata.kt",
|
|
24
|
+
"ios/Source/GeneratedReleaseMetadata.swift",
|
|
25
|
+
]
|
|
26
|
+
for (const relativePath of files) {
|
|
27
|
+
const contents = readFileSync(path.join(packageRoot, relativePath), "utf8")
|
|
28
|
+
if (!contents.includes(otaManifestUrl)) throw new Error(`${relativePath} does not contain the OTA manifest URL`)
|
|
29
|
+
if (!contents.includes(otaManifestSha256))
|
|
30
|
+
throw new Error(`${relativePath} does not contain the OTA manifest SHA-256`)
|
|
31
|
+
if (!contents.includes(releaseIdentity)) throw new Error(`${relativePath} does not contain the release identity`)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.log(`Verified packed Bluetooth SDK ${releaseIdentity} metadata across JavaScript, Android, and iOS`)
|