@mentra/engine 3.2.0-dev.195 → 3.2.0-dev.206
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/build/generated/releaseMetadata.js +5 -5
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/services/DeviceEventRouter.d.ts.map +1 -1
- package/build/services/DeviceEventRouter.js +3 -7
- package/build/services/DeviceEventRouter.js.map +1 -1
- package/build/services/PhoneStreamCoordinator.d.ts +7 -20
- package/build/services/PhoneStreamCoordinator.d.ts.map +1 -1
- package/build/services/PhoneStreamCoordinator.js +16 -99
- package/build/services/PhoneStreamCoordinator.js.map +1 -1
- package/build/services/asg/galleryNotices.d.ts +1 -1
- package/build/services/asg/galleryNotices.d.ts.map +1 -1
- package/build/services/asg/galleryNotices.js.map +1 -1
- package/build/services/asg/gallerySyncService.d.ts +14 -0
- package/build/services/asg/gallerySyncService.d.ts.map +1 -1
- package/build/services/asg/gallerySyncService.js +232 -96
- package/build/services/asg/gallerySyncService.js.map +1 -1
- package/build/services/slimStreamStatus.d.ts.map +1 -1
- package/build/services/slimStreamStatus.js +16 -0
- package/build/services/slimStreamStatus.js.map +1 -1
- package/package.json +7 -7
- package/src/generated/releaseMetadata.ts +5 -5
- package/src/services/DeviceEventRouter.ts +5 -9
- package/src/services/PhoneStreamCoordinator.ts +23 -128
- package/src/services/asg/galleryNotices.ts +1 -0
- package/src/services/asg/gallerySyncService.ts +260 -112
- package/src/services/slimStreamStatus.ts +10 -1
- package/build/services/StreamLifecycleController.d.ts +0 -85
- package/build/services/StreamLifecycleController.d.ts.map +0 -1
- package/build/services/StreamLifecycleController.js +0 -173
- package/build/services/StreamLifecycleController.js.map +0 -1
- package/src/services/StreamLifecycleController.ts +0 -243
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
export const ENGINE_RELEASE_METADATA = Object.freeze({
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"familyBaseVersion": "3.2.0",
|
|
4
|
-
"releaseIdentity": "3.2.0-dev.
|
|
5
|
-
"releaseSetId": "mentra-3.2.0-dev.
|
|
6
|
-
"sourceCommit": "
|
|
7
|
-
"otaManifestUrl": "https://github.com/Mentra-Community/MentraOS/releases/download/mentra-builds-v3.2.0/mentra-live-ota-3.2.0-dev.
|
|
8
|
-
"otaManifestSha256": "
|
|
4
|
+
"releaseIdentity": "3.2.0-dev.206",
|
|
5
|
+
"releaseSetId": "mentra-3.2.0-dev.206",
|
|
6
|
+
"sourceCommit": "3c22b92a5a53ce608c087503417ca5b3d16cc926",
|
|
7
|
+
"otaManifestUrl": "https://github.com/Mentra-Community/MentraOS/releases/download/mentra-builds-v3.2.0/mentra-live-ota-3.2.0-dev.206.json",
|
|
8
|
+
"otaManifestSha256": "7bc5e09f2757b2c11da58c93459bb0a554f406dd0ae5b50b58db28b64b92c838"
|
|
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,uBAAuB,GAAoC,MAAM,CAAC,MAAM,CAAC;IACpF,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 EngineReleaseMetadata {\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 ENGINE_RELEASE_METADATA: Readonly<EngineReleaseMetadata> = Object.freeze({\n \"schemaVersion\": 1,\n \"familyBaseVersion\": \"3.2.0\",\n \"releaseIdentity\": \"3.2.0-dev.
|
|
1
|
+
{"version":3,"file":"releaseMetadata.js","sourceRoot":"","sources":["../../src/generated/releaseMetadata.ts"],"names":[],"mappings":"AAWA,MAAM,CAAC,MAAM,uBAAuB,GAAoC,MAAM,CAAC,MAAM,CAAC;IACpF,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 EngineReleaseMetadata {\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 ENGINE_RELEASE_METADATA: Readonly<EngineReleaseMetadata> = Object.freeze({\n \"schemaVersion\": 1,\n \"familyBaseVersion\": \"3.2.0\",\n \"releaseIdentity\": \"3.2.0-dev.206\",\n \"releaseSetId\": \"mentra-3.2.0-dev.206\",\n \"sourceCommit\": \"3c22b92a5a53ce608c087503417ca5b3d16cc926\",\n \"otaManifestUrl\": \"https://github.com/Mentra-Community/MentraOS/releases/download/mentra-builds-v3.2.0/mentra-live-ota-3.2.0-dev.206.json\",\n \"otaManifestSha256\": \"7bc5e09f2757b2c11da58c93459bb0a554f406dd0ae5b50b58db28b64b92c838\"\n})\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DeviceEventRouter.d.ts","sourceRoot":"","sources":["../../src/services/DeviceEventRouter.ts"],"names":[],"mappings":"AAmCA,wBAAgB,sBAAsB,IAAI,IAAI,
|
|
1
|
+
{"version":3,"file":"DeviceEventRouter.d.ts","sourceRoot":"","sources":["../../src/services/DeviceEventRouter.ts"],"names":[],"mappings":"AAmCA,wBAAgB,sBAAsB,IAAI,IAAI,CA0M7C;AAED,wBAAgB,qBAAqB,IAAI,IAAI,CAG5C"}
|
|
@@ -128,18 +128,14 @@ export function startDeviceEventRouter() {
|
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
130
|
}));
|
|
131
|
-
// Phone-owned stream status
|
|
131
|
+
// Phone-owned stream status → the stream coordinator. The Cloud V1 relay
|
|
132
132
|
// for non-owned streams was removed with Cloud V1 app end-of-life.
|
|
133
133
|
subs.push(BluetoothSdk.addListener("stream_status", (event) => {
|
|
134
|
-
if (event.streamId && phoneStreamCoordinator.owns(event.streamId))
|
|
134
|
+
if ((event.streamId && phoneStreamCoordinator.owns(event.streamId)) ||
|
|
135
|
+
(!event.streamId && event.kind === "snapshot" && event.terminal === true)) {
|
|
135
136
|
phoneStreamCoordinator.handleGlassesStatus(event);
|
|
136
137
|
}
|
|
137
138
|
}));
|
|
138
|
-
subs.push(BluetoothSdk.addListener("keep_alive_ack", (event) => {
|
|
139
|
-
if (event.streamId && phoneStreamCoordinator.owns(event.streamId)) {
|
|
140
|
-
phoneStreamCoordinator.handleKeepAliveAck(event);
|
|
141
|
-
}
|
|
142
|
-
}));
|
|
143
139
|
// --- device input → local miniapps (forwardEvent is subscriber-gated; a no-op when
|
|
144
140
|
// no miniapp listens). The v1 SocketComms cloud legs for these were removed with
|
|
145
141
|
// the Cloud V1 ripout. ---
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DeviceEventRouter.js","sourceRoot":"","sources":["../../src/services/DeviceEventRouter.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Device-event router — engine-owned. Subscribes to the inbound native BLE/device\n * events and routes them into the engine runtime: device stores, the process event\n * bus, the photo/stream coordinators, and local miniapps (via forwardEvent).\n *\n * Why this exists: engine owns the stores, coordinators, miniapp runtime, and facades,\n * but it never *subscribed to the device* for most events — the host MantleManager was\n * still the event router the whole runtime secretly depended on. So a bare OEM that\n * imported engine + called engine.start() got a connected runtime with almost no device\n * data flowing in (no miniapp input, dead gallery sync, starved coordinators). This\n * service moves those inbound bridges into engine so ANY host gets them.\n *\n * Scope: the engine legs only. The Cloud V1 relays that used to live beside them in\n * MantleManager (touch→cloud, the cloud-SDK stream/photo legs, etc.) were deleted with\n * Cloud V1 app end-of-life.\n *\n * Started by `engine.start()`. Idempotent.\n */\nimport BluetoothSdk from \"@mentra/bluetooth-sdk/internal\"\nimport {shallow} from \"zustand/shallow\"\n\nimport localMiniappRuntime from \"./LocalMiniappRuntime\"\nimport localSttFallbackCoordinator from \"./LocalSttFallbackCoordinator\"\nimport {phonePhotoCoordinator} from \"./PhonePhotoCoordinator\"\nimport {phoneStreamCoordinator} from \"./PhoneStreamCoordinator\"\nimport {isGlassesConnected} from \"./GlassesReadiness\"\nimport {useGlassesStore} from \"../stores/glasses\"\nimport {useSettingsStore} from \"../stores/settings\"\nimport {useAppStatusStore} from \"../stores/apps\"\nimport {retirePendingSelectionOnPromotion} from \"./PairingIdentity\"\nimport GlobalEventEmitter from \"../utils/GlobalEventEmitter\"\nimport {asgCameraApi} from \"./asg/asgCameraApi\"\n\nlet subs: Array<{remove: () => void}> = []\n\nexport function startDeviceEventRouter(): void {\n if (subs.length) return\n\n // --- device state → engine stores ---\n\n // Standalone WiFi status → glasses store.\n subs.push(\n BluetoothSdk.addListener(\"wifi_status_change\", (event) => {\n const {type: _type, ...wifi} = event\n useGlassesStore.getState().setGlassesInfo({wifi})\n }),\n )\n\n // Forward glasses Wi-Fi to miniapps (session.glasses.onWifi) from the STORE — the\n // single source of truth — so every path converges here: wifi_status_change,\n // onGlassesStatus, and BLE disconnect. Effective connectivity requires the glasses\n // to be connected AND on Wi-Fi, so a disconnect correctly flips `connected` false.\n // `linkConnected` carries the BLE leg on its own so a miniapp can tell \"glasses\n // out of Bluetooth range\" from \"glasses on Bluetooth but off Wi-Fi\" — the two\n // need opposite recoveries (wait vs. open Wi-Fi setup).\n subs.push({\n remove: useGlassesStore.subscribe(\n (s) => {\n const linkConnected = isGlassesConnected(s.connection)\n const connected = linkConnected && s.wifi.state === \"connected\"\n // ssid/localIp describe how to reach the glasses over Wi-Fi, so they are\n // only meaningful under effective connectivity. Gate them on `connected`\n // (not raw wifi.state) so a BLE drop with a stale \"connected\" Wi-Fi leg\n // never hands miniapps an unreachable address.\n return {\n connected,\n linkConnected,\n ssid: linkConnected && s.wifi.state === \"connected\" ? s.wifi.ssid : undefined,\n localIp: linkConnected && s.wifi.state === \"connected\" ? s.wifi.localIp : undefined,\n }\n },\n (wifi) => localMiniappRuntime.forwardEvent(\"glasses_wifi\", wifi),\n {equalityFn: shallow},\n ),\n })\n\n // Incremental battery status → glasses store + local miniapps.\n subs.push(\n BluetoothSdk.addListener(\"battery_status\", (event) => {\n const state = useGlassesStore.getState()\n state.setBatteryInfo(event.level, event.charging, state.caseBatteryLevel, state.caseCharging)\n localMiniappRuntime.forwardEvent(\"glasses_battery_update\", {\n type: \"glasses_battery_update\",\n level: event.level,\n charging: event.charging,\n timestamp: event.timestamp ?? Date.now(),\n })\n }),\n )\n\n // Hotspot status → glasses store + event bus. engine's own gallerySyncService listens\n // on the bus for these, so without this bridge engine's gallery sync is dead.\n subs.push(\n BluetoothSdk.addListener(\"hotspot_status_change\", (event) => {\n const enabled = event.state === \"enabled\"\n const ssid = enabled ? event.ssid : \"\"\n const password = enabled ? event.password : \"\"\n const localIp = enabled ? event.localIp : \"\"\n useGlassesStore.getState().setHotspotInfo(enabled, ssid, password, localIp)\n // Always retarget the ASG camera client: on disable (or an event with no\n // IP) fall back to the localhost default so later calls fail fast instead\n // of hitting the previous network's stale glasses IP.\n asgCameraApi.setServer(localIp || \"localhost\", 8089)\n GlobalEventEmitter.emit(\"hotspot_status_change\", {enabled, ssid, password, local_ip: localIp})\n }),\n )\n subs.push(\n BluetoothSdk.addListener(\"hotspot_error\", (event) => {\n GlobalEventEmitter.emit(\"hotspot_error\", {error_message: event.errorMessage, timestamp: event.timestamp})\n }),\n )\n\n // Glasses gallery content counts → event bus (consumed by gallerySyncService).\n subs.push(\n BluetoothSdk.addListener(\"gallery_status\", (event) => {\n GlobalEventEmitter.emit(\"gallery_status\", {\n photos: event.photos,\n videos: event.videos,\n total: event.total,\n has_content: event.hasContent,\n camera_busy: event.cameraBusy,\n })\n }),\n )\n\n // Hardware-originated setting changes (user changes a setting ON the glasses) → store.\n // The inbound complement to GlassesSettingsSync (which only pushes store→device).\n subs.push(\n BluetoothSdk.addListener(\"save_setting\", async (event) => {\n const settings = useSettingsStore.getState()\n // Damp the relay: native re-echoes some settings unconditionally (e.g.\n // the identity block at every handleDeviceReady) — a same-value echo\n // must not become a store write (persistence churn + change-push noise).\n if (settings.getSetting(event.key) !== event.value) {\n await settings.setSetting(event.key, event.value)\n }\n // Two-phase identity: a promoted default retires the pending selection\n // marker. The rule lives with PairingIdentity (a no-op for other keys).\n await retirePendingSelectionOnPromotion(event.key, event.value)\n }),\n )\n\n // --- coordinators (owns()-gated) ---\n\n // Phone-owned photo errors settle the in-flight long-poll fast (vs. timeout).\n // Non-owned photo responses used to forward to the Cloud V1 photo pipeline via\n // restComms; that relay was removed with Cloud V1 app end-of-life, so they drop.\n // Wire v2 sends a short 4-hex BLE requestId; the coordinator maps it back to\n // the cloud requestId (resolveCloudRequestId) before ownership checks.\n subs.push(\n BluetoothSdk.addListener(\"photo_response\", (event) => {\n const cloudRequestId = event.requestId ? phonePhotoCoordinator.resolveCloudRequestId(event.requestId) : \"\"\n if (cloudRequestId && phonePhotoCoordinator.owns(cloudRequestId)) {\n if (event.state === \"error\") {\n phonePhotoCoordinator.handlePhotoError(\n event.requestId ?? cloudRequestId,\n event.errorCode ?? \"GLASSES_ERROR\",\n event.errorMessage ?? \"Glasses reported an error\",\n )\n }\n }\n }),\n )\n\n // Phone-owned stream status / keep-alive → the stream coordinator. The Cloud V1 relay\n // for non-owned streams was removed with Cloud V1 app end-of-life.\n subs.push(\n BluetoothSdk.addListener(\"stream_status\", (event) => {\n if (event.streamId && phoneStreamCoordinator.owns(event.streamId)) {\n phoneStreamCoordinator.handleGlassesStatus(event)\n }\n }),\n )\n subs.push(\n BluetoothSdk.addListener(\"keep_alive_ack\", (event) => {\n if (event.streamId && phoneStreamCoordinator.owns(event.streamId)) {\n phoneStreamCoordinator.handleKeepAliveAck(event)\n }\n }),\n )\n\n // --- device input → local miniapps (forwardEvent is subscriber-gated; a no-op when\n // no miniapp listens). The v1 SocketComms cloud legs for these were removed with\n // the Cloud V1 ripout. ---\n\n subs.push(\n BluetoothSdk.addListener(\"button_press\", (event) => {\n localMiniappRuntime.forwardEvent(\"button_press\", event)\n }),\n )\n subs.push(\n BluetoothSdk.addListener(\"touch_event\", (event) => {\n localMiniappRuntime.forwardEvent(\"touch_event\", event)\n }),\n )\n // G2 IMU accelerometer — payload already matches the miniapp AccelData shape.\n subs.push(\n BluetoothSdk.addListener(\"accel_event\", (event) => {\n localMiniappRuntime.forwardEvent(\"accel_event\", {\n x: event.x,\n y: event.y,\n z: event.z,\n timestamp: typeof event.timestamp === \"number\" ? event.timestamp : Date.now(),\n })\n }),\n )\n // Head position — translate native {up:boolean} → SDK {position:\"up\"|\"down\"}.\n subs.push(\n BluetoothSdk.addListener(\"head_up\", (event) => {\n localMiniappRuntime.forwardEvent(\"head_up\", {position: event.up ? \"up\" : \"down\", timestamp: Date.now()})\n }),\n )\n // On-device STT transcripts → local miniapps only while the local engine is\n // active (cloud fallback or an explicit forceLocal subscription). The runtime\n // filters mixed local/cloud delivery per miniapp to avoid double-delivery.\n // forwardEvent is subscriber-gated — a no-op when no miniapp listens.\n // (Was MantleManager.handle_local_transcription; its offline-captions display branch\n // went away with the pseudo captions renderer.)\n subs.push(\n BluetoothSdk.addListener(\"local_transcription\", (event) => {\n if (!localSttFallbackCoordinator.isActive()) return\n const lang = event.transcribeLanguage ?? localSttFallbackCoordinator.getActiveLanguage() ?? \"en-US\"\n localMiniappRuntime.forwardEvent(`transcription:${lang}`, event, \"local\")\n }),\n )\n\n // --- glasses swipe-menu app launcher (G2) ---\n subs.push(\n BluetoothSdk.addListener(\"miniapp_selected\", (event) => {\n const packageName = event.packageName as string\n if (!packageName) return\n const app = useAppStatusStore.getState().apps.find((a) => a.packageName === packageName)\n if (!app) return\n // Toggle: stop if running, else start.\n if (app.running) {\n useAppStatusStore.getState().stop(packageName)\n } else {\n useAppStatusStore.getState().start(app, {skipNavigation: true})\n }\n }),\n )\n}\n\nexport function stopDeviceEventRouter(): void {\n for (const sub of subs) sub.remove()\n subs = []\n}\n"]}
|
|
1
|
+
{"version":3,"file":"DeviceEventRouter.js","sourceRoot":"","sources":["../../src/services/DeviceEventRouter.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Device-event router — engine-owned. Subscribes to the inbound native BLE/device\n * events and routes them into the engine runtime: device stores, the process event\n * bus, the photo/stream coordinators, and local miniapps (via forwardEvent).\n *\n * Why this exists: engine owns the stores, coordinators, miniapp runtime, and facades,\n * but it never *subscribed to the device* for most events — the host MantleManager was\n * still the event router the whole runtime secretly depended on. So a bare OEM that\n * imported engine + called engine.start() got a connected runtime with almost no device\n * data flowing in (no miniapp input, dead gallery sync, starved coordinators). This\n * service moves those inbound bridges into engine so ANY host gets them.\n *\n * Scope: the engine legs only. The Cloud V1 relays that used to live beside them in\n * MantleManager (touch→cloud, the cloud-SDK stream/photo legs, etc.) were deleted with\n * Cloud V1 app end-of-life.\n *\n * Started by `engine.start()`. Idempotent.\n */\nimport BluetoothSdk from \"@mentra/bluetooth-sdk/internal\"\nimport {shallow} from \"zustand/shallow\"\n\nimport localMiniappRuntime from \"./LocalMiniappRuntime\"\nimport localSttFallbackCoordinator from \"./LocalSttFallbackCoordinator\"\nimport {phonePhotoCoordinator} from \"./PhonePhotoCoordinator\"\nimport {phoneStreamCoordinator} from \"./PhoneStreamCoordinator\"\nimport {isGlassesConnected} from \"./GlassesReadiness\"\nimport {useGlassesStore} from \"../stores/glasses\"\nimport {useSettingsStore} from \"../stores/settings\"\nimport {useAppStatusStore} from \"../stores/apps\"\nimport {retirePendingSelectionOnPromotion} from \"./PairingIdentity\"\nimport GlobalEventEmitter from \"../utils/GlobalEventEmitter\"\nimport {asgCameraApi} from \"./asg/asgCameraApi\"\n\nlet subs: Array<{remove: () => void}> = []\n\nexport function startDeviceEventRouter(): void {\n if (subs.length) return\n\n // --- device state → engine stores ---\n\n // Standalone WiFi status → glasses store.\n subs.push(\n BluetoothSdk.addListener(\"wifi_status_change\", (event) => {\n const {type: _type, ...wifi} = event\n useGlassesStore.getState().setGlassesInfo({wifi})\n }),\n )\n\n // Forward glasses Wi-Fi to miniapps (session.glasses.onWifi) from the STORE — the\n // single source of truth — so every path converges here: wifi_status_change,\n // onGlassesStatus, and BLE disconnect. Effective connectivity requires the glasses\n // to be connected AND on Wi-Fi, so a disconnect correctly flips `connected` false.\n // `linkConnected` carries the BLE leg on its own so a miniapp can tell \"glasses\n // out of Bluetooth range\" from \"glasses on Bluetooth but off Wi-Fi\" — the two\n // need opposite recoveries (wait vs. open Wi-Fi setup).\n subs.push({\n remove: useGlassesStore.subscribe(\n (s) => {\n const linkConnected = isGlassesConnected(s.connection)\n const connected = linkConnected && s.wifi.state === \"connected\"\n // ssid/localIp describe how to reach the glasses over Wi-Fi, so they are\n // only meaningful under effective connectivity. Gate them on `connected`\n // (not raw wifi.state) so a BLE drop with a stale \"connected\" Wi-Fi leg\n // never hands miniapps an unreachable address.\n return {\n connected,\n linkConnected,\n ssid: linkConnected && s.wifi.state === \"connected\" ? s.wifi.ssid : undefined,\n localIp: linkConnected && s.wifi.state === \"connected\" ? s.wifi.localIp : undefined,\n }\n },\n (wifi) => localMiniappRuntime.forwardEvent(\"glasses_wifi\", wifi),\n {equalityFn: shallow},\n ),\n })\n\n // Incremental battery status → glasses store + local miniapps.\n subs.push(\n BluetoothSdk.addListener(\"battery_status\", (event) => {\n const state = useGlassesStore.getState()\n state.setBatteryInfo(event.level, event.charging, state.caseBatteryLevel, state.caseCharging)\n localMiniappRuntime.forwardEvent(\"glasses_battery_update\", {\n type: \"glasses_battery_update\",\n level: event.level,\n charging: event.charging,\n timestamp: event.timestamp ?? Date.now(),\n })\n }),\n )\n\n // Hotspot status → glasses store + event bus. engine's own gallerySyncService listens\n // on the bus for these, so without this bridge engine's gallery sync is dead.\n subs.push(\n BluetoothSdk.addListener(\"hotspot_status_change\", (event) => {\n const enabled = event.state === \"enabled\"\n const ssid = enabled ? event.ssid : \"\"\n const password = enabled ? event.password : \"\"\n const localIp = enabled ? event.localIp : \"\"\n useGlassesStore.getState().setHotspotInfo(enabled, ssid, password, localIp)\n // Always retarget the ASG camera client: on disable (or an event with no\n // IP) fall back to the localhost default so later calls fail fast instead\n // of hitting the previous network's stale glasses IP.\n asgCameraApi.setServer(localIp || \"localhost\", 8089)\n GlobalEventEmitter.emit(\"hotspot_status_change\", {enabled, ssid, password, local_ip: localIp})\n }),\n )\n subs.push(\n BluetoothSdk.addListener(\"hotspot_error\", (event) => {\n GlobalEventEmitter.emit(\"hotspot_error\", {error_message: event.errorMessage, timestamp: event.timestamp})\n }),\n )\n\n // Glasses gallery content counts → event bus (consumed by gallerySyncService).\n subs.push(\n BluetoothSdk.addListener(\"gallery_status\", (event) => {\n GlobalEventEmitter.emit(\"gallery_status\", {\n photos: event.photos,\n videos: event.videos,\n total: event.total,\n has_content: event.hasContent,\n camera_busy: event.cameraBusy,\n })\n }),\n )\n\n // Hardware-originated setting changes (user changes a setting ON the glasses) → store.\n // The inbound complement to GlassesSettingsSync (which only pushes store→device).\n subs.push(\n BluetoothSdk.addListener(\"save_setting\", async (event) => {\n const settings = useSettingsStore.getState()\n // Damp the relay: native re-echoes some settings unconditionally (e.g.\n // the identity block at every handleDeviceReady) — a same-value echo\n // must not become a store write (persistence churn + change-push noise).\n if (settings.getSetting(event.key) !== event.value) {\n await settings.setSetting(event.key, event.value)\n }\n // Two-phase identity: a promoted default retires the pending selection\n // marker. The rule lives with PairingIdentity (a no-op for other keys).\n await retirePendingSelectionOnPromotion(event.key, event.value)\n }),\n )\n\n // --- coordinators (owns()-gated) ---\n\n // Phone-owned photo errors settle the in-flight long-poll fast (vs. timeout).\n // Non-owned photo responses used to forward to the Cloud V1 photo pipeline via\n // restComms; that relay was removed with Cloud V1 app end-of-life, so they drop.\n // Wire v2 sends a short 4-hex BLE requestId; the coordinator maps it back to\n // the cloud requestId (resolveCloudRequestId) before ownership checks.\n subs.push(\n BluetoothSdk.addListener(\"photo_response\", (event) => {\n const cloudRequestId = event.requestId ? phonePhotoCoordinator.resolveCloudRequestId(event.requestId) : \"\"\n if (cloudRequestId && phonePhotoCoordinator.owns(cloudRequestId)) {\n if (event.state === \"error\") {\n phonePhotoCoordinator.handlePhotoError(\n event.requestId ?? cloudRequestId,\n event.errorCode ?? \"GLASSES_ERROR\",\n event.errorMessage ?? \"Glasses reported an error\",\n )\n }\n }\n }),\n )\n\n // Phone-owned stream status → the stream coordinator. The Cloud V1 relay\n // for non-owned streams was removed with Cloud V1 app end-of-life.\n subs.push(\n BluetoothSdk.addListener(\"stream_status\", (event) => {\n if (\n (event.streamId && phoneStreamCoordinator.owns(event.streamId)) ||\n (!event.streamId && event.kind === \"snapshot\" && event.terminal === true)\n ) {\n phoneStreamCoordinator.handleGlassesStatus(event)\n }\n }),\n )\n\n // --- device input → local miniapps (forwardEvent is subscriber-gated; a no-op when\n // no miniapp listens). The v1 SocketComms cloud legs for these were removed with\n // the Cloud V1 ripout. ---\n\n subs.push(\n BluetoothSdk.addListener(\"button_press\", (event) => {\n localMiniappRuntime.forwardEvent(\"button_press\", event)\n }),\n )\n subs.push(\n BluetoothSdk.addListener(\"touch_event\", (event) => {\n localMiniappRuntime.forwardEvent(\"touch_event\", event)\n }),\n )\n // G2 IMU accelerometer — payload already matches the miniapp AccelData shape.\n subs.push(\n BluetoothSdk.addListener(\"accel_event\", (event) => {\n localMiniappRuntime.forwardEvent(\"accel_event\", {\n x: event.x,\n y: event.y,\n z: event.z,\n timestamp: typeof event.timestamp === \"number\" ? event.timestamp : Date.now(),\n })\n }),\n )\n // Head position — translate native {up:boolean} → SDK {position:\"up\"|\"down\"}.\n subs.push(\n BluetoothSdk.addListener(\"head_up\", (event) => {\n localMiniappRuntime.forwardEvent(\"head_up\", {position: event.up ? \"up\" : \"down\", timestamp: Date.now()})\n }),\n )\n // On-device STT transcripts → local miniapps only while the local engine is\n // active (cloud fallback or an explicit forceLocal subscription). The runtime\n // filters mixed local/cloud delivery per miniapp to avoid double-delivery.\n // forwardEvent is subscriber-gated — a no-op when no miniapp listens.\n // (Was MantleManager.handle_local_transcription; its offline-captions display branch\n // went away with the pseudo captions renderer.)\n subs.push(\n BluetoothSdk.addListener(\"local_transcription\", (event) => {\n if (!localSttFallbackCoordinator.isActive()) return\n const lang = event.transcribeLanguage ?? localSttFallbackCoordinator.getActiveLanguage() ?? \"en-US\"\n localMiniappRuntime.forwardEvent(`transcription:${lang}`, event, \"local\")\n }),\n )\n\n // --- glasses swipe-menu app launcher (G2) ---\n subs.push(\n BluetoothSdk.addListener(\"miniapp_selected\", (event) => {\n const packageName = event.packageName as string\n if (!packageName) return\n const app = useAppStatusStore.getState().apps.find((a) => a.packageName === packageName)\n if (!app) return\n // Toggle: stop if running, else start.\n if (app.running) {\n useAppStatusStore.getState().stop(packageName)\n } else {\n useAppStatusStore.getState().start(app, {skipNavigation: true})\n }\n }),\n )\n}\n\nexport function stopDeviceEventRouter(): void {\n for (const sub of subs) sub.remove()\n subs = []\n}\n"]}
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
* miniapp → SDK → LocalMiniappRuntime → coordinator
|
|
6
6
|
* coordinator → BluetoothSdk (BLE → glasses publisher)
|
|
7
7
|
* coordinator ↔ cloudStreamApi (managed only, cloud-v2 runtime provisioning)
|
|
8
|
-
* coordinator ↔ StreamLifecycleController (keep-alive heartbeat)
|
|
9
8
|
* coordinator → status listeners → routed back to miniapp(s)
|
|
10
9
|
*
|
|
11
10
|
* Single-stream constraint:
|
|
@@ -28,33 +27,24 @@
|
|
|
28
27
|
* the legacy path.
|
|
29
28
|
*
|
|
30
29
|
* BLE link loss is a SUSPENDED state, not a failure:
|
|
31
|
-
*
|
|
32
|
-
* link
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* `glassesGraceMs` to come back. On reconnect we resume, heartbeat at once,
|
|
36
|
-
* and fan out `resumed` with the SAME streamId — no re-provision. The grace
|
|
37
|
-
* ends early when Cloudflare reports the publisher gone for two probes in
|
|
38
|
-
* a row (glasses powered off, not a BLE hiccup). If the grace expires the
|
|
39
|
-
* stream is torn down with reason `glasses_disconnected`, and the BLE stop
|
|
40
|
-
* the glasses never received is sent on the next reconnect.
|
|
30
|
+
* ASG owns the ten-second phone/controller liveness deadline. The coordinator
|
|
31
|
+
* observes link suspension, retains cloud resources briefly for reconciliation,
|
|
32
|
+
* and never uses missing JavaScript heartbeats as evidence of publisher failure.
|
|
33
|
+
* An explicit stop that could not reach BLE is sent on the next reconnect.
|
|
41
34
|
*/
|
|
42
|
-
import type {
|
|
35
|
+
import type { StreamResolvedConfig, StreamStartRequest, StreamStatusEvent } from "@mentra/bluetooth-sdk/internal";
|
|
43
36
|
import { type RestreamDestinationInput } from "./cloudStreamApi";
|
|
44
37
|
/**
|
|
45
38
|
* Default cadence + thresholds. Exposed via {@link CoordinatorTimings} so tests
|
|
46
39
|
* can shorten them; production code should never override these.
|
|
47
40
|
*/
|
|
48
41
|
declare const DEFAULT_TIMINGS: {
|
|
49
|
-
readonly keepAliveIntervalMs: 15000;
|
|
50
|
-
readonly ackTimeoutMs: 10000;
|
|
51
|
-
readonly maxMissedAcks: 3;
|
|
52
42
|
readonly cloudflareStatusPollMs: 5000;
|
|
53
43
|
readonly cloudflareStartupPollInitialMs: 500;
|
|
54
44
|
readonly hlsReadinessInitialDelayMs: 5000;
|
|
55
45
|
readonly hlsReadinessPollMs: 2000;
|
|
56
46
|
readonly hlsReadinessMaxAttempts: 30;
|
|
57
|
-
readonly glassesGraceMs:
|
|
47
|
+
readonly glassesGraceMs: 15000;
|
|
58
48
|
readonly suspendedPublisherGoneProbes: 2;
|
|
59
49
|
};
|
|
60
50
|
type TimingConfig = {
|
|
@@ -140,7 +130,6 @@ export declare class StreamConflictError extends Error {
|
|
|
140
130
|
}
|
|
141
131
|
export declare class PhoneStreamCoordinator {
|
|
142
132
|
private current;
|
|
143
|
-
private lifecycle;
|
|
144
133
|
private statusSubscriber;
|
|
145
134
|
private idCounter;
|
|
146
135
|
private readonly timings;
|
|
@@ -206,8 +195,6 @@ export declare class PhoneStreamCoordinator {
|
|
|
206
195
|
* and the registry says it's phone-owned.
|
|
207
196
|
*/
|
|
208
197
|
handleGlassesStatus(event: StreamStatusEvent): void;
|
|
209
|
-
/** Called by MantleManager when a phone-owned keep_alive_ack arrives. */
|
|
210
|
-
handleKeepAliveAck(event: KeepAliveAckEvent): void;
|
|
211
198
|
private attachLink;
|
|
212
199
|
private detachLinkIfIdle;
|
|
213
200
|
private handleLinkChange;
|
|
@@ -222,7 +209,7 @@ export declare class PhoneStreamCoordinator {
|
|
|
222
209
|
private failSuspended;
|
|
223
210
|
private flushPendingBleStop;
|
|
224
211
|
private mintId;
|
|
225
|
-
/**
|
|
212
|
+
/** Observe link/status; native SDK and ASG own controller liveness. */
|
|
226
213
|
private startLifecycle;
|
|
227
214
|
private startCloudflareStatusPoll;
|
|
228
215
|
private startHlsReadinessPoll;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PhoneStreamCoordinator.d.ts","sourceRoot":"","sources":["../../src/services/PhoneStreamCoordinator.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"PhoneStreamCoordinator.d.ts","sourceRoot":"","sources":["../../src/services/PhoneStreamCoordinator.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
* miniapp → SDK → LocalMiniappRuntime → coordinator
|
|
6
6
|
* coordinator → BluetoothSdk (BLE → glasses publisher)
|
|
7
7
|
* coordinator ↔ cloudStreamApi (managed only, cloud-v2 runtime provisioning)
|
|
8
|
-
* coordinator ↔ StreamLifecycleController (keep-alive heartbeat)
|
|
9
8
|
* coordinator → status listeners → routed back to miniapp(s)
|
|
10
9
|
*
|
|
11
10
|
* Single-stream constraint:
|
|
@@ -28,21 +27,14 @@
|
|
|
28
27
|
* the legacy path.
|
|
29
28
|
*
|
|
30
29
|
* BLE link loss is a SUSPENDED state, not a failure:
|
|
31
|
-
*
|
|
32
|
-
* link
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* `glassesGraceMs` to come back. On reconnect we resume, heartbeat at once,
|
|
36
|
-
* and fan out `resumed` with the SAME streamId — no re-provision. The grace
|
|
37
|
-
* ends early when Cloudflare reports the publisher gone for two probes in
|
|
38
|
-
* a row (glasses powered off, not a BLE hiccup). If the grace expires the
|
|
39
|
-
* stream is torn down with reason `glasses_disconnected`, and the BLE stop
|
|
40
|
-
* the glasses never received is sent on the next reconnect.
|
|
30
|
+
* ASG owns the ten-second phone/controller liveness deadline. The coordinator
|
|
31
|
+
* observes link suspension, retains cloud resources briefly for reconciliation,
|
|
32
|
+
* and never uses missing JavaScript heartbeats as evidence of publisher failure.
|
|
33
|
+
* An explicit stop that could not reach BLE is sent on the next reconnect.
|
|
41
34
|
*/
|
|
42
35
|
import BluetoothSdk from "@mentra/bluetooth-sdk/internal";
|
|
43
36
|
import { isGlassesConnected } from "./GlassesReadiness";
|
|
44
37
|
import { useGlassesStore } from "../stores/glasses";
|
|
45
|
-
import { StreamLifecycleController } from "./StreamLifecycleController";
|
|
46
38
|
import { slimStreamStatusEvent, streamStatusSignature } from "./slimStreamStatus";
|
|
47
39
|
import { getManagedStreamStatus, provisionManagedStream, teardownManagedStream, } from "./cloudStreamApi";
|
|
48
40
|
/**
|
|
@@ -50,9 +42,6 @@ import { getManagedStreamStatus, provisionManagedStream, teardownManagedStream,
|
|
|
50
42
|
* can shorten them; production code should never override these.
|
|
51
43
|
*/
|
|
52
44
|
const DEFAULT_TIMINGS = {
|
|
53
|
-
keepAliveIntervalMs: 15_000,
|
|
54
|
-
ackTimeoutMs: 10_000,
|
|
55
|
-
maxMissedAcks: 3,
|
|
56
45
|
cloudflareStatusPollMs: 5_000,
|
|
57
46
|
// During WHIP startup, probe quickly so readiness is not quantized to the
|
|
58
47
|
// steady-state 5s monitoring cadence. The delay backs off on each miss.
|
|
@@ -61,10 +50,8 @@ const DEFAULT_TIMINGS = {
|
|
|
61
50
|
hlsReadinessInitialDelayMs: 5_000,
|
|
62
51
|
hlsReadinessPollMs: 2_000,
|
|
63
52
|
hlsReadinessMaxAttempts: 30,
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
// margin for the resume heartbeat's round trip.
|
|
67
|
-
glassesGraceMs: 45_000,
|
|
53
|
+
// Allow ASG's ten-second stop plus a short delivery/reconciliation margin.
|
|
54
|
+
glassesGraceMs: 15_000,
|
|
68
55
|
// Consecutive Cloudflare "publisher disconnected" probes while suspended
|
|
69
56
|
// before we conclude the glasses are off (not just out of BLE range).
|
|
70
57
|
suspendedPublisherGoneProbes: 2,
|
|
@@ -79,18 +66,6 @@ export const LINK_STATUS = {
|
|
|
79
66
|
resumed: "resumed",
|
|
80
67
|
reason: "glasses_disconnected",
|
|
81
68
|
};
|
|
82
|
-
// Console-backed minimal logger; replaces pino on the phone.
|
|
83
|
-
const consoleLogger = {
|
|
84
|
-
child: (bindings) => ({
|
|
85
|
-
...consoleLogger,
|
|
86
|
-
debug: (...args) => console.debug("[STREAM]", bindings, ...args),
|
|
87
|
-
warn: (...args) => console.warn("[STREAM]", bindings, ...args),
|
|
88
|
-
error: (...args) => console.error("[STREAM]", bindings, ...args),
|
|
89
|
-
}),
|
|
90
|
-
debug: (...args) => console.debug("[STREAM]", ...args),
|
|
91
|
-
warn: (...args) => console.warn("[STREAM]", ...args),
|
|
92
|
-
error: (...args) => console.error("[STREAM]", ...args),
|
|
93
|
-
};
|
|
94
69
|
export class StreamConflictError extends Error {
|
|
95
70
|
code;
|
|
96
71
|
stage;
|
|
@@ -109,7 +84,6 @@ export class StreamConflictError extends Error {
|
|
|
109
84
|
}
|
|
110
85
|
export class PhoneStreamCoordinator {
|
|
111
86
|
current = null;
|
|
112
|
-
lifecycle = null;
|
|
113
87
|
statusSubscriber = null;
|
|
114
88
|
idCounter = 0;
|
|
115
89
|
timings;
|
|
@@ -235,7 +209,7 @@ export class PhoneStreamCoordinator {
|
|
|
235
209
|
// the lock sees the in-progress entry and rejects with the conflict.
|
|
236
210
|
this.current = entry;
|
|
237
211
|
try {
|
|
238
|
-
const event = await BluetoothSdk.
|
|
212
|
+
const event = await BluetoothSdk.startStream({
|
|
239
213
|
type: "start_stream",
|
|
240
214
|
streamUrl: opts.streamUrl,
|
|
241
215
|
streamId,
|
|
@@ -285,9 +259,7 @@ export class PhoneStreamCoordinator {
|
|
|
285
259
|
throw new StreamConflictError("STREAM_DESTINATIONS_LOCKED", "Restream destinations cannot be modified on an already-running managed stream.");
|
|
286
260
|
}
|
|
287
261
|
existing.subscribers.add(packageName);
|
|
288
|
-
const immediate = existing.hlsReady
|
|
289
|
-
? managedStartResult(existing)
|
|
290
|
-
: null;
|
|
262
|
+
const immediate = existing.hlsReady ? managedStartResult(existing) : null;
|
|
291
263
|
return { kind: "join", entry: existing, immediate };
|
|
292
264
|
}
|
|
293
265
|
// Fresh provision — claim slot BEFORE awaiting Cloudflare so a
|
|
@@ -322,7 +294,7 @@ export class PhoneStreamCoordinator {
|
|
|
322
294
|
elapsedMs: Date.now() - startupStartedAtMs,
|
|
323
295
|
});
|
|
324
296
|
try {
|
|
325
|
-
const event = await BluetoothSdk.
|
|
297
|
+
const event = await BluetoothSdk.startStream({
|
|
326
298
|
type: "start_stream",
|
|
327
299
|
streamUrl: ingestUrl,
|
|
328
300
|
streamId,
|
|
@@ -403,7 +375,6 @@ export class PhoneStreamCoordinator {
|
|
|
403
375
|
return;
|
|
404
376
|
if (event.streamId && event.streamId !== this.current.streamId)
|
|
405
377
|
return;
|
|
406
|
-
this.lifecycle?.recordActivity();
|
|
407
378
|
const includeResolvedConfig = !this.resolvedConfigForwarded && !!event.resolvedConfig;
|
|
408
379
|
if (includeResolvedConfig)
|
|
409
380
|
this.resolvedConfigForwarded = true;
|
|
@@ -423,14 +394,13 @@ export class PhoneStreamCoordinator {
|
|
|
423
394
|
// glasses publisher auto-recovers (error → reconnecting → reconnected),
|
|
424
395
|
// and tearing down on the first hiccup deletes the live input out from
|
|
425
396
|
// under a publisher that comes right back (it then retries into a dead
|
|
426
|
-
// input forever).
|
|
427
|
-
//
|
|
428
|
-
// lock so it serializes with any start/stop currently in flight.
|
|
397
|
+
// input forever). ASG explicitly marks terminal failures; serialize teardown
|
|
398
|
+
// with any start/stop currently in flight.
|
|
429
399
|
const isStopped = (event.kind === "lifecycle" && event.status === "stopped") ||
|
|
430
400
|
(event.kind === "snapshot" && event.status === "stopped");
|
|
431
401
|
const isGiveUp = event.kind === "reconnect" && event.status === "reconnect_failed";
|
|
432
|
-
if (isGiveUp || isStopped) {
|
|
433
|
-
const reason = isGiveUp ? "glasses_gave_up" : "glasses_stopped";
|
|
402
|
+
if (event.terminal === true || isGiveUp || isStopped) {
|
|
403
|
+
const reason = isGiveUp ? "glasses_gave_up" : event.status === "error" ? "glasses_error" : "glasses_stopped";
|
|
434
404
|
const targetStreamId = this.current.streamId;
|
|
435
405
|
void this.runExclusive(async () => {
|
|
436
406
|
// The stream we wanted to tear down may already be gone (e.g. another
|
|
@@ -441,12 +411,6 @@ export class PhoneStreamCoordinator {
|
|
|
441
411
|
});
|
|
442
412
|
}
|
|
443
413
|
}
|
|
444
|
-
/** Called by MantleManager when a phone-owned keep_alive_ack arrives. */
|
|
445
|
-
handleKeepAliveAck(event) {
|
|
446
|
-
if (!event.ackId)
|
|
447
|
-
return;
|
|
448
|
-
this.lifecycle?.handleAck(event.ackId);
|
|
449
|
-
}
|
|
450
414
|
// ===========================================================================
|
|
451
415
|
// BLE link suspension
|
|
452
416
|
// ===========================================================================
|
|
@@ -479,9 +443,6 @@ export class PhoneStreamCoordinator {
|
|
|
479
443
|
if (!entry)
|
|
480
444
|
return;
|
|
481
445
|
const since = Date.now();
|
|
482
|
-
// Pause the heartbeat: misses now would be our own fault, not the
|
|
483
|
-
// publisher's, and counting them would reap a stream that is still live.
|
|
484
|
-
this.lifecycle?.setActive(false);
|
|
485
446
|
const graceTimer = setTimeout(() => this.onGraceExpired(entry.streamId), this.timings.glassesGraceMs);
|
|
486
447
|
this.suspended = { since, graceTimer, publisherGoneProbes: 0 };
|
|
487
448
|
console.warn("[STREAM] BLE link lost; stream suspended", {
|
|
@@ -504,10 +465,6 @@ export class PhoneStreamCoordinator {
|
|
|
504
465
|
this.suspended = null;
|
|
505
466
|
const suspendedMs = Date.now() - suspended.since;
|
|
506
467
|
console.info("[STREAM] BLE link back; stream resumed", { streamId: entry.streamId, suspendedMs });
|
|
507
|
-
// Resume and heartbeat immediately: the glasses watchdog has been running
|
|
508
|
-
// the whole time, so the first keep-alive must not wait another interval.
|
|
509
|
-
this.lifecycle?.setActive(true);
|
|
510
|
-
this.lifecycle?.tickNow();
|
|
511
468
|
// A resumed session is a fresh status baseline for subscribers.
|
|
512
469
|
this.lastFanoutSignature = null;
|
|
513
470
|
this.fanout({
|
|
@@ -557,43 +514,10 @@ export class PhoneStreamCoordinator {
|
|
|
557
514
|
this.idCounter += 1;
|
|
558
515
|
return `phone-${prefix}-${Date.now().toString(36)}-${this.idCounter}`;
|
|
559
516
|
}
|
|
560
|
-
/**
|
|
561
|
-
startLifecycle(
|
|
562
|
-
// A new stream supersedes any deferred stop for the previous one.
|
|
517
|
+
/** Observe link/status; native SDK and ASG own controller liveness. */
|
|
518
|
+
startLifecycle(_streamId) {
|
|
563
519
|
this.pendingBleStop = null;
|
|
564
520
|
this.attachLink();
|
|
565
|
-
this.lifecycle?.dispose();
|
|
566
|
-
const ctrl = new StreamLifecycleController({
|
|
567
|
-
logger: consoleLogger,
|
|
568
|
-
streamId,
|
|
569
|
-
keepAliveIntervalMs: this.timings.keepAliveIntervalMs,
|
|
570
|
-
ackTimeoutMs: this.timings.ackTimeoutMs,
|
|
571
|
-
maxMissedAcks: this.timings.maxMissedAcks,
|
|
572
|
-
}, {
|
|
573
|
-
sendKeepAlive: async (ackId) => {
|
|
574
|
-
await BluetoothSdk.sendExternallyManagedStreamKeepAlive({
|
|
575
|
-
type: "keep_stream_alive",
|
|
576
|
-
streamId,
|
|
577
|
-
ackId,
|
|
578
|
-
});
|
|
579
|
-
},
|
|
580
|
-
onTimeout: async () => {
|
|
581
|
-
this.fanout({
|
|
582
|
-
streamId,
|
|
583
|
-
source: "coordinator",
|
|
584
|
-
status: "error",
|
|
585
|
-
data: { reason: "keep_alive_timeout" },
|
|
586
|
-
});
|
|
587
|
-
await this.runExclusive(async () => {
|
|
588
|
-
// The stream this timeout was bound to may already be gone.
|
|
589
|
-
if (this.current?.streamId !== streamId)
|
|
590
|
-
return;
|
|
591
|
-
await this.teardownLocked("keep_alive_timeout");
|
|
592
|
-
});
|
|
593
|
-
},
|
|
594
|
-
});
|
|
595
|
-
ctrl.setActive(true);
|
|
596
|
-
this.lifecycle = ctrl;
|
|
597
521
|
}
|
|
598
522
|
startCloudflareStatusPoll(entry) {
|
|
599
523
|
// Keep the existing ~60s readiness budget while decoupling it from probe
|
|
@@ -800,9 +724,7 @@ export class PhoneStreamCoordinator {
|
|
|
800
724
|
fanout(update) {
|
|
801
725
|
if (!this.current || !this.statusSubscriber)
|
|
802
726
|
return;
|
|
803
|
-
const targets = this.current.kind === "managed"
|
|
804
|
-
? Array.from(this.current.subscribers)
|
|
805
|
-
: [this.current.packageName];
|
|
727
|
+
const targets = this.current.kind === "managed" ? Array.from(this.current.subscribers) : [this.current.packageName];
|
|
806
728
|
for (const pkg of targets) {
|
|
807
729
|
try {
|
|
808
730
|
this.statusSubscriber(pkg, update);
|
|
@@ -830,11 +752,6 @@ export class PhoneStreamCoordinator {
|
|
|
830
752
|
clearTimeout(this.suspended.graceTimer);
|
|
831
753
|
this.suspended = null;
|
|
832
754
|
}
|
|
833
|
-
// Dispose the lifecycle controller immediately so it doesn't fire one
|
|
834
|
-
// more keep-alive against a stream we're tearing down. The transition
|
|
835
|
-
// lock guarantees no new lifecycle is started concurrently.
|
|
836
|
-
this.lifecycle?.dispose();
|
|
837
|
-
this.lifecycle = null;
|
|
838
755
|
// With the link down a BLE write can only fail (and hold the transition
|
|
839
756
|
// lock for the native timeout). Defer it to the next reconnect instead.
|
|
840
757
|
const linkUp = this.linkSource.isConnected();
|