@mentra/bluetooth-sdk 3.3.0-dev.323 → 3.3.0-dev.327
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 +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/GeneratedReleaseMetadata.kt +5 -5
- package/android/src/main/java/com/mentra/bluetoothsdk/VersionInfoResponseAccumulator.kt +13 -2
- package/android/src/test/java/com/mentra/bluetoothsdk/VersionInfoResponseAccumulatorTest.kt +71 -0
- package/build/generated/releaseMetadata.js +5 -5
- package/build/generated/releaseMetadata.js.map +1 -1
- package/ios/Source/BluetoothSdkDefaults.swift +1 -1
- package/ios/Source/GeneratedReleaseMetadata.swift +5 -5
- package/ios/Source/requests/VersionInfoResponseAccumulator.swift +11 -1
- package/ios/Tests/VersionInfoResponseAccumulatorTests.swift +65 -0
- package/package.json +2 -2
- package/src/generated/releaseMetadata.ts +5 -5
package/README.md
CHANGED
|
@@ -507,7 +507,7 @@ Disabling restores normal hotspot-only behavior: the server rebinds to an active
|
|
|
507
507
|
Both the native SDK and ASG client need this command; older glasses firmware times out without claiming success. The client app must have local-network access and permit cleartext HTTP to the local device under its platform/MDM policy.
|
|
508
508
|
|
|
509
509
|
WiFi, hotspot, and version-info commands resolve from the ASG response path, not local dispatch:
|
|
510
|
-
`requestWifiScan()` resolves from the ASG `wifi_scan_result` completion response with the updated scan list, including `[]` when no networks are found. Intermediate `wifi_scan_result` events can arrive with `scanComplete: false` while the glasses stream discovered networks; the final event uses `scanComplete: true`. If older glasses stream non-empty scan results but never send the completion event, the request resolves with the accumulated scan list when the request times out. `sendWifiCredentials()` resolves when the requested SSID is connected, `forgetWifiNetwork()` returns a semantic `WifiForgetResult`, `getSavedWifiNetworks()` returns a semantic `SavedWifiNetworksResult`, `setHotspotState()` resolves when the requested hotspot state is reported, and `requestVersionInfo()` waits for all chunks declared by the correlated response's `chunkCount`, `chunkIndex`, and `final` metadata. Legacy single-message responses complete immediately; legacy chunked responses complete on `version_info_3` after `version_info_1`. Missing final chunks time out rather than returning partial data after a quiet period.
|
|
510
|
+
`requestWifiScan()` resolves from the ASG `wifi_scan_result` completion response with the updated scan list, including `[]` when no networks are found. Intermediate `wifi_scan_result` events can arrive with `scanComplete: false` while the glasses stream discovered networks; the final event uses `scanComplete: true`. If older glasses stream non-empty scan results but never send the completion event, the request resolves with the accumulated scan list when the request times out. `sendWifiCredentials()` resolves when the requested SSID is connected, `forgetWifiNetwork()` returns a semantic `WifiForgetResult`, `getSavedWifiNetworks()` returns a semantic `SavedWifiNetworksResult`, `setHotspotState()` resolves when the requested hotspot state is reported, and `requestVersionInfo()` waits for all chunks declared by the correlated response's `chunkCount`, `chunkIndex`, and `final` metadata. Legacy single-message responses complete immediately. Factory ASG27 declares build 27 in `version_info_1` and completes on its final `version_info_2` carrying the OTA URL; other legacy chunked responses complete on `version_info_3` after `version_info_1`. Missing final chunks time out rather than returning partial data after a quiet period.
|
|
511
511
|
|
|
512
512
|
Current Mentra Live builds advertise `wifiForgetResultVersion` and `savedWifiNetworksVersion` in `version_info_1`. Native queues calls while those capabilities are unknown, sharing one 15-second deadline for negotiation and response. A discovery timeout rejects and never guesses that the glasses are legacy. Only version 1 is supported: modern commands carry `protocolVersion: 1`, a nonempty request ID, and the glasses process session ID; legacy forget commands omit all three fields. Unknown or malformed versions fail closed. Correlated results must match the complete tuple and (for forget) exact SSID; unrelated `wifi_status` events never settle a modern request. Session changes and disconnects reject pending WiFi work instead of allowing stale responses to cross sessions.
|
|
513
513
|
|
|
@@ -3,9 +3,9 @@ package com.mentra.bluetoothsdk
|
|
|
3
3
|
/** Generated by release CI. Do not edit in a release checkout. */
|
|
4
4
|
internal object GeneratedReleaseMetadata {
|
|
5
5
|
const val FAMILY_BASE_VERSION: String = "3.3.0"
|
|
6
|
-
const val RELEASE_IDENTITY: String = "3.3.0-dev.
|
|
7
|
-
const val RELEASE_SET_ID: String = "mentra-3.3.0-dev.
|
|
8
|
-
const val SOURCE_COMMIT: String = "
|
|
9
|
-
const val OTA_MANIFEST_URL: String = "https://artifactscdn.mentraglass.com/Mentra-Community/MentraOS/releases/mentra-builds-v3.3.0/mentra-live-ota-3.3.0-dev.
|
|
10
|
-
const val OTA_MANIFEST_SHA256: String = "
|
|
6
|
+
const val RELEASE_IDENTITY: String = "3.3.0-dev.327"
|
|
7
|
+
const val RELEASE_SET_ID: String = "mentra-3.3.0-dev.327"
|
|
8
|
+
const val SOURCE_COMMIT: String = "d9cf3febb3ef48f69e1c4306d71dcf53b6a96923"
|
|
9
|
+
const val OTA_MANIFEST_URL: String = "https://artifactscdn.mentraglass.com/Mentra-Community/MentraOS/releases/mentra-builds-v3.3.0/mentra-live-ota-3.3.0-dev.327.json"
|
|
10
|
+
const val OTA_MANIFEST_SHA256: String = "d117092ab5e92ed3ae76d2cc62530a977bcb18ba5aac98b8e56b5dd100773af0"
|
|
11
11
|
}
|
|
@@ -6,13 +6,14 @@ internal sealed interface VersionInfoAccumulatorOutcome {
|
|
|
6
6
|
data class Complete(val result: VersionInfoResult) : VersionInfoAccumulatorOutcome
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
/** One request, explicit modern completion, or the
|
|
9
|
+
/** One request, explicit modern completion, or the legacy firmware's terminal chunk. */
|
|
10
10
|
internal class VersionInfoResponseAccumulator(private val expectedRequestId: String) {
|
|
11
11
|
private val values = mutableMapOf<String, Any>()
|
|
12
12
|
private val chunks = mutableMapOf<Int, Map<String, Any>>()
|
|
13
13
|
private var count: Int? = null
|
|
14
14
|
private var sid: String? = null
|
|
15
15
|
private var legacyStarted = false
|
|
16
|
+
private var legacyTwoChunkResponse = false
|
|
16
17
|
private var completed = false
|
|
17
18
|
|
|
18
19
|
fun accept(event: Map<String, Any>): VersionInfoAccumulatorOutcome {
|
|
@@ -41,10 +42,20 @@ internal class VersionInfoResponseAccumulator(private val expectedRequestId: Str
|
|
|
41
42
|
if (count != null) return VersionInfoAccumulatorOutcome.Ignored
|
|
42
43
|
when (event[RESPONSE_CHUNK_KEY] as? String ?: "version_info") {
|
|
43
44
|
"version_info" -> { values.clear(); merge(event); return finish() }
|
|
44
|
-
"version_info_1" -> {
|
|
45
|
+
"version_info_1" -> {
|
|
46
|
+
values.clear()
|
|
47
|
+
legacyStarted = true
|
|
48
|
+
legacyTwoChunkResponse = event["buildNumber"] == "27"
|
|
49
|
+
merge(event)
|
|
50
|
+
}
|
|
45
51
|
"version_info_2" -> {
|
|
46
52
|
if (!legacyStarted) return VersionInfoAccumulatorOutcome.Ignored
|
|
53
|
+
// Factory ASG27 sends only chunks 1 and 2. Its second chunk carries the
|
|
54
|
+
// OTA URL and legacy firmware version; ASG31+ moves firmware to chunk 3.
|
|
47
55
|
merge(event)
|
|
56
|
+
if (legacyTwoChunkResponse && values["buildNumber"] == "27" &&
|
|
57
|
+
!(event["otaVersionUrl"] as? String).isNullOrBlank()
|
|
58
|
+
) return finish()
|
|
48
59
|
}
|
|
49
60
|
"version_info_3" -> {
|
|
50
61
|
if (!legacyStarted) return VersionInfoAccumulatorOutcome.Ignored
|
|
@@ -102,6 +102,77 @@ class VersionInfoResponseAccumulatorTest {
|
|
|
102
102
|
.isEqualTo(VersionInfoAccumulatorOutcome.Waiting)
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
@Test
|
|
106
|
+
fun factoryAsg27CompletesAfterItsTwoChunks() {
|
|
107
|
+
// Exact January wire shape after Bridge normalization; firmware can be unknown.
|
|
108
|
+
for (firmware in listOf("", "17.26.1.13")) {
|
|
109
|
+
val accumulator = VersionInfoResponseAccumulator("request-1")
|
|
110
|
+
accumulator.accept(chunk("version_info_1", null, "buildNumber" to "27", "appVersion" to "27.0"))
|
|
111
|
+
val complete = accumulator.accept(chunk("version_info_2", null,
|
|
112
|
+
"buildNumber" to "", "otaVersionUrl" to "https://ota.example/live_version.json", "firmwareVersion" to firmware,
|
|
113
|
+
)) as VersionInfoAccumulatorOutcome.Complete
|
|
114
|
+
assertThat(complete.result.buildNumber).isEqualTo("27")
|
|
115
|
+
assertThat(complete.result.otaVersionUrl).isEqualTo("https://ota.example/live_version.json")
|
|
116
|
+
assertThat(complete.result.firmwareVersion).isEqualTo(firmware)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
@Test
|
|
121
|
+
fun otherLegacyBuildsStillRequireTheirThirdChunk() {
|
|
122
|
+
for (build in listOf("", "26", "28", "31", "37", "42")) {
|
|
123
|
+
val accumulator = VersionInfoResponseAccumulator("request-1")
|
|
124
|
+
accumulator.accept(chunk("version_info_1", null, "buildNumber" to build))
|
|
125
|
+
assertThat(accumulator.accept(chunk("version_info_2", null,
|
|
126
|
+
"otaVersionUrl" to "https://ota.example/version.json",
|
|
127
|
+
))).isEqualTo(VersionInfoAccumulatorOutcome.Waiting)
|
|
128
|
+
assertThat(accumulator.accept(chunk("version_info_3", null, "besFirmwareVersion" to "new")))
|
|
129
|
+
.isInstanceOf(VersionInfoAccumulatorOutcome.Complete::class.java)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
@Test
|
|
134
|
+
fun factorySecondChunkCannotCompleteWithoutFirstChunkAndOtaUrl() {
|
|
135
|
+
val accumulator = VersionInfoResponseAccumulator("request-1")
|
|
136
|
+
val second = chunk("version_info_2", null, "otaVersionUrl" to "https://ota.example/version.json")
|
|
137
|
+
assertThat(accumulator.accept(second)).isEqualTo(VersionInfoAccumulatorOutcome.Ignored)
|
|
138
|
+
accumulator.accept(chunk("version_info_1", null, "buildNumber" to "27"))
|
|
139
|
+
for (values in listOf(emptyMap(), mapOf("otaVersionUrl" to ""), mapOf("otaVersionUrl" to " \n"))) {
|
|
140
|
+
assertThat(accumulator.accept(chunk("version_info_2", null) + values))
|
|
141
|
+
.isEqualTo(VersionInfoAccumulatorOutcome.Waiting)
|
|
142
|
+
}
|
|
143
|
+
assertThat(accumulator.accept(second)).isInstanceOf(VersionInfoAccumulatorOutcome.Complete::class.java)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
@Test
|
|
147
|
+
fun secondChunkCannotChangeBuildIntoFactoryProtocol() {
|
|
148
|
+
val accumulator = VersionInfoResponseAccumulator("request-1")
|
|
149
|
+
accumulator.accept(chunk("version_info_1", null, "buildNumber" to "31"))
|
|
150
|
+
assertThat(accumulator.accept(chunk("version_info_2", null,
|
|
151
|
+
"buildNumber" to "27", "otaVersionUrl" to "https://ota.example/version.json",
|
|
152
|
+
))).isEqualTo(VersionInfoAccumulatorOutcome.Waiting)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
@Test
|
|
156
|
+
fun repeatedFirstChunkClearsFactoryCompletionRule() {
|
|
157
|
+
val accumulator = VersionInfoResponseAccumulator("request-1")
|
|
158
|
+
accumulator.accept(chunk("version_info_1", null, "buildNumber" to "27"))
|
|
159
|
+
accumulator.accept(chunk("version_info_1", null, "buildNumber" to "31"))
|
|
160
|
+
assertThat(accumulator.accept(chunk("version_info_2", null,
|
|
161
|
+
"otaVersionUrl" to "https://ota.example/version.json",
|
|
162
|
+
))).isEqualTo(VersionInfoAccumulatorOutcome.Waiting)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
@Test
|
|
166
|
+
fun factoryBuildCannotBypassModernCorrelation() {
|
|
167
|
+
val accumulator = VersionInfoResponseAccumulator("request-1")
|
|
168
|
+
accumulator.accept(chunk("version_info_1", "request-1", "buildNumber" to "27"))
|
|
169
|
+
assertThat(accumulator.accept(chunk("version_info_2", null,
|
|
170
|
+
"otaVersionUrl" to "https://ota.example/version.json",
|
|
171
|
+
))).isEqualTo(VersionInfoAccumulatorOutcome.Ignored)
|
|
172
|
+
assertThat(accumulator.accept(chunk("version_info_3", "request-1", "besFirmwareVersion" to "new")))
|
|
173
|
+
.isInstanceOf(VersionInfoAccumulatorOutcome.Complete::class.java)
|
|
174
|
+
}
|
|
175
|
+
|
|
105
176
|
@Test
|
|
106
177
|
fun modernFinalChunkWaitsForMissingFirstChunk() {
|
|
107
178
|
val accumulator = VersionInfoResponseAccumulator("request-1")
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
export const BLUETOOTH_SDK_RELEASE_METADATA = Object.freeze({
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"familyBaseVersion": "3.3.0",
|
|
4
|
-
"releaseIdentity": "3.3.0-dev.
|
|
5
|
-
"releaseSetId": "mentra-3.3.0-dev.
|
|
6
|
-
"sourceCommit": "
|
|
7
|
-
"otaManifestUrl": "https://artifactscdn.mentraglass.com/Mentra-Community/MentraOS/releases/mentra-builds-v3.3.0/mentra-live-ota-3.3.0-dev.
|
|
8
|
-
"otaManifestSha256": "
|
|
4
|
+
"releaseIdentity": "3.3.0-dev.327",
|
|
5
|
+
"releaseSetId": "mentra-3.3.0-dev.327",
|
|
6
|
+
"sourceCommit": "d9cf3febb3ef48f69e1c4306d71dcf53b6a96923",
|
|
7
|
+
"otaManifestUrl": "https://artifactscdn.mentraglass.com/Mentra-Community/MentraOS/releases/mentra-builds-v3.3.0/mentra-live-ota-3.3.0-dev.327.json",
|
|
8
|
+
"otaManifestSha256": "d117092ab5e92ed3ae76d2cc62530a977bcb18ba5aac98b8e56b5dd100773af0"
|
|
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,iIAAiI;IACnJ,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.3.0\",\n \"releaseIdentity\": \"3.3.0-dev.
|
|
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,iIAAiI;IACnJ,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.3.0\",\n \"releaseIdentity\": \"3.3.0-dev.327\",\n \"releaseSetId\": \"mentra-3.3.0-dev.327\",\n \"sourceCommit\": \"d9cf3febb3ef48f69e1c4306d71dcf53b6a96923\",\n \"otaManifestUrl\": \"https://artifactscdn.mentraglass.com/Mentra-Community/MentraOS/releases/mentra-builds-v3.3.0/mentra-live-ota-3.3.0-dev.327.json\",\n \"otaManifestSha256\": \"d117092ab5e92ed3ae76d2cc62530a977bcb18ba5aac98b8e56b5dd100773af0\"\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.3.0-dev.
|
|
14
|
+
private static let swiftPackageSdkVersion = "3.3.0-dev.327"
|
|
15
15
|
private static let swiftPackageSdkVersionPlaceholder = "__MENTRA" + "_BLUETOOTH_SDK_VERSION__"
|
|
16
16
|
|
|
17
17
|
private static func normalizedSdkVersion(_ value: String?) -> String? {
|
|
@@ -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.3.0"
|
|
6
|
-
static let releaseIdentity = "3.3.0-dev.
|
|
7
|
-
static let releaseSetId = "mentra-3.3.0-dev.
|
|
8
|
-
static let sourceCommit = "
|
|
9
|
-
static let otaManifestUrl = "https://artifactscdn.mentraglass.com/Mentra-Community/MentraOS/releases/mentra-builds-v3.3.0/mentra-live-ota-3.3.0-dev.
|
|
10
|
-
static let otaManifestSha256 = "
|
|
6
|
+
static let releaseIdentity = "3.3.0-dev.327"
|
|
7
|
+
static let releaseSetId = "mentra-3.3.0-dev.327"
|
|
8
|
+
static let sourceCommit = "d9cf3febb3ef48f69e1c4306d71dcf53b6a96923"
|
|
9
|
+
static let otaManifestUrl = "https://artifactscdn.mentraglass.com/Mentra-Community/MentraOS/releases/mentra-builds-v3.3.0/mentra-live-ota-3.3.0-dev.327.json"
|
|
10
|
+
static let otaManifestSha256 = "d117092ab5e92ed3ae76d2cc62530a977bcb18ba5aac98b8e56b5dd100773af0"
|
|
11
11
|
}
|
|
@@ -7,7 +7,7 @@ enum VersionInfoAccumulatorOutcome {
|
|
|
7
7
|
case complete(VersionInfoResult)
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
/// One request, explicit modern completion, or the
|
|
10
|
+
/// One request, explicit modern completion, or the legacy firmware's terminal chunk.
|
|
11
11
|
final class VersionInfoResponseAccumulator {
|
|
12
12
|
static let responseChunkKey = "_responseChunk"
|
|
13
13
|
static let responseRequestIdKey = "_responseRequestId"
|
|
@@ -23,6 +23,7 @@ final class VersionInfoResponseAccumulator {
|
|
|
23
23
|
private var count: Int?
|
|
24
24
|
private var sid: String?
|
|
25
25
|
private var legacyStarted = false
|
|
26
|
+
private var legacyTwoChunkResponse = false
|
|
26
27
|
private var completed = false
|
|
27
28
|
|
|
28
29
|
init(expectedRequestId: String) {
|
|
@@ -63,10 +64,19 @@ final class VersionInfoResponseAccumulator {
|
|
|
63
64
|
case "version_info_1":
|
|
64
65
|
values.removeAll()
|
|
65
66
|
legacyStarted = true
|
|
67
|
+
legacyTwoChunkResponse = event["buildNumber"] as? String == "27"
|
|
66
68
|
merge(event)
|
|
67
69
|
case "version_info_2":
|
|
68
70
|
guard legacyStarted else { return .ignored }
|
|
71
|
+
// Factory ASG27 sends only chunks 1 and 2. Its second chunk carries the
|
|
72
|
+
// OTA URL and legacy firmware version; ASG31+ moves firmware to chunk 3.
|
|
69
73
|
merge(event)
|
|
74
|
+
if legacyTwoChunkResponse, values["buildNumber"] as? String == "27",
|
|
75
|
+
let otaUrl = event["otaVersionUrl"] as? String,
|
|
76
|
+
!otaUrl.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
|
77
|
+
{
|
|
78
|
+
return finish()
|
|
79
|
+
}
|
|
70
80
|
case "version_info_3":
|
|
71
81
|
guard legacyStarted else { return .ignored }
|
|
72
82
|
merge(event)
|
|
@@ -117,6 +117,71 @@ final class VersionInfoResponseAccumulatorTests: XCTestCase {
|
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
func testFactoryAsg27CompletesAfterItsTwoChunks() {
|
|
121
|
+
// Exact January wire shape after Bridge normalization; firmware can be unknown.
|
|
122
|
+
for firmware in ["", "17.26.1.13"] {
|
|
123
|
+
let accumulator = VersionInfoResponseAccumulator(expectedRequestId: "request-1")
|
|
124
|
+
_ = accumulator.accept(chunk("version_info_1", nil, ["buildNumber": "27", "appVersion": "27.0"]))
|
|
125
|
+
guard case let .complete(result) = accumulator.accept(chunk("version_info_2", nil, [
|
|
126
|
+
"buildNumber": "", "otaVersionUrl": "https://ota.example/live_version.json", "firmwareVersion": firmware,
|
|
127
|
+
])) else { return XCTFail("ASG27 has no third chunk") }
|
|
128
|
+
XCTAssertEqual(result.buildNumber, "27")
|
|
129
|
+
XCTAssertEqual(result.otaVersionUrl, "https://ota.example/live_version.json")
|
|
130
|
+
XCTAssertEqual(result.firmwareVersion, firmware)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
func testOtherLegacyBuildsStillRequireTheirThirdChunk() {
|
|
135
|
+
for build in ["", "26", "28", "31", "37", "42"] {
|
|
136
|
+
let accumulator = VersionInfoResponseAccumulator(expectedRequestId: "request-1")
|
|
137
|
+
_ = accumulator.accept(chunk("version_info_1", nil, ["buildNumber": build]))
|
|
138
|
+
guard case .waiting = accumulator.accept(chunk("version_info_2", nil, [
|
|
139
|
+
"otaVersionUrl": "https://ota.example/version.json",
|
|
140
|
+
])) else { return XCTFail("Unknown or three-chunk builds must wait: " + build) }
|
|
141
|
+
guard case .complete = accumulator.accept(chunk("version_info_3", nil, ["besFirmwareVersion": "new"]))
|
|
142
|
+
else { return XCTFail("Legacy third chunk must complete") }
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
func testFactorySecondChunkCannotCompleteWithoutFirstChunkAndOtaUrl() {
|
|
147
|
+
let accumulator = VersionInfoResponseAccumulator(expectedRequestId: "request-1")
|
|
148
|
+
let second = chunk("version_info_2", nil, ["otaVersionUrl": "https://ota.example/version.json"])
|
|
149
|
+
guard case .ignored = accumulator.accept(second) else { return XCTFail("Trailing chunk is stale") }
|
|
150
|
+
_ = accumulator.accept(chunk("version_info_1", nil, ["buildNumber": "27"]))
|
|
151
|
+
for values in [[:], ["otaVersionUrl": ""], ["otaVersionUrl": " \n"]] as [[String: Any]] {
|
|
152
|
+
guard case .waiting = accumulator.accept(chunk("version_info_2", nil, values))
|
|
153
|
+
else { return XCTFail("Incomplete second chunk cannot complete") }
|
|
154
|
+
}
|
|
155
|
+
guard case .complete = accumulator.accept(second) else { return XCTFail("Actual terminal chunk") }
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
func testSecondChunkCannotChangeBuildIntoFactoryProtocol() {
|
|
159
|
+
let accumulator = VersionInfoResponseAccumulator(expectedRequestId: "request-1")
|
|
160
|
+
_ = accumulator.accept(chunk("version_info_1", nil, ["buildNumber": "31"]))
|
|
161
|
+
guard case .waiting = accumulator.accept(chunk("version_info_2", nil, [
|
|
162
|
+
"buildNumber": "27", "otaVersionUrl": "https://ota.example/version.json",
|
|
163
|
+
])) else { return XCTFail("Only the first chunk declares the protocol") }
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
func testRepeatedFirstChunkClearsFactoryCompletionRule() {
|
|
167
|
+
let accumulator = VersionInfoResponseAccumulator(expectedRequestId: "request-1")
|
|
168
|
+
_ = accumulator.accept(chunk("version_info_1", nil, ["buildNumber": "27"]))
|
|
169
|
+
_ = accumulator.accept(chunk("version_info_1", nil, ["buildNumber": "31"]))
|
|
170
|
+
guard case .waiting = accumulator.accept(chunk("version_info_2", nil, [
|
|
171
|
+
"otaVersionUrl": "https://ota.example/version.json",
|
|
172
|
+
])) else { return XCTFail("The latest first chunk determines the protocol") }
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
func testFactoryBuildCannotBypassModernCorrelation() {
|
|
176
|
+
let accumulator = VersionInfoResponseAccumulator(expectedRequestId: "request-1")
|
|
177
|
+
_ = accumulator.accept(chunk("version_info_1", "request-1", ["buildNumber": "27"]))
|
|
178
|
+
guard case .ignored = accumulator.accept(chunk("version_info_2", nil, [
|
|
179
|
+
"otaVersionUrl": "https://ota.example/version.json",
|
|
180
|
+
])) else { return XCTFail("Uncorrelated terminal chunk cannot complete a modern request") }
|
|
181
|
+
guard case .complete = accumulator.accept(chunk("version_info_3", "request-1", ["besFirmwareVersion": "new"]))
|
|
182
|
+
else { return XCTFail("Modern completeness metadata remains authoritative") }
|
|
183
|
+
}
|
|
184
|
+
|
|
120
185
|
func testModernFinalChunkWaitsForMissingFirstChunk() {
|
|
121
186
|
let accumulator = VersionInfoResponseAccumulator(expectedRequestId: "request-1")
|
|
122
187
|
guard case .waiting = accumulator.accept(chunk("version_info_3", "request-1", ["besFirmwareVersion": "new"])) else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mentra/bluetooth-sdk",
|
|
3
|
-
"version": "3.3.0-dev.
|
|
3
|
+
"version": "3.3.0-dev.327",
|
|
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.3.0-dev.
|
|
117
|
+
"@mentra/cloud-protocol": "3.3.0-dev.327"
|
|
118
118
|
},
|
|
119
119
|
"devDependencies": {
|
|
120
120
|
"@types/node": "^25.9.3",
|
|
@@ -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.3.0",
|
|
15
|
-
"releaseIdentity": "3.3.0-dev.
|
|
16
|
-
"releaseSetId": "mentra-3.3.0-dev.
|
|
17
|
-
"sourceCommit": "
|
|
18
|
-
"otaManifestUrl": "https://artifactscdn.mentraglass.com/Mentra-Community/MentraOS/releases/mentra-builds-v3.3.0/mentra-live-ota-3.3.0-dev.
|
|
19
|
-
"otaManifestSha256": "
|
|
15
|
+
"releaseIdentity": "3.3.0-dev.327",
|
|
16
|
+
"releaseSetId": "mentra-3.3.0-dev.327",
|
|
17
|
+
"sourceCommit": "d9cf3febb3ef48f69e1c4306d71dcf53b6a96923",
|
|
18
|
+
"otaManifestUrl": "https://artifactscdn.mentraglass.com/Mentra-Community/MentraOS/releases/mentra-builds-v3.3.0/mentra-live-ota-3.3.0-dev.327.json",
|
|
19
|
+
"otaManifestSha256": "d117092ab5e92ed3ae76d2cc62530a977bcb18ba5aac98b8e56b5dd100773af0"
|
|
20
20
|
})
|