@mentra/engine 3.1.0-dev.10 → 3.1.0-dev.101

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/README.md +24 -6
  2. package/build/bluetooth-sdk/ota-transport.d.ts +2 -0
  3. package/build/bluetooth-sdk/ota-transport.d.ts.map +1 -0
  4. package/build/bluetooth-sdk/ota-transport.js +2 -0
  5. package/build/bluetooth-sdk/ota-transport.js.map +1 -0
  6. package/build/engine.d.ts +5 -2
  7. package/build/engine.d.ts.map +1 -1
  8. package/build/facades/ota.d.ts +12 -7
  9. package/build/facades/ota.d.ts.map +1 -1
  10. package/build/facades/ota.js +10 -6
  11. package/build/facades/ota.js.map +1 -1
  12. package/build/facades/pairing.d.ts +4 -0
  13. package/build/facades/pairing.d.ts.map +1 -1
  14. package/build/facades/pairing.js +81 -7
  15. package/build/facades/pairing.js.map +1 -1
  16. package/build/generated/releaseMetadata.js +5 -5
  17. package/build/generated/releaseMetadata.js.map +1 -1
  18. package/build/index.d.ts +3 -3
  19. package/build/index.d.ts.map +1 -1
  20. package/build/index.js +2 -2
  21. package/build/index.js.map +1 -1
  22. package/build/react/MentraLiveOtaFlow.d.ts +8 -2
  23. package/build/react/MentraLiveOtaFlow.d.ts.map +1 -1
  24. package/build/react/MentraLiveOtaFlow.js +256 -354
  25. package/build/react/MentraLiveOtaFlow.js.map +1 -1
  26. package/build/react/index.d.ts +1 -0
  27. package/build/react/index.d.ts.map +1 -1
  28. package/build/react/index.js +1 -0
  29. package/build/react/index.js.map +1 -1
  30. package/build/react/useMentraLiveOta.d.ts +84 -0
  31. package/build/react/useMentraLiveOta.d.ts.map +1 -0
  32. package/build/react/useMentraLiveOta.js +627 -0
  33. package/build/react/useMentraLiveOta.js.map +1 -0
  34. package/build/services/BlobStore.d.ts +2 -0
  35. package/build/services/BlobStore.d.ts.map +1 -1
  36. package/build/services/BlobStore.js +63 -10
  37. package/build/services/BlobStore.js.map +1 -1
  38. package/build/services/DisplayProcessor.d.ts +3 -3
  39. package/build/services/DisplayProcessor.js +3 -3
  40. package/build/services/DisplayProcessor.js.map +1 -1
  41. package/build/services/GlassesStatusProjection.d.ts +15 -2
  42. package/build/services/GlassesStatusProjection.d.ts.map +1 -1
  43. package/build/services/GlassesStatusProjection.js +3 -3
  44. package/build/services/GlassesStatusProjection.js.map +1 -1
  45. package/build/services/HotspotOtaTransport.d.ts.map +1 -1
  46. package/build/services/HotspotOtaTransport.js +8 -7
  47. package/build/services/HotspotOtaTransport.js.map +1 -1
  48. package/build/services/LocalDisplayManager.d.ts +2 -3
  49. package/build/services/LocalDisplayManager.d.ts.map +1 -1
  50. package/build/services/LocalDisplayManager.js +2 -3
  51. package/build/services/LocalDisplayManager.js.map +1 -1
  52. package/build/services/OtaAutoChain.d.ts +9 -2
  53. package/build/services/OtaAutoChain.d.ts.map +1 -1
  54. package/build/services/OtaAutoChain.js +10 -2
  55. package/build/services/OtaAutoChain.js.map +1 -1
  56. package/build/services/OtaInstallCoordinator.d.ts +26 -3
  57. package/build/services/OtaInstallCoordinator.d.ts.map +1 -1
  58. package/build/services/OtaInstallCoordinator.js +35 -13
  59. package/build/services/OtaInstallCoordinator.js.map +1 -1
  60. package/build/services/OtaService.d.ts.map +1 -1
  61. package/build/services/OtaService.js +1 -1
  62. package/build/services/OtaService.js.map +1 -1
  63. package/build/services/OtaUpdateCheckService.d.ts +4 -1
  64. package/build/services/OtaUpdateCheckService.d.ts.map +1 -1
  65. package/build/services/OtaUpdateCheckService.js +21 -5
  66. package/build/services/OtaUpdateCheckService.js.map +1 -1
  67. package/build/services/PhonePhotoCoordinator.js +1 -1
  68. package/build/services/PhonePhotoCoordinator.js.map +1 -1
  69. package/build/services/StreamLifecycleController.d.ts +3 -3
  70. package/build/services/StreamLifecycleController.js +3 -3
  71. package/build/services/StreamLifecycleController.js.map +1 -1
  72. package/build/services/asg/localNetworkTransport.d.ts.map +1 -1
  73. package/build/services/asg/localNetworkTransport.js +13 -12
  74. package/build/services/asg/localNetworkTransport.js.map +1 -1
  75. package/build/services/glassesClockSync.js +1 -1
  76. package/build/services/glassesClockSync.js.map +1 -1
  77. package/build/services/otaInstallPolicy.d.ts +4 -0
  78. package/build/services/otaInstallPolicy.d.ts.map +1 -1
  79. package/build/services/otaInstallPolicy.js +4 -0
  80. package/build/services/otaInstallPolicy.js.map +1 -1
  81. package/build/services/otaReleaseVersion.d.ts +10 -0
  82. package/build/services/otaReleaseVersion.d.ts.map +1 -0
  83. package/build/services/otaReleaseVersion.js +14 -0
  84. package/build/services/otaReleaseVersion.js.map +1 -0
  85. package/build/stores/appVisibility.d.ts +3 -0
  86. package/build/stores/appVisibility.d.ts.map +1 -0
  87. package/build/stores/appVisibility.js +3 -0
  88. package/build/stores/appVisibility.js.map +1 -0
  89. package/build/stores/apps.d.ts.map +1 -1
  90. package/build/stores/apps.js +7 -1
  91. package/build/stores/apps.js.map +1 -1
  92. package/build/stores/core.d.ts +3 -3
  93. package/build/stores/core.d.ts.map +1 -1
  94. package/build/stores/core.js.map +1 -1
  95. package/build/stores/glasses.d.ts +10 -4
  96. package/build/stores/glasses.d.ts.map +1 -1
  97. package/build/stores/glasses.js.map +1 -1
  98. package/build/types/applet.d.ts +1 -1
  99. package/build/types/applet.js +1 -1
  100. package/build/types/applet.js.map +1 -1
  101. package/build/types/capabilities/even-realities-g1.d.ts.map +1 -1
  102. package/build/types/capabilities/even-realities-g1.js +2 -0
  103. package/build/types/capabilities/even-realities-g1.js.map +1 -1
  104. package/build/types/capabilities/even-realities-g2.d.ts.map +1 -1
  105. package/build/types/capabilities/even-realities-g2.js +2 -0
  106. package/build/types/capabilities/even-realities-g2.js.map +1 -1
  107. package/build/types/capabilities/mentra-display.d.ts.map +1 -1
  108. package/build/types/capabilities/mentra-display.js +2 -0
  109. package/build/types/capabilities/mentra-display.js.map +1 -1
  110. package/build/types/capabilities/mentra-live.d.ts.map +1 -1
  111. package/build/types/capabilities/mentra-live.js +2 -0
  112. package/build/types/capabilities/mentra-live.js.map +1 -1
  113. package/build/types/capabilities/nimo.d.ts +9 -0
  114. package/build/types/capabilities/nimo.d.ts.map +1 -0
  115. package/build/types/capabilities/nimo.js +57 -0
  116. package/build/types/capabilities/nimo.js.map +1 -0
  117. package/build/types/capabilities/none.d.ts.map +1 -1
  118. package/build/types/capabilities/none.js +2 -0
  119. package/build/types/capabilities/none.js.map +1 -1
  120. package/build/types/capabilities/simulated-glasses.d.ts.map +1 -1
  121. package/build/types/capabilities/simulated-glasses.js +2 -0
  122. package/build/types/capabilities/simulated-glasses.js.map +1 -1
  123. package/build/types/capabilities/vuzix-z100.d.ts.map +1 -1
  124. package/build/types/capabilities/vuzix-z100.js +2 -0
  125. package/build/types/capabilities/vuzix-z100.js.map +1 -1
  126. package/build/types/enums.d.ts +2 -1
  127. package/build/types/enums.d.ts.map +1 -1
  128. package/build/types/enums.js +1 -0
  129. package/build/types/enums.js.map +1 -1
  130. package/build/types/hardware.d.ts +3 -2
  131. package/build/types/hardware.d.ts.map +1 -1
  132. package/build/types/hardware.js +3 -1
  133. package/build/types/hardware.js.map +1 -1
  134. package/build/types/index.d.ts +1 -1
  135. package/build/types/index.d.ts.map +1 -1
  136. package/build/types/index.js +1 -1
  137. package/build/types/index.js.map +1 -1
  138. package/build/utils/display/composer/ColumnComposer.d.ts +1 -1
  139. package/build/utils/display/composer/ColumnComposer.js +1 -1
  140. package/build/utils/display/composer/ColumnComposer.js.map +1 -1
  141. package/package.json +13 -6
  142. package/src/bluetooth-sdk/ota-transport.ts +1 -0
  143. package/src/facades/ota.ts +20 -7
  144. package/src/facades/pairing.ts +88 -12
  145. package/src/generated/releaseMetadata.ts +5 -5
  146. package/src/index.ts +3 -0
  147. package/src/react/MentraLiveOtaFlow.tsx +378 -480
  148. package/src/react/index.ts +14 -0
  149. package/src/react/useMentraLiveOta.ts +793 -0
  150. package/src/services/BlobStore.ts +57 -10
  151. package/src/services/DisplayProcessor.ts +3 -3
  152. package/src/services/GlassesStatusProjection.ts +7 -6
  153. package/src/services/HotspotOtaTransport.ts +8 -7
  154. package/src/services/LocalDisplayManager.ts +2 -3
  155. package/src/services/OtaAutoChain.ts +26 -2
  156. package/src/services/OtaInstallCoordinator.ts +37 -13
  157. package/src/services/OtaService.ts +1 -2
  158. package/src/services/OtaUpdateCheckService.ts +26 -8
  159. package/src/services/PhonePhotoCoordinator.ts +1 -1
  160. package/src/services/StreamLifecycleController.ts +3 -3
  161. package/src/services/asg/localNetworkTransport.ts +20 -19
  162. package/src/services/glassesClockSync.ts +1 -1
  163. package/src/services/otaInstallPolicy.ts +4 -0
  164. package/src/services/otaReleaseVersion.ts +22 -0
  165. package/src/stores/appVisibility.ts +3 -0
  166. package/src/stores/apps.ts +7 -1
  167. package/src/stores/core.ts +4 -4
  168. package/src/stores/glasses.ts +13 -6
  169. package/src/types/applet.ts +1 -1
  170. package/src/types/capabilities/even-realities-g1.ts +3 -0
  171. package/src/types/capabilities/even-realities-g2.ts +3 -0
  172. package/src/types/capabilities/mentra-display.ts +3 -0
  173. package/src/types/capabilities/mentra-live.ts +3 -0
  174. package/src/types/capabilities/nimo.ts +59 -0
  175. package/src/types/capabilities/none.ts +3 -0
  176. package/src/types/capabilities/simulated-glasses.ts +3 -0
  177. package/src/types/capabilities/vuzix-z100.ts +3 -0
  178. package/src/types/enums.ts +1 -0
  179. package/src/types/hardware.ts +4 -2
  180. package/src/types/index.ts +1 -0
  181. package/src/utils/display/composer/ColumnComposer.ts +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"OtaService.js","sourceRoot":"","sources":["../../src/services/OtaService.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * OTA service — engine-owned. Subscribes to the glasses' OTA BLE events and projects\n * them into the engine glasses store (otaStatus / otaProgress), so\n * the OTA read surface works for ANY host — not just the first-party Mentra app, where\n * these handlers used to live in MantleManager.\n *\n * This is the inbound projection half of `engine.ota` (read/observe). The install\n * ORCHESTRATION (startOtaUpdate/poll/ping) is host-side for now — Phase 2 moves it here\n * behind `engine.ota.{check,install,retry}` (bootloop-risk; needs on-device verification).\n *\n * Started by `engine.start()`. Idempotent.\n */\nimport BluetoothSdk from \"@mentra/bluetooth-sdk/internal\"\nimport type {OtaStatus} from \"@mentra/bluetooth-sdk/internal\"\nimport GlobalEventEmitter from \"../utils/GlobalEventEmitter\"\nimport {useGlassesStore} from \"../stores/glasses\"\nimport {handleOtaClockSkewFromGlasses} from \"./glassesClockSync\"\nimport {legacyOtaProgressFromOtaStatusEvent, normalizeOtaStatusEvent, otaStatusFromNormalized} from \"./otaLegacyMapping\"\n\nlet subs: Array<{remove: () => void}> = []\n\nexport function startOtaService(): void {\n if (subs.length) return\n\n // MTK firmware update finished (self power-cycle path).\n subs.push(\n BluetoothSdk.addListener(\"mtk_update_complete\", (event) => {\n GlobalEventEmitter.emit(\"mtk_update_complete\", {message: event.message, timestamp: event.timestamp})\n }),\n )\n\n // Glasses acknowledged the install start.\n subs.push(\n BluetoothSdk.addListener(\"ota_start_ack\", (event) => {\n GlobalEventEmitter.emit(\"ota_start_ack\", {timestamp: event.timestamp})\n }),\n )\n\n // The glasses process restarted under a live BLE link (sid changed in\n // glasses_ready/version_info_1). The physical connection never dropped, so this is\n // the ONLY reconnect-edge signal the OTA coordinator gets after an APK install.\n subs.push(\n BluetoothSdk.addListener(\"glasses_session_changed\", (event) => {\n console.log(`[OTA] glasses_session_changed ${event.previous_sid || \"<none>\"} -> ${event.sid}`)\n GlobalEventEmitter.emit(\"glasses_session_changed\", {previousSid: event.previous_sid, sid: event.sid})\n }),\n )\n\n // Install progress / lifecycle. Updates both the new (otaStatus) and the legacy\n // (otaProgress) store shapes; auto-fixes clock skew on the relevant failures.\n subs.push(\n BluetoothSdk.addListener(\"ota_status\", (event) => {\n const normalized = normalizeOtaStatusEvent(event as Record<string, unknown>)\n const status: OtaStatus = otaStatusFromNormalized(normalized)\n useGlassesStore.getState().setOtaStatus(status)\n // Emit before legacy progress: setOtaProgress can throw (e.g. JSON.stringify in store);\n // native logs would still show while RN UI would stay on \"Starting update…\".\n GlobalEventEmitter.emit(\"ota_status\", status)\n try {\n useGlassesStore.getState().setOtaProgress(legacyOtaProgressFromOtaStatusEvent(normalized))\n } catch (err) {\n console.warn(\"OTA: ota_status legacy otaProgress mapping failed\", err)\n }\n\n if (status.status === \"failed\") {\n const raw = event as Record<string, unknown>\n const glassesTimeMs = Number(raw.glasses_time_ms ?? raw.glassesTimeMs ?? 0) || undefined\n const errorCode = normalized.error_message\n if (\n errorCode === \"clock_skew\" ||\n (errorCode === \"ssl_error\" && typeof glassesTimeMs === \"number\" && Number.isFinite(glassesTimeMs))\n ) {\n handleOtaClockSkewFromGlasses(errorCode, glassesTimeMs).catch((err: unknown) => {\n console.warn(\"OTA: clock skew auto-fix failed\", err)\n })\n }\n }\n\n if (status.status === \"complete\" || status.status === \"failed\") {\n useGlassesStore.getState().setOtaUpdateAvailable(null)\n }\n }),\n )\n}\n\nexport function stopOtaService(): void {\n subs.forEach((s) => s.remove())\n subs = []\n}\n"]}
1
+ {"version":3,"file":"OtaService.js","sourceRoot":"","sources":["../../src/services/OtaService.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,YAA8B,MAAM,uBAAuB,CAAA;AAClE,OAAO,kBAAkB,MAAM,6BAA6B,CAAA;AAC5D,OAAO,EAAC,eAAe,EAAC,MAAM,mBAAmB,CAAA;AACjD,OAAO,EAAC,6BAA6B,EAAC,MAAM,oBAAoB,CAAA;AAChE,OAAO,EAAC,mCAAmC,EAAE,uBAAuB,EAAE,uBAAuB,EAAC,MAAM,oBAAoB,CAAA;AAExH,IAAI,IAAI,GAAgC,EAAE,CAAA;AAE1C,MAAM,UAAU,eAAe;IAC7B,IAAI,IAAI,CAAC,MAAM;QAAE,OAAM;IAEvB,wDAAwD;IACxD,IAAI,CAAC,IAAI,CACP,YAAY,CAAC,WAAW,CAAC,qBAAqB,EAAE,CAAC,KAAK,EAAE,EAAE;QACxD,kBAAkB,CAAC,IAAI,CAAC,qBAAqB,EAAE,EAAC,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAC,CAAC,CAAA;IACtG,CAAC,CAAC,CACH,CAAA;IAED,0CAA0C;IAC1C,IAAI,CAAC,IAAI,CACP,YAAY,CAAC,WAAW,CAAC,eAAe,EAAE,CAAC,KAAK,EAAE,EAAE;QAClD,kBAAkB,CAAC,IAAI,CAAC,eAAe,EAAE,EAAC,SAAS,EAAE,KAAK,CAAC,SAAS,EAAC,CAAC,CAAA;IACxE,CAAC,CAAC,CACH,CAAA;IAED,sEAAsE;IACtE,mFAAmF;IACnF,gFAAgF;IAChF,IAAI,CAAC,IAAI,CACP,YAAY,CAAC,WAAW,CAAC,yBAAyB,EAAE,CAAC,KAAK,EAAE,EAAE;QAC5D,OAAO,CAAC,GAAG,CAAC,iCAAiC,KAAK,CAAC,YAAY,IAAI,QAAQ,OAAO,KAAK,CAAC,GAAG,EAAE,CAAC,CAAA;QAC9F,kBAAkB,CAAC,IAAI,CAAC,yBAAyB,EAAE,EAAC,WAAW,EAAE,KAAK,CAAC,YAAY,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAC,CAAC,CAAA;IACvG,CAAC,CAAC,CACH,CAAA;IAED,gFAAgF;IAChF,8EAA8E;IAC9E,IAAI,CAAC,IAAI,CACP,YAAY,CAAC,WAAW,CAAC,YAAY,EAAE,CAAC,KAAK,EAAE,EAAE;QAC/C,MAAM,UAAU,GAAG,uBAAuB,CAAC,KAAgC,CAAC,CAAA;QAC5E,MAAM,MAAM,GAAc,uBAAuB,CAAC,UAAU,CAAC,CAAA;QAC7D,eAAe,CAAC,QAAQ,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,CAAA;QAC/C,wFAAwF;QACxF,6EAA6E;QAC7E,kBAAkB,CAAC,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC,CAAA;QAC7C,IAAI,CAAC;YACH,eAAe,CAAC,QAAQ,EAAE,CAAC,cAAc,CAAC,mCAAmC,CAAC,UAAU,CAAC,CAAC,CAAA;QAC5F,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CAAC,mDAAmD,EAAE,GAAG,CAAC,CAAA;QACxE,CAAC;QAED,IAAI,MAAM,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC/B,MAAM,GAAG,GAAG,KAAgC,CAAA;YAC5C,MAAM,aAAa,GAAG,MAAM,CAAC,GAAG,CAAC,eAAe,IAAI,GAAG,CAAC,aAAa,IAAI,CAAC,CAAC,IAAI,SAAS,CAAA;YACxF,MAAM,SAAS,GAAG,UAAU,CAAC,aAAa,CAAA;YAC1C,IACE,SAAS,KAAK,YAAY;gBAC1B,CAAC,SAAS,KAAK,WAAW,IAAI,OAAO,aAAa,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,EAClG,CAAC;gBACD,6BAA6B,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;oBAC7E,OAAO,CAAC,IAAI,CAAC,iCAAiC,EAAE,GAAG,CAAC,CAAA;gBACtD,CAAC,CAAC,CAAA;YACJ,CAAC;QACH,CAAC;QAED,IAAI,MAAM,CAAC,MAAM,KAAK,UAAU,IAAI,MAAM,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC/D,eAAe,CAAC,QAAQ,EAAE,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAA;QACxD,CAAC;IACH,CAAC,CAAC,CACH,CAAA;AACH,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAA;IAC/B,IAAI,GAAG,EAAE,CAAA;AACX,CAAC","sourcesContent":["/**\n * OTA service — engine-owned. Subscribes to the glasses' OTA BLE events and projects\n * them into the engine glasses store (otaStatus / otaProgress), so\n * the OTA read surface works for ANY host — not just the first-party Mentra app, where\n * these handlers used to live in MantleManager.\n *\n * This is the inbound projection half of `engine.ota` (read/observe). The install\n * ORCHESTRATION (startOtaUpdate/poll/ping) is host-side for now — Phase 2 moves it here\n * behind `engine.ota.{check,install,retry}` (bootloop-risk; needs on-device verification).\n *\n * Started by `engine.start()`. Idempotent.\n */\nimport BluetoothSdk, {type OtaStatus} from \"@mentra/bluetooth-sdk\"\nimport GlobalEventEmitter from \"../utils/GlobalEventEmitter\"\nimport {useGlassesStore} from \"../stores/glasses\"\nimport {handleOtaClockSkewFromGlasses} from \"./glassesClockSync\"\nimport {legacyOtaProgressFromOtaStatusEvent, normalizeOtaStatusEvent, otaStatusFromNormalized} from \"./otaLegacyMapping\"\n\nlet subs: Array<{remove: () => void}> = []\n\nexport function startOtaService(): void {\n if (subs.length) return\n\n // MTK firmware update finished (self power-cycle path).\n subs.push(\n BluetoothSdk.addListener(\"mtk_update_complete\", (event) => {\n GlobalEventEmitter.emit(\"mtk_update_complete\", {message: event.message, timestamp: event.timestamp})\n }),\n )\n\n // Glasses acknowledged the install start.\n subs.push(\n BluetoothSdk.addListener(\"ota_start_ack\", (event) => {\n GlobalEventEmitter.emit(\"ota_start_ack\", {timestamp: event.timestamp})\n }),\n )\n\n // The glasses process restarted under a live BLE link (sid changed in\n // glasses_ready/version_info_1). The physical connection never dropped, so this is\n // the ONLY reconnect-edge signal the OTA coordinator gets after an APK install.\n subs.push(\n BluetoothSdk.addListener(\"glasses_session_changed\", (event) => {\n console.log(`[OTA] glasses_session_changed ${event.previous_sid || \"<none>\"} -> ${event.sid}`)\n GlobalEventEmitter.emit(\"glasses_session_changed\", {previousSid: event.previous_sid, sid: event.sid})\n }),\n )\n\n // Install progress / lifecycle. Updates both the new (otaStatus) and the legacy\n // (otaProgress) store shapes; auto-fixes clock skew on the relevant failures.\n subs.push(\n BluetoothSdk.addListener(\"ota_status\", (event) => {\n const normalized = normalizeOtaStatusEvent(event as Record<string, unknown>)\n const status: OtaStatus = otaStatusFromNormalized(normalized)\n useGlassesStore.getState().setOtaStatus(status)\n // Emit before legacy progress: setOtaProgress can throw (e.g. JSON.stringify in store);\n // native logs would still show while RN UI would stay on \"Starting update…\".\n GlobalEventEmitter.emit(\"ota_status\", status)\n try {\n useGlassesStore.getState().setOtaProgress(legacyOtaProgressFromOtaStatusEvent(normalized))\n } catch (err) {\n console.warn(\"OTA: ota_status legacy otaProgress mapping failed\", err)\n }\n\n if (status.status === \"failed\") {\n const raw = event as Record<string, unknown>\n const glassesTimeMs = Number(raw.glasses_time_ms ?? raw.glassesTimeMs ?? 0) || undefined\n const errorCode = normalized.error_message\n if (\n errorCode === \"clock_skew\" ||\n (errorCode === \"ssl_error\" && typeof glassesTimeMs === \"number\" && Number.isFinite(glassesTimeMs))\n ) {\n handleOtaClockSkewFromGlasses(errorCode, glassesTimeMs).catch((err: unknown) => {\n console.warn(\"OTA: clock skew auto-fix failed\", err)\n })\n }\n }\n\n if (status.status === \"complete\" || status.status === \"failed\") {\n useGlassesStore.getState().setOtaUpdateAvailable(null)\n }\n }),\n )\n}\n\nexport function stopOtaService(): void {\n subs.forEach((s) => s.remove())\n subs = []\n}\n"]}
@@ -1,4 +1,4 @@
1
- import type { OtaUpdateInfo } from "@mentra/bluetooth-sdk";
1
+ import { type OtaUpdateInfo } from "@mentra/bluetooth-sdk";
2
2
  /**
3
3
  * Phone-side mirror of ASG's `DOWNGRADE_FLOOR_VERSION_CODE`. Set to the versionCode of the first
4
4
  * release shipping the shared media root (the first downgrade-safe build). A non-positive value
@@ -29,6 +29,7 @@ export interface BesFirmware {
29
29
  sha256?: string;
30
30
  }
31
31
  export interface VersionJson {
32
+ releaseVersion?: string;
32
33
  apps?: {
33
34
  [packageName: string]: VersionInfo;
34
35
  };
@@ -56,6 +57,8 @@ export interface OtaCheckResult {
56
57
  * instead of racing it with a second fetch of the same URL.
57
58
  */
58
59
  manifestBody: string | null;
60
+ /** Coordinated release identity for the selected OTA pin. */
61
+ releaseVersion: string | null;
59
62
  /**
60
63
  * Why the check failed, when it did. "network" is retryable (connection/server
61
64
  * trouble); "pin_unavailable" is permanent for this app build (manifest 404/gone,
@@ -1 +1 @@
1
- {"version":3,"file":"OtaUpdateCheckService.d.ts","sourceRoot":"","sources":["../../src/services/OtaUpdateCheckService.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"OtaUpdateCheckService.d.ts","sourceRoot":"","sources":["../../src/services/OtaUpdateCheckService.ts"],"names":[],"mappings":"AAAA,OAAqB,EAAC,KAAK,aAAa,EAAC,MAAM,uBAAuB,CAAA;AAOtE;;;;;;;GAOG;AACH,eAAO,MAAM,4BAA4B,IAAI,CAAA;AAE7C,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,YAAY,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AAED,MAAM,WAAW,QAAQ;IACvB,cAAc,EAAE,MAAM,CAAA;IACtB,YAAY,EAAE,MAAM,CAAA;IACpB,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAA;IACf,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,IAAI,CAAC,EAAE;QACL,CAAC,WAAW,EAAE,MAAM,GAAG,WAAW,CAAA;KACnC,CAAA;IACD,WAAW,CAAC,EAAE,QAAQ,EAAE,CAAA;IACxB,YAAY,CAAC,EAAE,WAAW,CAAA;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,iBAAiB,EAAE,OAAO,CAAA;IAC1B,eAAe,EAAE,OAAO,CAAA;IACxB,iBAAiB,EAAE,WAAW,GAAG,IAAI,CAAA;IACrC,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAA;IACzB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,6FAA6F;IAC7F,cAAc,EAAE,OAAO,CAAA;IACvB;;;;OAIG;IACH,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,6DAA6D;IAC7D,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,SAAS,GAAG,iBAAiB,CAAA;CACnD;AAED,MAAM,MAAM,qBAAqB,GAAG,cAAc,GAAG,eAAe,GAAG,WAAW,CAAA;AAElF,MAAM,WAAW,4BAA6B,SAAQ,cAAc;IAClE,UAAU,EAAE,aAAa,GAAG,IAAI,CAAA;IAChC,UAAU,EAAE,OAAO,CAAA;IACnB,aAAa,CAAC,EAAE,qBAAqB,CAAA;IACrC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAC5B;AAED,MAAM,WAAW,6BAA6B;IAC5C,oBAAoB,CAAC,EAAE,MAAM,CAAA;IAC7B,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B,sFAAsF;IACtF,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAkDD,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAE/E;AAED,wBAAgB,2BAA2B,CACzC,kBAAkB,EAAE,MAAM,GAAG,SAAS,EACtC,WAAW,EAAE,WAAW,GAAG,IAAI,EAC/B,gBAAgB,GAAE,MAAqC,GACtD,OAAO,CAET;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,kBAAkB,EAAE,MAAM,GAAG,SAAS,EACtC,WAAW,EAAE,WAAW,GAAG,IAAI,EAC/B,gBAAgB,GAAE,MAAqC,GACtD,SAAS,GAAG,WAAW,GAAG,IAAI,CAmChC;AAED,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,WAAW,GAAG,IAAI,GAAG,WAAW,GAAG,IAAI,CAqBxF;AAED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,QAAQ,EAAE,GAAG,SAAS,EAC/B,cAAc,EAAE,MAAM,GAAG,SAAS,GACjC,QAAQ,GAAG,IAAI,CAgBjB;AAED,wBAAgB,cAAc,CAAC,WAAW,EAAE,WAAW,GAAG,SAAS,EAAE,cAAc,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAUhH;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAiB1E;AAED,wBAAsB,iBAAiB,CACrC,aAAa,EAAE,MAAM,EACrB,kBAAkB,EAAE,MAAM,EAC1B,iBAAiB,CAAC,EAAE,MAAM,EAC1B,iBAAiB,CAAC,EAAE,MAAM,EAC1B,gBAAgB,GAAE,MAAqC,GACtD,OAAO,CAAC,cAAc,CAAC,CA4GzB;AAED,wBAAsB,4BAA4B,CAChD,OAAO,GAAE,6BAAkC,GAC1C,OAAO,CAAC,4BAA4B,CAAC,CAiKvC"}
@@ -1,7 +1,9 @@
1
- import BluetoothSdk from "@mentra/bluetooth-sdk/internal";
1
+ import BluetoothSdk from "@mentra/bluetooth-sdk";
2
+ import { ENGINE_RELEASE_METADATA } from "../generated/releaseMetadata";
2
3
  import { getGlassesSystemTimeMs, isGlassesConnected, useGlassesStore, waitForGlassesState } from "../stores/glasses";
3
4
  import { maybeFixGlassesClockFromVersionInfo } from "./glassesClockSync";
4
5
  import { hasConfiguredModernOtaManifestPin, resolveOtaManifestUrl } from "./otaManifestUrl";
6
+ import { resolveOtaReleaseVersion } from "./otaReleaseVersion";
5
7
  /**
6
8
  * Phone-side mirror of ASG's `DOWNGRADE_FLOOR_VERSION_CODE`. Set to the versionCode of the first
7
9
  * release shipping the shared media root (the first downgrade-safe build). A non-positive value
@@ -21,6 +23,7 @@ function emptyCheckResult(skippedReason) {
21
23
  besVersion: null,
22
24
  isApkDowngrade: false,
23
25
  manifestBody: null,
26
+ releaseVersion: null,
24
27
  updateInfo: null,
25
28
  isRequired: true,
26
29
  skippedReason,
@@ -33,7 +36,7 @@ async function fetchVersionInfoDetailed(url) {
33
36
  try {
34
37
  const response = await fetch(url);
35
38
  if (!response.ok) {
36
- console.error("Failed to fetch version info:", response.status);
39
+ console.warn("Failed to fetch version info:", response.status);
37
40
  // 4xx: the manifest does not exist (or is gone) — permanent for this app
38
41
  // build, retrying cannot help. Everything else is transient server/network
39
42
  // trouble and retryable.
@@ -46,12 +49,12 @@ async function fetchVersionInfoDetailed(url) {
46
49
  return { json: await response.json() };
47
50
  }
48
51
  catch (parseError) {
49
- console.error("OTA: manifest is not valid JSON:", parseError);
52
+ console.warn("OTA: manifest is not valid JSON:", parseError);
50
53
  return { json: null, failureReason: "pin_unavailable" };
51
54
  }
52
55
  }
53
56
  catch (error) {
54
- console.error("OTA: Error fetching version info:", error);
57
+ console.warn("OTA: Error fetching version info:", error);
55
58
  return { json: null, failureReason: "network" };
56
59
  }
57
60
  }
@@ -173,6 +176,7 @@ export async function checkForOtaUpdate(otaVersionUrl, currentBuildNumber, curre
173
176
  besVersion: null,
174
177
  isApkDowngrade: false,
175
178
  manifestBody: null,
179
+ releaseVersion: null,
176
180
  checkFailureReason: fetched.failureReason ?? "network",
177
181
  };
178
182
  }
@@ -199,6 +203,7 @@ export async function checkForOtaUpdate(otaVersionUrl, currentBuildNumber, curre
199
203
  besVersion: null,
200
204
  isApkDowngrade: false,
201
205
  manifestBody: null,
206
+ releaseVersion: null,
202
207
  checkFailureReason: "pin_unavailable",
203
208
  };
204
209
  }
@@ -230,6 +235,12 @@ export async function checkForOtaUpdate(otaVersionUrl, currentBuildNumber, curre
230
235
  besVersion: versionJson?.bes_firmware?.version || null,
231
236
  isApkDowngrade: apkDirection === "downgrade",
232
237
  manifestBody: versionJson ? JSON.stringify(versionJson) : null,
238
+ releaseVersion: resolveOtaReleaseVersion({
239
+ manifestReleaseVersion: versionJson.releaseVersion,
240
+ manifestUrl: otaVersionUrl,
241
+ packagedManifestUrl: ENGINE_RELEASE_METADATA.otaManifestUrl,
242
+ packagedReleaseIdentity: ENGINE_RELEASE_METADATA.releaseIdentity,
243
+ }),
233
244
  };
234
245
  }
235
246
  catch (error) {
@@ -243,6 +254,7 @@ export async function checkForOtaUpdate(otaVersionUrl, currentBuildNumber, curre
243
254
  besVersion: null,
244
255
  isApkDowngrade: false,
245
256
  manifestBody: null,
257
+ releaseVersion: null,
246
258
  checkFailureReason: "network",
247
259
  };
248
260
  }
@@ -257,7 +269,11 @@ export async function checkCurrentGlassesForUpdate(options = {}) {
257
269
  return emptyCheckResult("dev_build");
258
270
  }
259
271
  if (refreshVersionInfo) {
260
- void BluetoothSdk.requestVersionInfo().catch((error) => {
272
+ void BluetoothSdk.requestVersionInfo()
273
+ .then((versionInfo) => {
274
+ useGlassesStore.getState().setGlassesInfo(versionInfo);
275
+ })
276
+ .catch((error) => {
261
277
  console.warn("OTA: Failed to request version_info from glasses:", error);
262
278
  });
263
279
  }
@@ -1 +1 @@
1
- {"version":3,"file":"OtaUpdateCheckService.js","sourceRoot":"","sources":["../../src/services/OtaUpdateCheckService.ts"],"names":[],"mappings":"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","sourcesContent":["import BluetoothSdk from \"@mentra/bluetooth-sdk/internal\"\nimport type {OtaUpdateInfo} from \"@mentra/bluetooth-sdk\"\nimport {getGlassesSystemTimeMs, isGlassesConnected, useGlassesStore, waitForGlassesState} from \"../stores/glasses\"\nimport {maybeFixGlassesClockFromVersionInfo} from \"./glassesClockSync\"\nimport {hasConfiguredModernOtaManifestPin, resolveOtaManifestUrl} from \"./otaManifestUrl\"\n\n/**\n * Phone-side mirror of ASG's `DOWNGRADE_FLOOR_VERSION_CODE`. Set to the versionCode of the first\n * release shipping the shared media root (the first downgrade-safe build). A non-positive value\n * means downgrades are disabled, so the phone must NOT offer them — offering a downgrade the\n * glasses' `DowngradeGate` will refuse leaves the version-change session with no install and the\n * glasses reporting a bare \"complete\", which would otherwise present as false success. Keep this\n * in lockstep with the ASG/recovery constant at release time.\n */\nexport const DOWNGRADE_FLOOR_VERSION_CODE = 0\n\nexport interface VersionInfo {\n versionCode: number\n versionName: string\n downloadUrl: string\n apkSize: number\n sha256: string\n releaseNotes: string\n isRequired?: boolean\n}\n\nexport interface MtkPatch {\n start_firmware: string\n end_firmware: string\n url: string\n sha256?: string\n}\n\nexport interface BesFirmware {\n version: string\n url: string\n sha256?: string\n}\n\nexport interface VersionJson {\n apps?: {\n [packageName: string]: VersionInfo\n }\n mtk_patches?: MtkPatch[]\n bes_firmware?: BesFirmware\n versionCode?: number\n versionName?: string\n downloadUrl?: string\n apkSize?: number\n sha256?: string\n releaseNotes?: string\n}\n\nexport interface OtaCheckResult {\n hasCheckCompleted: boolean\n updateAvailable: boolean\n latestVersionInfo: VersionInfo | null\n updates: string[]\n mtkPatch: MtkPatch | null\n besVersion: string | null\n /** True when the pending APK step installs an OLDER build than the glasses currently run. */\n isApkDowngrade: boolean\n /**\n * Raw manifest JSON (stringified) this check actually fetched, or null when the\n * fetch failed. Lets change-watchers baseline on exactly what the check saw,\n * instead of racing it with a second fetch of the same URL.\n */\n manifestBody: string | null\n /**\n * Why the check failed, when it did. \"network\" is retryable (connection/server\n * trouble); \"pin_unavailable\" is permanent for this app build (manifest 404/gone,\n * unparseable, or carrying no valid ASG pin) — the remedy is updating the app.\n */\n checkFailureReason?: \"network\" | \"pin_unavailable\"\n}\n\nexport type OtaCheckSkippedReason = \"disconnected\" | \"missing_build\" | \"dev_build\"\n\nexport interface OtaCheckCurrentGlassesResult extends OtaCheckResult {\n updateInfo: OtaUpdateInfo | null\n isRequired: boolean\n skippedReason?: OtaCheckSkippedReason\n manifestUrl?: string\n buildNumber?: string\n mtkFirmwareVersion?: string\n besFirmwareVersion?: string\n}\n\nexport interface OtaCheckCurrentGlassesOptions {\n waitForBuildNumberMs?: number\n waitForBesVersionMs?: number\n waitForMtkVersionMs?: number\n refreshVersionInfo?: boolean\n fixClockBeforeCheck?: boolean\n /** Downgrade floor override (defaults to DOWNGRADE_FLOOR_VERSION_CODE); for tests. */\n floorVersionCode?: number\n}\n\nfunction emptyCheckResult(skippedReason?: OtaCheckSkippedReason): OtaCheckCurrentGlassesResult {\n return {\n hasCheckCompleted: false,\n updateAvailable: false,\n latestVersionInfo: null,\n updates: [],\n mtkPatch: null,\n besVersion: null,\n isApkDowngrade: false,\n manifestBody: null,\n updateInfo: null,\n isRequired: true,\n skippedReason,\n }\n}\n\nfunction glassesConnectedNow(): boolean {\n return isGlassesConnected(useGlassesStore.getState().connection)\n}\n\ntype ManifestFetchResult = {json: VersionJson | null; failureReason?: \"network\" | \"pin_unavailable\"}\n\nasync function fetchVersionInfoDetailed(url: string): Promise<ManifestFetchResult> {\n try {\n const response = await fetch(url)\n if (!response.ok) {\n console.error(\"Failed to fetch version info:\", response.status)\n // 4xx: the manifest does not exist (or is gone) — permanent for this app\n // build, retrying cannot help. Everything else is transient server/network\n // trouble and retryable.\n return {\n json: null,\n failureReason: response.status >= 400 && response.status < 500 ? \"pin_unavailable\" : \"network\",\n }\n }\n try {\n return {json: await response.json()}\n } catch (parseError) {\n console.error(\"OTA: manifest is not valid JSON:\", parseError)\n return {json: null, failureReason: \"pin_unavailable\"}\n }\n } catch (error) {\n console.error(\"OTA: Error fetching version info:\", error)\n return {json: null, failureReason: \"network\"}\n }\n}\n\nexport async function fetchVersionInfo(url: string): Promise<VersionJson | null> {\n return (await fetchVersionInfoDetailed(url)).json\n}\n\nexport function checkVersionUpdateAvailable(\n currentBuildNumber: string | undefined,\n versionJson: VersionJson | null,\n floorVersionCode: number = DOWNGRADE_FLOOR_VERSION_CODE,\n): boolean {\n return getApkUpdateDirection(currentBuildNumber, versionJson, floorVersionCode) !== null\n}\n\n/**\n * Direction of the pending APK change, or null when the glasses already match the manifest.\n * Every apps-shaped manifest the phone drives is an exact pin, so both directions are\n * actionable (mirroring the glasses-side `DowngradeGate`); the ancient top-level manifest\n * shape stays upgrade-only, and zeroed rescue pins are never actionable.\n */\nexport function getApkUpdateDirection(\n currentBuildNumber: string | undefined,\n versionJson: VersionJson | null,\n floorVersionCode: number = DOWNGRADE_FLOOR_VERSION_CODE,\n): \"upgrade\" | \"downgrade\" | null {\n if (!currentBuildNumber || !versionJson) {\n return null\n }\n\n const currentVersion = parseInt(currentBuildNumber, 10)\n if (isNaN(currentVersion)) {\n return null\n }\n\n let serverVersion: number | undefined\n let exactPin = false\n\n const appEntry = versionJson.apps?.[\"com.mentra.asg_client\"]\n if (appEntry) {\n serverVersion = appEntry.versionCode\n exactPin = true\n } else if (versionJson.versionCode) {\n serverVersion = versionJson.versionCode\n }\n\n if (!serverVersion || isNaN(serverVersion)) {\n return null\n }\n\n if (serverVersion > currentVersion) {\n return \"upgrade\"\n }\n // Downgrades require an exact pin AND a target at/above the enabled floor. A non-positive floor\n // disables downgrades entirely, matching ASG's fail-closed DowngradeGate — so the phone never\n // offers what the glasses would refuse.\n if (serverVersion < currentVersion && exactPin && floorVersionCode > 0 && serverVersion >= floorVersionCode) {\n return \"downgrade\"\n }\n return null\n}\n\nexport function getLatestVersionInfo(versionJson: VersionJson | null): VersionInfo | null {\n if (!versionJson) {\n return null\n }\n\n if (versionJson.apps?.[\"com.mentra.asg_client\"]) {\n return versionJson.apps[\"com.mentra.asg_client\"]\n }\n\n if (versionJson.versionCode) {\n return {\n versionCode: versionJson.versionCode,\n versionName: versionJson.versionName || \"\",\n downloadUrl: versionJson.downloadUrl || \"\",\n apkSize: versionJson.apkSize || 0,\n sha256: versionJson.sha256 || \"\",\n releaseNotes: versionJson.releaseNotes || \"\",\n }\n }\n\n return null\n}\n\nexport function findMatchingMtkPatch(\n patches: MtkPatch[] | undefined,\n currentVersion: string | undefined,\n): MtkPatch | null {\n if (!patches || !currentVersion) {\n return null\n }\n\n return (\n patches.find((patch) => {\n if (patch.start_firmware === currentVersion) {\n return true\n }\n const serverDate = patch.start_firmware.includes(\"_\")\n ? patch.start_firmware.split(\"_\").pop()\n : patch.start_firmware\n return serverDate === currentVersion\n }) || null\n )\n}\n\nexport function checkBesUpdate(besFirmware: BesFirmware | undefined, currentVersion: string | undefined): boolean {\n if (!besFirmware) {\n return false\n }\n\n if (!currentVersion) {\n console.log(`📱 BES current version unknown - assuming update needed (server: ${besFirmware.version})`)\n return true\n }\n return compareVersions(besFirmware.version, currentVersion) > 0\n}\n\nexport function compareVersions(version1: string, version2: string): number {\n if (version1.includes(\".\") && version2.includes(\".\")) {\n const parts1 = version1.split(\".\")\n const parts2 = version2.split(\".\")\n const maxLen = Math.max(parts1.length, parts2.length)\n\n for (let i = 0; i < maxLen; i++) {\n const v1 = i < parts1.length ? parseInt(parts1[i], 10) : 0\n const v2 = i < parts2.length ? parseInt(parts2[i], 10) : 0\n if (v1 !== v2) {\n return v1 - v2\n }\n }\n return 0\n }\n\n return version1.localeCompare(version2)\n}\n\nexport async function checkForOtaUpdate(\n otaVersionUrl: string,\n currentBuildNumber: string,\n currentMtkVersion?: string,\n currentBesVersion?: string,\n floorVersionCode: number = DOWNGRADE_FLOOR_VERSION_CODE,\n): Promise<OtaCheckResult> {\n try {\n console.log(\"OTA: Checking for OTA update - URL: \" + otaVersionUrl + \", current build: \" + currentBuildNumber)\n const fetched = await fetchVersionInfoDetailed(otaVersionUrl)\n const versionJson = fetched.json\n if (!versionJson) {\n return {\n hasCheckCompleted: false,\n updateAvailable: false,\n latestVersionInfo: null,\n updates: [],\n mtkPatch: null,\n besVersion: null,\n isApkDowngrade: false,\n manifestBody: null,\n checkFailureReason: fetched.failureReason ?? \"network\",\n }\n }\n const latestVersionInfo = getLatestVersionInfo(versionJson)\n\n // A manifest whose ASG pin is missing/zeroed cannot verify anything for modern\n // glasses (zeroed pins are only legitimate in the frozen legacy rescue manifests\n // that pre-39 glasses are checked against). Report the check as FAILED rather\n // than silently completing as \"up to date\" — an unverifiable state must never\n // present as a verified one.\n const currentVersionNumber = parseInt(currentBuildNumber, 10)\n const pinnedEntry = versionJson?.apps?.[\"com.mentra.asg_client\"]\n const pinnedVersion = pinnedEntry?.versionCode ?? versionJson?.versionCode\n if (\n versionJson &&\n Number.isFinite(currentVersionNumber) &&\n currentVersionNumber >= 39 &&\n !(typeof pinnedVersion === \"number\" && pinnedVersion > 0)\n ) {\n console.error(\n `OTA: manifest at ${otaVersionUrl} has no valid ASG pin (versionCode=${String(pinnedVersion)}) - treating check as failed`,\n )\n return {\n hasCheckCompleted: false,\n updateAvailable: false,\n latestVersionInfo: null,\n updates: [],\n mtkPatch: null,\n besVersion: null,\n isApkDowngrade: false,\n manifestBody: null,\n checkFailureReason: \"pin_unavailable\",\n }\n }\n\n const apkDirection = getApkUpdateDirection(currentBuildNumber, versionJson, floorVersionCode)\n const apkUpdateAvailable = apkDirection !== null\n console.log(\n `OTA: APK update available: ${apkUpdateAvailable} (current: ${currentBuildNumber}, direction: ${apkDirection ?? \"none\"})`,\n )\n\n const mtkPatch = findMatchingMtkPatch(versionJson?.mtk_patches, currentMtkVersion)\n const mtkUpdateAvailable = mtkPatch !== null\n if (!currentMtkVersion && versionJson?.mtk_patches?.length) {\n console.log(`OTA: MTK current version unknown - skipping MTK patch check`)\n }\n console.log(\n `OTA: MTK patch available: ${mtkUpdateAvailable ? \"yes\" : \"no\"} (current MTK: ${currentMtkVersion || \"unknown\"})`,\n )\n\n const besUpdateAvailable = checkBesUpdate(versionJson?.bes_firmware, currentBesVersion)\n console.log(`OTA: BES update available: ${besUpdateAvailable} (current BES: ${currentBesVersion || \"unknown\"})`)\n\n const updates: string[] = []\n if (apkUpdateAvailable) updates.push(\"apk\")\n if (mtkUpdateAvailable) updates.push(\"mtk\")\n if (besUpdateAvailable) updates.push(\"bes\")\n\n console.log(`OTA: OTA check result - updates available: ${updates.length > 0}, updates: [${updates.join(\", \")}]`)\n\n return {\n hasCheckCompleted: true,\n updateAvailable: updates.length > 0,\n latestVersionInfo,\n updates,\n mtkPatch,\n besVersion: versionJson?.bes_firmware?.version || null,\n isApkDowngrade: apkDirection === \"downgrade\",\n manifestBody: versionJson ? JSON.stringify(versionJson) : null,\n }\n } catch (error) {\n console.error(\"Error checking for OTA update:\", error)\n return {\n hasCheckCompleted: false,\n updateAvailable: false,\n latestVersionInfo: null,\n updates: [],\n mtkPatch: null,\n besVersion: null,\n isApkDowngrade: false,\n manifestBody: null,\n checkFailureReason: \"network\",\n }\n }\n}\n\nexport async function checkCurrentGlassesForUpdate(\n options: OtaCheckCurrentGlassesOptions = {},\n): Promise<OtaCheckCurrentGlassesResult> {\n const {\n waitForBuildNumberMs = 0,\n waitForBesVersionMs = 5000,\n waitForMtkVersionMs = 2000,\n refreshVersionInfo = true,\n fixClockBeforeCheck = true,\n floorVersionCode = DOWNGRADE_FLOOR_VERSION_CODE,\n } = options\n\n if (!glassesConnectedNow()) {\n return emptyCheckResult(\"disconnected\")\n }\n\n if (!hasConfiguredModernOtaManifestPin()) {\n console.log(\"OTA: check skipped - this app or Engine package has no embedded OTA manifest pin\")\n return emptyCheckResult(\"dev_build\")\n }\n\n if (refreshVersionInfo) {\n void BluetoothSdk.requestVersionInfo().catch((error) => {\n console.warn(\"OTA: Failed to request version_info from glasses:\", error)\n })\n }\n\n let buildNumber = useGlassesStore.getState().buildNumber\n if (!buildNumber && waitForBuildNumberMs > 0) {\n await waitForGlassesState(\"buildNumber\", (value) => !!value, waitForBuildNumberMs)\n buildNumber = useGlassesStore.getState().buildNumber\n }\n\n if (!buildNumber || !(parseInt(buildNumber, 10) > 0)) {\n // Covers absent, non-numeric, and zero build numbers: a glasses version we\n // cannot parse means nothing is verifiable, so skip rather than ghost an\n // \"up to date\" result from a comparison that never really ran.\n return emptyCheckResult(\"missing_build\")\n }\n\n if (!glassesConnectedNow()) {\n return emptyCheckResult(\"disconnected\")\n }\n\n let besFirmwareVersion = useGlassesStore.getState().besFirmwareVersion\n if (!besFirmwareVersion && waitForBesVersionMs > 0) {\n console.log(\"OTA: BES version still unknown - waiting up to 5s for it to arrive...\")\n await waitForGlassesState(\"besFirmwareVersion\", (value) => !!value, waitForBesVersionMs)\n besFirmwareVersion = useGlassesStore.getState().besFirmwareVersion\n if (besFirmwareVersion) {\n console.log(`OTA: BES version arrived: ${besFirmwareVersion}`)\n } else {\n console.log(\"OTA: BES version still unknown after extended wait - will assume BES update if published\")\n }\n }\n\n if (!glassesConnectedNow()) {\n return emptyCheckResult(\"disconnected\")\n }\n\n let mtkFirmwareVersion = useGlassesStore.getState().mtkFirmwareVersion\n if (!mtkFirmwareVersion && waitForMtkVersionMs > 0) {\n await waitForGlassesState(\"mtkFirmwareVersion\", (value) => !!value, waitForMtkVersionMs)\n mtkFirmwareVersion = useGlassesStore.getState().mtkFirmwareVersion\n }\n\n if (!glassesConnectedNow()) {\n return emptyCheckResult(\"disconnected\")\n }\n\n if (fixClockBeforeCheck) {\n const systemTimeMs = getGlassesSystemTimeMs()\n await maybeFixGlassesClockFromVersionInfo(systemTimeMs > 0 ? systemTimeMs : undefined).catch((error) => {\n console.warn(\"OTA: clock fix attempt failed; continuing OTA check\", error)\n })\n }\n\n const manifestUrl = resolveOtaManifestUrl(useGlassesStore.getState().otaVersionUrl, buildNumber)\n if (!manifestUrl) {\n console.log(\"OTA: check skipped - no OTA manifest pin resolved\")\n return emptyCheckResult(\"dev_build\")\n }\n const result = await checkForOtaUpdate(\n manifestUrl,\n buildNumber,\n mtkFirmwareVersion,\n besFirmwareVersion,\n floorVersionCode,\n )\n\n if (!result.hasCheckCompleted) {\n return {\n ...result,\n updateInfo: null,\n isRequired: true,\n manifestUrl,\n buildNumber,\n mtkFirmwareVersion,\n besFirmwareVersion,\n }\n }\n\n const mtkUpdatedThisSession = useGlassesStore.getState().mtkUpdatedThisSession\n let filteredUpdates = result.updates\n if (mtkUpdatedThisSession && filteredUpdates.includes(\"mtk\")) {\n console.log(\"OTA: Filtering out MTK - already updated this session (pending reboot)\")\n filteredUpdates = filteredUpdates.filter((update) => update !== \"mtk\")\n }\n\n // Pre-migration parity: an update is only surfaced when the manifest also\n // carries APK version metadata. A firmware-only manifest (mtk_patches /\n // bes_firmware with no APK entry) is therefore dropped here — same as the\n // legacy host check. Loud-log it so the case is visible in the field;\n // surfacing firmware-only updates is a deliberate behavior change to make\n // separately, not silently inside the boundary migration.\n if (filteredUpdates.length > 0 && !result.latestVersionInfo) {\n console.warn(\n `OTA: manifest lists updates [${filteredUpdates.join(\", \")}] but has no APK version metadata - ` +\n \"reporting no update (legacy behavior)\",\n )\n }\n const updateAvailable = filteredUpdates.length > 0 && !!result.latestVersionInfo\n const isApkDowngrade = result.isApkDowngrade && filteredUpdates.includes(\"apk\")\n const updateInfo = updateAvailable\n ? {\n available: true,\n versionCode: result.latestVersionInfo?.versionCode || 0,\n versionName: result.latestVersionInfo?.versionName || \"\",\n updates: filteredUpdates,\n totalSize: 0,\n ...(filteredUpdates.includes(\"bes\") && result.besVersion ? {besVersion: result.besVersion} : {}),\n isDowngrade: isApkDowngrade,\n }\n : null\n\n useGlassesStore.getState().setOtaUpdateAvailable(updateInfo)\n\n return {\n ...result,\n updateAvailable,\n updates: filteredUpdates,\n isApkDowngrade,\n updateInfo,\n // Required-update policy: an EXPLICIT isRequired in the manifest is the\n // release-engineering escape hatch and wins in both directions. When absent,\n // upgrades keep the historical forced default, but downgrades are always\n // skippable — a version change that resets glasses settings must be\n // declinable unless someone explicitly forced it.\n isRequired:\n result.latestVersionInfo?.isRequired === true\n ? true\n : result.latestVersionInfo?.isRequired === false\n ? false\n : !isApkDowngrade,\n manifestUrl,\n buildNumber,\n mtkFirmwareVersion,\n besFirmwareVersion,\n }\n}\n"]}
1
+ {"version":3,"file":"OtaUpdateCheckService.js","sourceRoot":"","sources":["../../src/services/OtaUpdateCheckService.ts"],"names":[],"mappings":"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","sourcesContent":["import BluetoothSdk, {type OtaUpdateInfo} from \"@mentra/bluetooth-sdk\"\nimport {ENGINE_RELEASE_METADATA} from \"../generated/releaseMetadata\"\nimport {getGlassesSystemTimeMs, isGlassesConnected, useGlassesStore, waitForGlassesState} from \"../stores/glasses\"\nimport {maybeFixGlassesClockFromVersionInfo} from \"./glassesClockSync\"\nimport {hasConfiguredModernOtaManifestPin, resolveOtaManifestUrl} from \"./otaManifestUrl\"\nimport {resolveOtaReleaseVersion} from \"./otaReleaseVersion\"\n\n/**\n * Phone-side mirror of ASG's `DOWNGRADE_FLOOR_VERSION_CODE`. Set to the versionCode of the first\n * release shipping the shared media root (the first downgrade-safe build). A non-positive value\n * means downgrades are disabled, so the phone must NOT offer them — offering a downgrade the\n * glasses' `DowngradeGate` will refuse leaves the version-change session with no install and the\n * glasses reporting a bare \"complete\", which would otherwise present as false success. Keep this\n * in lockstep with the ASG/recovery constant at release time.\n */\nexport const DOWNGRADE_FLOOR_VERSION_CODE = 0\n\nexport interface VersionInfo {\n versionCode: number\n versionName: string\n downloadUrl: string\n apkSize: number\n sha256: string\n releaseNotes: string\n isRequired?: boolean\n}\n\nexport interface MtkPatch {\n start_firmware: string\n end_firmware: string\n url: string\n sha256?: string\n}\n\nexport interface BesFirmware {\n version: string\n url: string\n sha256?: string\n}\n\nexport interface VersionJson {\n releaseVersion?: string\n apps?: {\n [packageName: string]: VersionInfo\n }\n mtk_patches?: MtkPatch[]\n bes_firmware?: BesFirmware\n versionCode?: number\n versionName?: string\n downloadUrl?: string\n apkSize?: number\n sha256?: string\n releaseNotes?: string\n}\n\nexport interface OtaCheckResult {\n hasCheckCompleted: boolean\n updateAvailable: boolean\n latestVersionInfo: VersionInfo | null\n updates: string[]\n mtkPatch: MtkPatch | null\n besVersion: string | null\n /** True when the pending APK step installs an OLDER build than the glasses currently run. */\n isApkDowngrade: boolean\n /**\n * Raw manifest JSON (stringified) this check actually fetched, or null when the\n * fetch failed. Lets change-watchers baseline on exactly what the check saw,\n * instead of racing it with a second fetch of the same URL.\n */\n manifestBody: string | null\n /** Coordinated release identity for the selected OTA pin. */\n releaseVersion: string | null\n /**\n * Why the check failed, when it did. \"network\" is retryable (connection/server\n * trouble); \"pin_unavailable\" is permanent for this app build (manifest 404/gone,\n * unparseable, or carrying no valid ASG pin) — the remedy is updating the app.\n */\n checkFailureReason?: \"network\" | \"pin_unavailable\"\n}\n\nexport type OtaCheckSkippedReason = \"disconnected\" | \"missing_build\" | \"dev_build\"\n\nexport interface OtaCheckCurrentGlassesResult extends OtaCheckResult {\n updateInfo: OtaUpdateInfo | null\n isRequired: boolean\n skippedReason?: OtaCheckSkippedReason\n manifestUrl?: string\n buildNumber?: string\n mtkFirmwareVersion?: string\n besFirmwareVersion?: string\n}\n\nexport interface OtaCheckCurrentGlassesOptions {\n waitForBuildNumberMs?: number\n waitForBesVersionMs?: number\n waitForMtkVersionMs?: number\n refreshVersionInfo?: boolean\n fixClockBeforeCheck?: boolean\n /** Downgrade floor override (defaults to DOWNGRADE_FLOOR_VERSION_CODE); for tests. */\n floorVersionCode?: number\n}\n\nfunction emptyCheckResult(skippedReason?: OtaCheckSkippedReason): OtaCheckCurrentGlassesResult {\n return {\n hasCheckCompleted: false,\n updateAvailable: false,\n latestVersionInfo: null,\n updates: [],\n mtkPatch: null,\n besVersion: null,\n isApkDowngrade: false,\n manifestBody: null,\n releaseVersion: null,\n updateInfo: null,\n isRequired: true,\n skippedReason,\n }\n}\n\nfunction glassesConnectedNow(): boolean {\n return isGlassesConnected(useGlassesStore.getState().connection)\n}\n\ntype ManifestFetchResult = {json: VersionJson | null; failureReason?: \"network\" | \"pin_unavailable\"}\n\nasync function fetchVersionInfoDetailed(url: string): Promise<ManifestFetchResult> {\n try {\n const response = await fetch(url)\n if (!response.ok) {\n console.warn(\"Failed to fetch version info:\", response.status)\n // 4xx: the manifest does not exist (or is gone) — permanent for this app\n // build, retrying cannot help. Everything else is transient server/network\n // trouble and retryable.\n return {\n json: null,\n failureReason: response.status >= 400 && response.status < 500 ? \"pin_unavailable\" : \"network\",\n }\n }\n try {\n return {json: await response.json()}\n } catch (parseError) {\n console.warn(\"OTA: manifest is not valid JSON:\", parseError)\n return {json: null, failureReason: \"pin_unavailable\"}\n }\n } catch (error) {\n console.warn(\"OTA: Error fetching version info:\", error)\n return {json: null, failureReason: \"network\"}\n }\n}\n\nexport async function fetchVersionInfo(url: string): Promise<VersionJson | null> {\n return (await fetchVersionInfoDetailed(url)).json\n}\n\nexport function checkVersionUpdateAvailable(\n currentBuildNumber: string | undefined,\n versionJson: VersionJson | null,\n floorVersionCode: number = DOWNGRADE_FLOOR_VERSION_CODE,\n): boolean {\n return getApkUpdateDirection(currentBuildNumber, versionJson, floorVersionCode) !== null\n}\n\n/**\n * Direction of the pending APK change, or null when the glasses already match the manifest.\n * Every apps-shaped manifest the phone drives is an exact pin, so both directions are\n * actionable (mirroring the glasses-side `DowngradeGate`); the ancient top-level manifest\n * shape stays upgrade-only, and zeroed rescue pins are never actionable.\n */\nexport function getApkUpdateDirection(\n currentBuildNumber: string | undefined,\n versionJson: VersionJson | null,\n floorVersionCode: number = DOWNGRADE_FLOOR_VERSION_CODE,\n): \"upgrade\" | \"downgrade\" | null {\n if (!currentBuildNumber || !versionJson) {\n return null\n }\n\n const currentVersion = parseInt(currentBuildNumber, 10)\n if (isNaN(currentVersion)) {\n return null\n }\n\n let serverVersion: number | undefined\n let exactPin = false\n\n const appEntry = versionJson.apps?.[\"com.mentra.asg_client\"]\n if (appEntry) {\n serverVersion = appEntry.versionCode\n exactPin = true\n } else if (versionJson.versionCode) {\n serverVersion = versionJson.versionCode\n }\n\n if (!serverVersion || isNaN(serverVersion)) {\n return null\n }\n\n if (serverVersion > currentVersion) {\n return \"upgrade\"\n }\n // Downgrades require an exact pin AND a target at/above the enabled floor. A non-positive floor\n // disables downgrades entirely, matching ASG's fail-closed DowngradeGate — so the phone never\n // offers what the glasses would refuse.\n if (serverVersion < currentVersion && exactPin && floorVersionCode > 0 && serverVersion >= floorVersionCode) {\n return \"downgrade\"\n }\n return null\n}\n\nexport function getLatestVersionInfo(versionJson: VersionJson | null): VersionInfo | null {\n if (!versionJson) {\n return null\n }\n\n if (versionJson.apps?.[\"com.mentra.asg_client\"]) {\n return versionJson.apps[\"com.mentra.asg_client\"]\n }\n\n if (versionJson.versionCode) {\n return {\n versionCode: versionJson.versionCode,\n versionName: versionJson.versionName || \"\",\n downloadUrl: versionJson.downloadUrl || \"\",\n apkSize: versionJson.apkSize || 0,\n sha256: versionJson.sha256 || \"\",\n releaseNotes: versionJson.releaseNotes || \"\",\n }\n }\n\n return null\n}\n\nexport function findMatchingMtkPatch(\n patches: MtkPatch[] | undefined,\n currentVersion: string | undefined,\n): MtkPatch | null {\n if (!patches || !currentVersion) {\n return null\n }\n\n return (\n patches.find((patch) => {\n if (patch.start_firmware === currentVersion) {\n return true\n }\n const serverDate = patch.start_firmware.includes(\"_\")\n ? patch.start_firmware.split(\"_\").pop()\n : patch.start_firmware\n return serverDate === currentVersion\n }) || null\n )\n}\n\nexport function checkBesUpdate(besFirmware: BesFirmware | undefined, currentVersion: string | undefined): boolean {\n if (!besFirmware) {\n return false\n }\n\n if (!currentVersion) {\n console.log(`📱 BES current version unknown - assuming update needed (server: ${besFirmware.version})`)\n return true\n }\n return compareVersions(besFirmware.version, currentVersion) > 0\n}\n\nexport function compareVersions(version1: string, version2: string): number {\n if (version1.includes(\".\") && version2.includes(\".\")) {\n const parts1 = version1.split(\".\")\n const parts2 = version2.split(\".\")\n const maxLen = Math.max(parts1.length, parts2.length)\n\n for (let i = 0; i < maxLen; i++) {\n const v1 = i < parts1.length ? parseInt(parts1[i], 10) : 0\n const v2 = i < parts2.length ? parseInt(parts2[i], 10) : 0\n if (v1 !== v2) {\n return v1 - v2\n }\n }\n return 0\n }\n\n return version1.localeCompare(version2)\n}\n\nexport async function checkForOtaUpdate(\n otaVersionUrl: string,\n currentBuildNumber: string,\n currentMtkVersion?: string,\n currentBesVersion?: string,\n floorVersionCode: number = DOWNGRADE_FLOOR_VERSION_CODE,\n): Promise<OtaCheckResult> {\n try {\n console.log(\"OTA: Checking for OTA update - URL: \" + otaVersionUrl + \", current build: \" + currentBuildNumber)\n const fetched = await fetchVersionInfoDetailed(otaVersionUrl)\n const versionJson = fetched.json\n if (!versionJson) {\n return {\n hasCheckCompleted: false,\n updateAvailable: false,\n latestVersionInfo: null,\n updates: [],\n mtkPatch: null,\n besVersion: null,\n isApkDowngrade: false,\n manifestBody: null,\n releaseVersion: null,\n checkFailureReason: fetched.failureReason ?? \"network\",\n }\n }\n const latestVersionInfo = getLatestVersionInfo(versionJson)\n\n // A manifest whose ASG pin is missing/zeroed cannot verify anything for modern\n // glasses (zeroed pins are only legitimate in the frozen legacy rescue manifests\n // that pre-39 glasses are checked against). Report the check as FAILED rather\n // than silently completing as \"up to date\" — an unverifiable state must never\n // present as a verified one.\n const currentVersionNumber = parseInt(currentBuildNumber, 10)\n const pinnedEntry = versionJson?.apps?.[\"com.mentra.asg_client\"]\n const pinnedVersion = pinnedEntry?.versionCode ?? versionJson?.versionCode\n if (\n versionJson &&\n Number.isFinite(currentVersionNumber) &&\n currentVersionNumber >= 39 &&\n !(typeof pinnedVersion === \"number\" && pinnedVersion > 0)\n ) {\n console.error(\n `OTA: manifest at ${otaVersionUrl} has no valid ASG pin (versionCode=${String(pinnedVersion)}) - treating check as failed`,\n )\n return {\n hasCheckCompleted: false,\n updateAvailable: false,\n latestVersionInfo: null,\n updates: [],\n mtkPatch: null,\n besVersion: null,\n isApkDowngrade: false,\n manifestBody: null,\n releaseVersion: null,\n checkFailureReason: \"pin_unavailable\",\n }\n }\n\n const apkDirection = getApkUpdateDirection(currentBuildNumber, versionJson, floorVersionCode)\n const apkUpdateAvailable = apkDirection !== null\n console.log(\n `OTA: APK update available: ${apkUpdateAvailable} (current: ${currentBuildNumber}, direction: ${apkDirection ?? \"none\"})`,\n )\n\n const mtkPatch = findMatchingMtkPatch(versionJson?.mtk_patches, currentMtkVersion)\n const mtkUpdateAvailable = mtkPatch !== null\n if (!currentMtkVersion && versionJson?.mtk_patches?.length) {\n console.log(`OTA: MTK current version unknown - skipping MTK patch check`)\n }\n console.log(\n `OTA: MTK patch available: ${mtkUpdateAvailable ? \"yes\" : \"no\"} (current MTK: ${currentMtkVersion || \"unknown\"})`,\n )\n\n const besUpdateAvailable = checkBesUpdate(versionJson?.bes_firmware, currentBesVersion)\n console.log(`OTA: BES update available: ${besUpdateAvailable} (current BES: ${currentBesVersion || \"unknown\"})`)\n\n const updates: string[] = []\n if (apkUpdateAvailable) updates.push(\"apk\")\n if (mtkUpdateAvailable) updates.push(\"mtk\")\n if (besUpdateAvailable) updates.push(\"bes\")\n\n console.log(`OTA: OTA check result - updates available: ${updates.length > 0}, updates: [${updates.join(\", \")}]`)\n\n return {\n hasCheckCompleted: true,\n updateAvailable: updates.length > 0,\n latestVersionInfo,\n updates,\n mtkPatch,\n besVersion: versionJson?.bes_firmware?.version || null,\n isApkDowngrade: apkDirection === \"downgrade\",\n manifestBody: versionJson ? JSON.stringify(versionJson) : null,\n releaseVersion: resolveOtaReleaseVersion({\n manifestReleaseVersion: versionJson.releaseVersion,\n manifestUrl: otaVersionUrl,\n packagedManifestUrl: ENGINE_RELEASE_METADATA.otaManifestUrl,\n packagedReleaseIdentity: ENGINE_RELEASE_METADATA.releaseIdentity,\n }),\n }\n } catch (error) {\n console.error(\"Error checking for OTA update:\", error)\n return {\n hasCheckCompleted: false,\n updateAvailable: false,\n latestVersionInfo: null,\n updates: [],\n mtkPatch: null,\n besVersion: null,\n isApkDowngrade: false,\n manifestBody: null,\n releaseVersion: null,\n checkFailureReason: \"network\",\n }\n }\n}\n\nexport async function checkCurrentGlassesForUpdate(\n options: OtaCheckCurrentGlassesOptions = {},\n): Promise<OtaCheckCurrentGlassesResult> {\n const {\n waitForBuildNumberMs = 0,\n waitForBesVersionMs = 5000,\n waitForMtkVersionMs = 2000,\n refreshVersionInfo = true,\n fixClockBeforeCheck = true,\n floorVersionCode = DOWNGRADE_FLOOR_VERSION_CODE,\n } = options\n\n if (!glassesConnectedNow()) {\n return emptyCheckResult(\"disconnected\")\n }\n\n if (!hasConfiguredModernOtaManifestPin()) {\n console.log(\"OTA: check skipped - this app or Engine package has no embedded OTA manifest pin\")\n return emptyCheckResult(\"dev_build\")\n }\n\n if (refreshVersionInfo) {\n void BluetoothSdk.requestVersionInfo()\n .then((versionInfo) => {\n useGlassesStore.getState().setGlassesInfo(versionInfo)\n })\n .catch((error) => {\n console.warn(\"OTA: Failed to request version_info from glasses:\", error)\n })\n }\n\n let buildNumber = useGlassesStore.getState().buildNumber\n if (!buildNumber && waitForBuildNumberMs > 0) {\n await waitForGlassesState(\"buildNumber\", (value) => !!value, waitForBuildNumberMs)\n buildNumber = useGlassesStore.getState().buildNumber\n }\n\n if (!buildNumber || !(parseInt(buildNumber, 10) > 0)) {\n // Covers absent, non-numeric, and zero build numbers: a glasses version we\n // cannot parse means nothing is verifiable, so skip rather than ghost an\n // \"up to date\" result from a comparison that never really ran.\n return emptyCheckResult(\"missing_build\")\n }\n\n if (!glassesConnectedNow()) {\n return emptyCheckResult(\"disconnected\")\n }\n\n let besFirmwareVersion = useGlassesStore.getState().besFirmwareVersion\n if (!besFirmwareVersion && waitForBesVersionMs > 0) {\n console.log(\"OTA: BES version still unknown - waiting up to 5s for it to arrive...\")\n await waitForGlassesState(\"besFirmwareVersion\", (value) => !!value, waitForBesVersionMs)\n besFirmwareVersion = useGlassesStore.getState().besFirmwareVersion\n if (besFirmwareVersion) {\n console.log(`OTA: BES version arrived: ${besFirmwareVersion}`)\n } else {\n console.log(\"OTA: BES version still unknown after extended wait - will assume BES update if published\")\n }\n }\n\n if (!glassesConnectedNow()) {\n return emptyCheckResult(\"disconnected\")\n }\n\n let mtkFirmwareVersion = useGlassesStore.getState().mtkFirmwareVersion\n if (!mtkFirmwareVersion && waitForMtkVersionMs > 0) {\n await waitForGlassesState(\"mtkFirmwareVersion\", (value) => !!value, waitForMtkVersionMs)\n mtkFirmwareVersion = useGlassesStore.getState().mtkFirmwareVersion\n }\n\n if (!glassesConnectedNow()) {\n return emptyCheckResult(\"disconnected\")\n }\n\n if (fixClockBeforeCheck) {\n const systemTimeMs = getGlassesSystemTimeMs()\n await maybeFixGlassesClockFromVersionInfo(systemTimeMs > 0 ? systemTimeMs : undefined).catch((error) => {\n console.warn(\"OTA: clock fix attempt failed; continuing OTA check\", error)\n })\n }\n\n const manifestUrl = resolveOtaManifestUrl(useGlassesStore.getState().otaVersionUrl, buildNumber)\n if (!manifestUrl) {\n console.log(\"OTA: check skipped - no OTA manifest pin resolved\")\n return emptyCheckResult(\"dev_build\")\n }\n const result = await checkForOtaUpdate(\n manifestUrl,\n buildNumber,\n mtkFirmwareVersion,\n besFirmwareVersion,\n floorVersionCode,\n )\n\n if (!result.hasCheckCompleted) {\n return {\n ...result,\n updateInfo: null,\n isRequired: true,\n manifestUrl,\n buildNumber,\n mtkFirmwareVersion,\n besFirmwareVersion,\n }\n }\n\n const mtkUpdatedThisSession = useGlassesStore.getState().mtkUpdatedThisSession\n let filteredUpdates = result.updates\n if (mtkUpdatedThisSession && filteredUpdates.includes(\"mtk\")) {\n console.log(\"OTA: Filtering out MTK - already updated this session (pending reboot)\")\n filteredUpdates = filteredUpdates.filter((update) => update !== \"mtk\")\n }\n\n // Pre-migration parity: an update is only surfaced when the manifest also\n // carries APK version metadata. A firmware-only manifest (mtk_patches /\n // bes_firmware with no APK entry) is therefore dropped here — same as the\n // legacy host check. Loud-log it so the case is visible in the field;\n // surfacing firmware-only updates is a deliberate behavior change to make\n // separately, not silently inside the boundary migration.\n if (filteredUpdates.length > 0 && !result.latestVersionInfo) {\n console.warn(\n `OTA: manifest lists updates [${filteredUpdates.join(\", \")}] but has no APK version metadata - ` +\n \"reporting no update (legacy behavior)\",\n )\n }\n const updateAvailable = filteredUpdates.length > 0 && !!result.latestVersionInfo\n const isApkDowngrade = result.isApkDowngrade && filteredUpdates.includes(\"apk\")\n const updateInfo = updateAvailable\n ? {\n available: true,\n versionCode: result.latestVersionInfo?.versionCode || 0,\n versionName: result.latestVersionInfo?.versionName || \"\",\n updates: filteredUpdates,\n totalSize: 0,\n ...(filteredUpdates.includes(\"bes\") && result.besVersion ? {besVersion: result.besVersion} : {}),\n isDowngrade: isApkDowngrade,\n }\n : null\n\n useGlassesStore.getState().setOtaUpdateAvailable(updateInfo)\n\n return {\n ...result,\n updateAvailable,\n updates: filteredUpdates,\n isApkDowngrade,\n updateInfo,\n // Required-update policy: an EXPLICIT isRequired in the manifest is the\n // release-engineering escape hatch and wins in both directions. When absent,\n // upgrades keep the historical forced default, but downgrades are always\n // skippable — a version change that resets glasses settings must be\n // declinable unless someone explicitly forced it.\n isRequired:\n result.latestVersionInfo?.isRequired === true\n ? true\n : result.latestVersionInfo?.isRequired === false\n ? false\n : !isApkDowngrade,\n manifestUrl,\n buildNumber,\n mtkFirmwareVersion,\n besFirmwareVersion,\n }\n}\n"]}
@@ -104,7 +104,7 @@ export class PhonePhotoCoordinator {
104
104
  // to time out. Fail fast with a typed error.
105
105
  //
106
106
  // We DON'T pre-check `hasCamera` here — the canonical capability data
107
- // lives in `getModelCapabilities(deviceModel)` from @mentra/types and
107
+ // lives in `getModelCapabilities(deviceModel)` from @mentra/engine and
108
108
  // pulling that into this file would add a cross-package import. If a
109
109
  // cameraless device receives the BLE photo command, the glasses-side
110
110
  // handler will return a photo_response error within ~1s and the gated
@@ -1 +1 @@
1
- {"version":3,"file":"PhonePhotoCoordinator.js","sourceRoot":"","sources":["../../src/services/PhonePhotoCoordinator.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * PhonePhotoCoordinator — owns local-miniapp takePhoto() end-to-end.\n *\n * Architecture:\n * miniapp → SDK → LocalMiniappRuntime → photo runtime hook\n * → coordinator.takePhoto(packageName, opts)\n * ├── (precheck) glasses connected + hasCamera\n * ├── cloudClientService.startManagedPhoto → {requestId, uploadUrl, readUrl} (cloud-v2 runtime presign)\n * ├── BluetoothSdk.requestPhoto(requestId, size, uploadUrl, compress, sound)\n * └── race:\n * - cloudClientService.awaitManagedPhotoReady(requestId) resolves on photo.ready push\n * - BluetoothSdk.requestPhoto rejects if terminal photo_response is an error\n * - handlePhotoError(requestId, code, message) rejects if MantleManager observes\n * the same BLE photo_response error before the native promise crosses the bridge\n *\n * `activeRequests` lets MantleManager's gated `photo_response` listener\n * short-circuit our long-poll with a typed error (CAMERA_BUSY, BATTERY_LOW,\n * etc.) instead of waiting 30s for cloud's timeout.\n */\n\nimport BluetoothSdk from \"@mentra/bluetooth-sdk/internal\"\nimport type {PhotoSize, PhotoTransferMethod} from \"@mentra/bluetooth-sdk/internal\"\nimport {cloudClientService} from \"./CloudClientService\"\nimport {isGlassesConnected} from \"./GlassesReadiness\"\nimport {useGlassesStore} from \"../stores/glasses\"\n\ninterface PhotoResult {\n photoUrl: string\n mimeType: string\n size: number\n}\n\n/** Map legacy/cloud size names onto the native take_photo enum. */\nfunction normalizePhotoSize(value: unknown): PhotoSize {\n if (typeof value !== \"string\") return \"medium\"\n switch (value) {\n case \"small\":\n return \"low\"\n case \"large\":\n return \"high\"\n case \"full\":\n return \"max\"\n default:\n return ([\"low\", \"medium\", \"high\", \"max\"] as const).includes(value as PhotoSize) ? (value as PhotoSize) : \"medium\"\n }\n}\n\nexport interface PhotoOpts {\n /** Legacy cloud size names are normalized before the native take_photo command. */\n size?: \"low\" | \"medium\" | \"high\" | \"max\" | \"small\" | \"large\" | \"full\"\n mode?: \"photo\" | \"text\"\n /** Select direct-only, phone-relayed BLE, or the default Wi-Fi/BLE fallback policy. */\n transferMethod?: PhotoTransferMethod\n compress?: \"none\" | \"low\" | \"medium\" | \"high\"\n sound?: boolean\n saveToGallery?: boolean\n exposureTimeNs?: number\n iso?: number | null\n aeExposureDivisor?: number\n isoCap?: number\n noiseReduction?: boolean\n edgeEnhancement?: boolean\n /** ZSL preview buffering. */\n zsl?: boolean\n /** MFNR still capture. */\n mfnr?: boolean\n ispDigitalGain?: number\n ispAnalogGain?: string\n}\n\nexport interface PhotoTaken {\n photoUrl: string\n mimeType: string\n size: number\n requestId: string\n}\n\n/** Pipeline stage a photo request failed at — surfaced to the miniapp so a dev\n * sees exactly where it broke, not just a flattened message. */\nexport type PhotoStage = \"presign\" | \"command\" | \"capture\" | \"upload\" | \"push\"\n/** Transport in play at the point of failure. */\nexport type PhotoTransport = \"cloud-rest\" | \"ble\" | \"wifi\" | \"ws\"\n\nexport class PhotoError extends Error {\n constructor(\n public readonly code: string,\n message: string,\n public readonly stage?: PhotoStage,\n public readonly transport?: PhotoTransport,\n ) {\n super(message)\n this.name = \"PhotoError\"\n }\n}\n\nfunction parsePhotoTransferMethod(value: unknown): PhotoTransferMethod | undefined {\n if (value === undefined) return undefined\n if (value === \"auto\" || value === \"direct\" || value === \"ble\") return value\n throw new PhotoError(\n \"INVALID_ARGUMENT\",\n `Invalid transferMethod ${JSON.stringify(value)}. Expected \"auto\", \"direct\", or \"ble\".`,\n )\n}\n\ninterface ActiveRequest {\n packageName: string\n abort: AbortController\n resolve: (r: PhotoResult) => void\n reject: (err: Error) => void\n}\n\n/**\n * Last-resort ceiling for the complete capture + encode + transfer + upload\n * pipeline. This must exceed the managed-photo service's 30s ready-push\n * timeout so the cloud can return its more specific failure first.\n *\n * Text mode commonly spends several seconds detecting/cropping the region and\n * can take another 10s+ to transfer over BLE. A 15s ceiling incorrectly\n * rejected successful captures while their upload was still in progress.\n */\nexport const CAPTURE_PIPELINE_TIMEOUT_MS = 45_000\nexport const CAMERA_WARM_UP_DEFAULT_DURATION_MS = 15_000\nexport const CAMERA_WARM_UP_MAX_DURATION_MS = 60_000\n\nlet bleRequestCounter = 0\n\n/** Short 4-hex-char correlation id sent over BLE instead of the full cloud\n * requestId UUID (wire v2 keeps BLE JSON small). */\nfunction mintBleRequestId(): string {\n bleRequestCounter = (bleRequestCounter + 1) & 0xffff\n return bleRequestCounter.toString(16).padStart(4, \"0\")\n}\n\nfunction toNativeCompression(compress: PhotoOpts[\"compress\"]): \"none\" | \"medium\" | \"heavy\" {\n if (compress === \"high\") return \"heavy\"\n if (compress === \"low\" || compress === \"medium\") return \"medium\"\n return \"none\"\n}\n\nexport class PhonePhotoCoordinator {\n // Cloud requestId → in-flight slot. The gated photo_response listener\n // (DeviceEventRouter) resolves short BLE ids via bleIdToCloud before calling\n // owns() / handlePhotoError().\n private readonly activeRequests = new Map<string, ActiveRequest>()\n /** Short BLE correlation id (4-char hex) → full cloud requestId. */\n private readonly bleIdToCloud = new Map<string, string>()\n private readonly activeWarmUps = new Map<\n string,\n {requestId: string; durationMs: number; expiryTimer?: ReturnType<typeof setTimeout>}\n >()\n\n /** Report-safe camera ownership snapshot for incident diagnostics. */\n getDiagnosticSnapshot(): Record<string, unknown> {\n return {\n captureOwners: [...new Set([...this.activeRequests.values()].map((request) => request.packageName))].sort(),\n warmUpOwners: [...this.activeWarmUps.keys()].sort(),\n activeCaptureCount: this.activeRequests.size,\n }\n }\n\n async takePhoto(packageName: string, opts: PhotoOpts): Promise<PhotoTaken> {\n const transferMethod = parsePhotoTransferMethod(opts.transferMethod)\n\n // Pre-check: if glasses aren't even connected, the BLE photo command\n // would be sent into the void and we'd wait 30s for the cloud long-poll\n // to time out. Fail fast with a typed error.\n //\n // We DON'T pre-check `hasCamera` here — the canonical capability data\n // lives in `getModelCapabilities(deviceModel)` from @mentra/types and\n // pulling that into this file would add a cross-package import. If a\n // cameraless device receives the BLE photo command, the glasses-side\n // handler will return a photo_response error within ~1s and the gated\n // photo_response listener in MantleManager will short-circuit our\n // long-poll. Slower but correct.\n if (!isGlassesConnected(useGlassesStore.getState().connection)) {\n throw new PhotoError(\"GLASSES_NOT_CONNECTED\", \"Glasses are not connected\", \"command\", \"ble\")\n }\n\n // Text-mode sensor resolution is owned by ASG constants; keep cloud metadata on a stable\n // high-capacity tier and let the glasses ignore the public size when mode=text.\n const captureSize = opts.size ?? \"medium\"\n\n // 1) Presign via the cloud-v2 managed-photo service. Local miniapps use\n // ONLY the cloud-v2 path: the runtime presigns upload+read URLs and the\n // phone (as the device controller) delivers the bytes; the legacy\n // backend_url mint is gone from this flow.\n let requestId: string\n let uploadUrl: string\n let readUrl: string\n const flowStarted = performance.now()\n try {\n const presignStarted = performance.now()\n const r = await cloudClientService.startManagedPhoto({\n size: opts.mode === \"text\" ? \"max\" : captureSize,\n })\n const presignMs = Math.round(performance.now() - presignStarted)\n if (typeof __DEV__ !== \"undefined\" && __DEV__) {\n console.debug(\n `[PhonePhotoCoordinator] presign ${presignMs}ms size=${opts.mode === \"text\" ? \"max\" : captureSize} mode=${\n opts.mode ?? \"photo\"\n }`,\n )\n }\n requestId = r.requestId\n uploadUrl = r.uploadUrl\n readUrl = r.readUrl\n } catch (err) {\n throw this.toPhotoError(err, \"PHOTO_REQUEST_FAILED\", \"presign\", \"cloud-rest\")\n }\n\n // 2) Build the outcome Promise FIRST so both resolve+reject handles\n // are wired into activeRequests BEFORE any code path can produce\n // an error. Without this, a fast BLE photo_response (BATTERY_LOW\n // etc.) racing with the Promise constructor could fire\n // handlePhotoError() against a no-op rejectFn and silently drop\n // the error.\n // When the managed-photo upload URL is loopback (the local storage provider\n // reached over `adb reverse`), the glasses cannot reach it over WiFi —\n // localhost on the glasses is the glasses. Force BLE transfer so the phone\n // (which CAN reach the reversed runtime) relays the bytes; a public r2/s3\n // presigned URL stays on \"auto\".\n const isLoopbackUpload = /^https?:\\/\\/(localhost|127\\.0\\.0\\.1|10\\.0\\.2\\.2)\\b/.test(uploadUrl)\n\n const abort = new AbortController()\n const bleRequestId = mintBleRequestId()\n this.bleIdToCloud.set(bleRequestId, requestId)\n\n const outcome = new Promise<PhotoResult>((resolve, reject) => {\n this.activeRequests.set(requestId, {packageName, abort, resolve, reject})\n })\n\n // Last-resort watchdog for a wedged pipeline. The managed-photo ready push\n // normally resolves or rejects first; this only prevents an indefinite hang\n // if both the native terminal response and cloud push disappear.\n const captureWatchdog = setTimeout(() => {\n const e = this.activeRequests.get(requestId)\n if (!e) return\n e.abort.abort()\n e.reject(\n new PhotoError(\n \"CAPTURE_TIMEOUT\",\n \"Photo capture did not complete. The take_photo command, media processing, transfer, or upload may have stalled.\",\n \"capture\",\n isLoopbackUpload ? \"ble\" : \"wifi\",\n ),\n )\n }, CAPTURE_PIPELINE_TIMEOUT_MS)\n // Clear the watchdog on BOTH arms. Not `.finally()`: that would mint a new\n // promise that re-rejects unobserved whenever the photo fails (the caller\n // only awaits `outcome` itself), i.e. an unhandled rejection per failure.\n const clearWatchdog = () => clearTimeout(captureWatchdog)\n void outcome.then(clearWatchdog, clearWatchdog)\n\n // 3) Drive glasses over BLE. requestPhoto now resolves at terminal\n // photo_response success, so run it beside the cloud poll instead of\n // awaiting it before polling. Native defaults transferMethod to \"auto\"\n // (Wi-Fi direct with BLE fallback) unless the miniapp forces BLE.\n try {\n void BluetoothSdk.requestPhoto({\n requestId: bleRequestId,\n appId: packageName,\n size: normalizePhotoSize(captureSize),\n mode: opts.mode ?? \"photo\",\n webhookUrl: uploadUrl,\n authToken: null,\n ...(isLoopbackUpload ? {transferMethod: \"ble\" as const} : transferMethod ? {transferMethod} : {}),\n compress: toNativeCompression(opts.compress),\n save: opts.saveToGallery ?? false,\n sound: opts.sound ?? true,\n exposureTimeNs: opts.exposureTimeNs ?? null,\n iso: opts.iso,\n aeExposureDivisor: opts.aeExposureDivisor,\n isoCap: opts.isoCap,\n noiseReduction: opts.noiseReduction,\n edgeEnhancement: opts.edgeEnhancement,\n ...(opts.zsl != null ? {zsl: opts.zsl} : {}),\n ...(opts.mfnr != null ? {mfnr: opts.mfnr} : {}),\n ispDigitalGain: opts.ispDigitalGain,\n ispAnalogGain: opts.ispAnalogGain,\n }).catch((err) => {\n const e = this.activeRequests.get(requestId)\n if (!e) return\n e.abort.abort()\n e.reject(this.toPhotoError(err, \"BLE_SEND_FAILED\", \"command\", \"ble\"))\n })\n } catch (err) {\n this.activeRequests.delete(requestId)\n this.bleIdToCloud.delete(bleRequestId)\n throw this.toPhotoError(err, \"BLE_SEND_FAILED\", \"command\", \"ble\")\n }\n\n // 4) Await the runtime's photo.ready push (replaces the legacy long-poll).\n // handlePhotoError races against it and uses the same entry to reject\n // first.\n cloudClientService\n .awaitManagedPhotoReady(requestId)\n .then((res) => {\n const e = this.activeRequests.get(requestId)\n if (!e) return // already settled by handlePhotoError\n e.resolve({photoUrl: res.readUrl ?? readUrl, mimeType: \"image/jpeg\", size: -1})\n })\n .catch((err) => {\n const e = this.activeRequests.get(requestId)\n if (e) e.reject(this.toPhotoError(err, \"POLL_FAILED\", \"push\", \"ws\"))\n })\n\n try {\n const result = await outcome\n if (typeof __DEV__ !== \"undefined\" && __DEV__) {\n console.debug(\n `[PhonePhotoCoordinator] takePhoto complete ${Math.round(\n performance.now() - flowStarted,\n )}ms requestId=${requestId}`,\n )\n }\n return {\n photoUrl: result.photoUrl,\n mimeType: result.mimeType,\n size: result.size,\n requestId,\n }\n } finally {\n this.activeRequests.delete(requestId)\n this.bleIdToCloud.delete(bleRequestId)\n }\n }\n\n /** Map a short BLE requestId from photo_response back to the cloud requestId. */\n resolveCloudRequestId(bleOrCloudId: string): string {\n return this.bleIdToCloud.get(bleOrCloudId) ?? bleOrCloudId\n }\n\n /**\n * Pre-warm the glasses camera so the next takePhoto() is near-instant.\n *\n * Pure BLE — NO cloud presign, NO upload, NO long-poll. The phone mints and owns\n * the requestId, sends the warm-up command, and resolves when the camera reports\n * ready (the native promise resolves on the ready status event).\n */\n async warmUpCamera(\n packageName: string,\n opts: {\n size?: \"low\" | \"medium\" | \"high\" | \"max\"\n mode?: \"photo\" | \"text\"\n exposureTimeNs?: number\n durationMs?: number\n zsl?: boolean\n mfnr?: boolean\n },\n ): Promise<void> {\n // Pre-check: if glasses aren't connected, the BLE warm-up command would be\n // sent into the void. Fail fast with a typed error.\n if (!isGlassesConnected(useGlassesStore.getState().connection)) {\n throw new PhotoError(\"GLASSES_NOT_CONNECTED\", \"Glasses are not connected\", \"command\", \"ble\")\n }\n\n let lease: {requestId: string; durationMs: number; expiryTimer?: ReturnType<typeof setTimeout>} | undefined\n try {\n await this.stopWarmUpForApp(packageName)\n const requestId = mintBleRequestId()\n const requestedDuration =\n typeof opts.durationMs === \"number\" && Number.isFinite(opts.durationMs) && opts.durationMs > 0\n ? Math.round(opts.durationMs)\n : CAMERA_WARM_UP_DEFAULT_DURATION_MS\n const durationMs = Math.min(requestedDuration, CAMERA_WARM_UP_MAX_DURATION_MS)\n lease = {requestId, durationMs}\n this.activeWarmUps.set(packageName, lease)\n await BluetoothSdk.warmUpCamera({\n requestId,\n size: normalizePhotoSize(opts.size ?? \"medium\"),\n mode: opts.mode ?? \"photo\",\n exposureTimeNs: opts.exposureTimeNs ?? null,\n durationMs,\n ...(opts.zsl != null ? {zsl: opts.zsl} : {}),\n ...(opts.mfnr != null ? {mfnr: opts.mfnr} : {}),\n })\n if (this.activeWarmUps.get(packageName) === lease) {\n lease.expiryTimer = setTimeout(() => {\n if (this.activeWarmUps.get(packageName) === lease) {\n this.activeWarmUps.delete(packageName)\n }\n }, durationMs)\n }\n } catch (err) {\n if (lease && this.activeWarmUps.get(packageName) === lease) {\n if (lease.expiryTimer) clearTimeout(lease.expiryTimer)\n this.activeWarmUps.delete(packageName)\n }\n throw this.toPhotoError(err, \"WARM_UP_FAILED\", \"command\", \"ble\")\n }\n }\n\n /** Release a miniapp's warm-up even if its original warmUpCamera promise is still opening. */\n async stopWarmUpForApp(packageName: string): Promise<void> {\n const active = this.activeWarmUps.get(packageName)\n if (!active) return\n await BluetoothSdk.stopCameraWarmUp(active.requestId)\n if (this.activeWarmUps.get(packageName) === active) {\n this.activeWarmUps.delete(packageName)\n if (active.expiryTimer) clearTimeout(active.expiryTimer)\n }\n }\n\n /** True iff this requestId (short BLE id or cloud id) is one we're currently waiting on. */\n owns(requestId: string): boolean {\n const cloudId = this.resolveCloudRequestId(requestId)\n return this.activeRequests.has(cloudId)\n }\n\n /**\n * Called by MantleManager's gated photo_response listener when glasses\n * report an error (BATTERY_LOW, CAMERA_BUSY, etc.) for a phone-owned\n * requestId. Rejects the in-flight takePhoto Promise immediately.\n */\n handlePhotoError(requestId: string, errorCode: string, errorMessage: string): void {\n const cloudId = this.resolveCloudRequestId(requestId)\n const entry = this.activeRequests.get(cloudId)\n if (!entry) return\n this.bleIdToCloud.delete(requestId)\n // Abort the in-flight long-poll first so we don't double-resolve.\n entry.abort.abort()\n entry.reject(new PhotoError(errorCode || \"GLASSES_ERROR\", errorMessage || \"Glasses error\", \"capture\", \"ble\"))\n // cloud-v2 pending photo requests TTL out on their own; nothing to free.\n }\n\n private toPhotoError(err: unknown, fallbackCode: string, stage?: PhotoStage, transport?: PhotoTransport): PhotoError {\n if (err instanceof PhotoError) return err\n const code = (err as {code?: string})?.code\n const message = err instanceof Error ? err.message : String(err)\n return new PhotoError(code || fallbackCode, message, stage, transport)\n }\n}\n\n// Singleton — coordinator state is process-wide (mirrors phoneStreamCoordinator).\nexport const phonePhotoCoordinator = new PhonePhotoCoordinator()\n"]}
1
+ {"version":3,"file":"PhonePhotoCoordinator.js","sourceRoot":"","sources":["../../src/services/PhonePhotoCoordinator.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * PhonePhotoCoordinator — owns local-miniapp takePhoto() end-to-end.\n *\n * Architecture:\n * miniapp → SDK → LocalMiniappRuntime → photo runtime hook\n * → coordinator.takePhoto(packageName, opts)\n * ├── (precheck) glasses connected + hasCamera\n * ├── cloudClientService.startManagedPhoto → {requestId, uploadUrl, readUrl} (cloud-v2 runtime presign)\n * ├── BluetoothSdk.requestPhoto(requestId, size, uploadUrl, compress, sound)\n * └── race:\n * - cloudClientService.awaitManagedPhotoReady(requestId) resolves on photo.ready push\n * - BluetoothSdk.requestPhoto rejects if terminal photo_response is an error\n * - handlePhotoError(requestId, code, message) rejects if MantleManager observes\n * the same BLE photo_response error before the native promise crosses the bridge\n *\n * `activeRequests` lets MantleManager's gated `photo_response` listener\n * short-circuit our long-poll with a typed error (CAMERA_BUSY, BATTERY_LOW,\n * etc.) instead of waiting 30s for cloud's timeout.\n */\n\nimport BluetoothSdk from \"@mentra/bluetooth-sdk/internal\"\nimport type {PhotoSize, PhotoTransferMethod} from \"@mentra/bluetooth-sdk/internal\"\nimport {cloudClientService} from \"./CloudClientService\"\nimport {isGlassesConnected} from \"./GlassesReadiness\"\nimport {useGlassesStore} from \"../stores/glasses\"\n\ninterface PhotoResult {\n photoUrl: string\n mimeType: string\n size: number\n}\n\n/** Map legacy/cloud size names onto the native take_photo enum. */\nfunction normalizePhotoSize(value: unknown): PhotoSize {\n if (typeof value !== \"string\") return \"medium\"\n switch (value) {\n case \"small\":\n return \"low\"\n case \"large\":\n return \"high\"\n case \"full\":\n return \"max\"\n default:\n return ([\"low\", \"medium\", \"high\", \"max\"] as const).includes(value as PhotoSize) ? (value as PhotoSize) : \"medium\"\n }\n}\n\nexport interface PhotoOpts {\n /** Legacy cloud size names are normalized before the native take_photo command. */\n size?: \"low\" | \"medium\" | \"high\" | \"max\" | \"small\" | \"large\" | \"full\"\n mode?: \"photo\" | \"text\"\n /** Select direct-only, phone-relayed BLE, or the default Wi-Fi/BLE fallback policy. */\n transferMethod?: PhotoTransferMethod\n compress?: \"none\" | \"low\" | \"medium\" | \"high\"\n sound?: boolean\n saveToGallery?: boolean\n exposureTimeNs?: number\n iso?: number | null\n aeExposureDivisor?: number\n isoCap?: number\n noiseReduction?: boolean\n edgeEnhancement?: boolean\n /** ZSL preview buffering. */\n zsl?: boolean\n /** MFNR still capture. */\n mfnr?: boolean\n ispDigitalGain?: number\n ispAnalogGain?: string\n}\n\nexport interface PhotoTaken {\n photoUrl: string\n mimeType: string\n size: number\n requestId: string\n}\n\n/** Pipeline stage a photo request failed at — surfaced to the miniapp so a dev\n * sees exactly where it broke, not just a flattened message. */\nexport type PhotoStage = \"presign\" | \"command\" | \"capture\" | \"upload\" | \"push\"\n/** Transport in play at the point of failure. */\nexport type PhotoTransport = \"cloud-rest\" | \"ble\" | \"wifi\" | \"ws\"\n\nexport class PhotoError extends Error {\n constructor(\n public readonly code: string,\n message: string,\n public readonly stage?: PhotoStage,\n public readonly transport?: PhotoTransport,\n ) {\n super(message)\n this.name = \"PhotoError\"\n }\n}\n\nfunction parsePhotoTransferMethod(value: unknown): PhotoTransferMethod | undefined {\n if (value === undefined) return undefined\n if (value === \"auto\" || value === \"direct\" || value === \"ble\") return value\n throw new PhotoError(\n \"INVALID_ARGUMENT\",\n `Invalid transferMethod ${JSON.stringify(value)}. Expected \"auto\", \"direct\", or \"ble\".`,\n )\n}\n\ninterface ActiveRequest {\n packageName: string\n abort: AbortController\n resolve: (r: PhotoResult) => void\n reject: (err: Error) => void\n}\n\n/**\n * Last-resort ceiling for the complete capture + encode + transfer + upload\n * pipeline. This must exceed the managed-photo service's 30s ready-push\n * timeout so the cloud can return its more specific failure first.\n *\n * Text mode commonly spends several seconds detecting/cropping the region and\n * can take another 10s+ to transfer over BLE. A 15s ceiling incorrectly\n * rejected successful captures while their upload was still in progress.\n */\nexport const CAPTURE_PIPELINE_TIMEOUT_MS = 45_000\nexport const CAMERA_WARM_UP_DEFAULT_DURATION_MS = 15_000\nexport const CAMERA_WARM_UP_MAX_DURATION_MS = 60_000\n\nlet bleRequestCounter = 0\n\n/** Short 4-hex-char correlation id sent over BLE instead of the full cloud\n * requestId UUID (wire v2 keeps BLE JSON small). */\nfunction mintBleRequestId(): string {\n bleRequestCounter = (bleRequestCounter + 1) & 0xffff\n return bleRequestCounter.toString(16).padStart(4, \"0\")\n}\n\nfunction toNativeCompression(compress: PhotoOpts[\"compress\"]): \"none\" | \"medium\" | \"heavy\" {\n if (compress === \"high\") return \"heavy\"\n if (compress === \"low\" || compress === \"medium\") return \"medium\"\n return \"none\"\n}\n\nexport class PhonePhotoCoordinator {\n // Cloud requestId → in-flight slot. The gated photo_response listener\n // (DeviceEventRouter) resolves short BLE ids via bleIdToCloud before calling\n // owns() / handlePhotoError().\n private readonly activeRequests = new Map<string, ActiveRequest>()\n /** Short BLE correlation id (4-char hex) → full cloud requestId. */\n private readonly bleIdToCloud = new Map<string, string>()\n private readonly activeWarmUps = new Map<\n string,\n {requestId: string; durationMs: number; expiryTimer?: ReturnType<typeof setTimeout>}\n >()\n\n /** Report-safe camera ownership snapshot for incident diagnostics. */\n getDiagnosticSnapshot(): Record<string, unknown> {\n return {\n captureOwners: [...new Set([...this.activeRequests.values()].map((request) => request.packageName))].sort(),\n warmUpOwners: [...this.activeWarmUps.keys()].sort(),\n activeCaptureCount: this.activeRequests.size,\n }\n }\n\n async takePhoto(packageName: string, opts: PhotoOpts): Promise<PhotoTaken> {\n const transferMethod = parsePhotoTransferMethod(opts.transferMethod)\n\n // Pre-check: if glasses aren't even connected, the BLE photo command\n // would be sent into the void and we'd wait 30s for the cloud long-poll\n // to time out. Fail fast with a typed error.\n //\n // We DON'T pre-check `hasCamera` here — the canonical capability data\n // lives in `getModelCapabilities(deviceModel)` from @mentra/engine and\n // pulling that into this file would add a cross-package import. If a\n // cameraless device receives the BLE photo command, the glasses-side\n // handler will return a photo_response error within ~1s and the gated\n // photo_response listener in MantleManager will short-circuit our\n // long-poll. Slower but correct.\n if (!isGlassesConnected(useGlassesStore.getState().connection)) {\n throw new PhotoError(\"GLASSES_NOT_CONNECTED\", \"Glasses are not connected\", \"command\", \"ble\")\n }\n\n // Text-mode sensor resolution is owned by ASG constants; keep cloud metadata on a stable\n // high-capacity tier and let the glasses ignore the public size when mode=text.\n const captureSize = opts.size ?? \"medium\"\n\n // 1) Presign via the cloud-v2 managed-photo service. Local miniapps use\n // ONLY the cloud-v2 path: the runtime presigns upload+read URLs and the\n // phone (as the device controller) delivers the bytes; the legacy\n // backend_url mint is gone from this flow.\n let requestId: string\n let uploadUrl: string\n let readUrl: string\n const flowStarted = performance.now()\n try {\n const presignStarted = performance.now()\n const r = await cloudClientService.startManagedPhoto({\n size: opts.mode === \"text\" ? \"max\" : captureSize,\n })\n const presignMs = Math.round(performance.now() - presignStarted)\n if (typeof __DEV__ !== \"undefined\" && __DEV__) {\n console.debug(\n `[PhonePhotoCoordinator] presign ${presignMs}ms size=${opts.mode === \"text\" ? \"max\" : captureSize} mode=${\n opts.mode ?? \"photo\"\n }`,\n )\n }\n requestId = r.requestId\n uploadUrl = r.uploadUrl\n readUrl = r.readUrl\n } catch (err) {\n throw this.toPhotoError(err, \"PHOTO_REQUEST_FAILED\", \"presign\", \"cloud-rest\")\n }\n\n // 2) Build the outcome Promise FIRST so both resolve+reject handles\n // are wired into activeRequests BEFORE any code path can produce\n // an error. Without this, a fast BLE photo_response (BATTERY_LOW\n // etc.) racing with the Promise constructor could fire\n // handlePhotoError() against a no-op rejectFn and silently drop\n // the error.\n // When the managed-photo upload URL is loopback (the local storage provider\n // reached over `adb reverse`), the glasses cannot reach it over WiFi —\n // localhost on the glasses is the glasses. Force BLE transfer so the phone\n // (which CAN reach the reversed runtime) relays the bytes; a public r2/s3\n // presigned URL stays on \"auto\".\n const isLoopbackUpload = /^https?:\\/\\/(localhost|127\\.0\\.0\\.1|10\\.0\\.2\\.2)\\b/.test(uploadUrl)\n\n const abort = new AbortController()\n const bleRequestId = mintBleRequestId()\n this.bleIdToCloud.set(bleRequestId, requestId)\n\n const outcome = new Promise<PhotoResult>((resolve, reject) => {\n this.activeRequests.set(requestId, {packageName, abort, resolve, reject})\n })\n\n // Last-resort watchdog for a wedged pipeline. The managed-photo ready push\n // normally resolves or rejects first; this only prevents an indefinite hang\n // if both the native terminal response and cloud push disappear.\n const captureWatchdog = setTimeout(() => {\n const e = this.activeRequests.get(requestId)\n if (!e) return\n e.abort.abort()\n e.reject(\n new PhotoError(\n \"CAPTURE_TIMEOUT\",\n \"Photo capture did not complete. The take_photo command, media processing, transfer, or upload may have stalled.\",\n \"capture\",\n isLoopbackUpload ? \"ble\" : \"wifi\",\n ),\n )\n }, CAPTURE_PIPELINE_TIMEOUT_MS)\n // Clear the watchdog on BOTH arms. Not `.finally()`: that would mint a new\n // promise that re-rejects unobserved whenever the photo fails (the caller\n // only awaits `outcome` itself), i.e. an unhandled rejection per failure.\n const clearWatchdog = () => clearTimeout(captureWatchdog)\n void outcome.then(clearWatchdog, clearWatchdog)\n\n // 3) Drive glasses over BLE. requestPhoto now resolves at terminal\n // photo_response success, so run it beside the cloud poll instead of\n // awaiting it before polling. Native defaults transferMethod to \"auto\"\n // (Wi-Fi direct with BLE fallback) unless the miniapp forces BLE.\n try {\n void BluetoothSdk.requestPhoto({\n requestId: bleRequestId,\n appId: packageName,\n size: normalizePhotoSize(captureSize),\n mode: opts.mode ?? \"photo\",\n webhookUrl: uploadUrl,\n authToken: null,\n ...(isLoopbackUpload ? {transferMethod: \"ble\" as const} : transferMethod ? {transferMethod} : {}),\n compress: toNativeCompression(opts.compress),\n save: opts.saveToGallery ?? false,\n sound: opts.sound ?? true,\n exposureTimeNs: opts.exposureTimeNs ?? null,\n iso: opts.iso,\n aeExposureDivisor: opts.aeExposureDivisor,\n isoCap: opts.isoCap,\n noiseReduction: opts.noiseReduction,\n edgeEnhancement: opts.edgeEnhancement,\n ...(opts.zsl != null ? {zsl: opts.zsl} : {}),\n ...(opts.mfnr != null ? {mfnr: opts.mfnr} : {}),\n ispDigitalGain: opts.ispDigitalGain,\n ispAnalogGain: opts.ispAnalogGain,\n }).catch((err) => {\n const e = this.activeRequests.get(requestId)\n if (!e) return\n e.abort.abort()\n e.reject(this.toPhotoError(err, \"BLE_SEND_FAILED\", \"command\", \"ble\"))\n })\n } catch (err) {\n this.activeRequests.delete(requestId)\n this.bleIdToCloud.delete(bleRequestId)\n throw this.toPhotoError(err, \"BLE_SEND_FAILED\", \"command\", \"ble\")\n }\n\n // 4) Await the runtime's photo.ready push (replaces the legacy long-poll).\n // handlePhotoError races against it and uses the same entry to reject\n // first.\n cloudClientService\n .awaitManagedPhotoReady(requestId)\n .then((res) => {\n const e = this.activeRequests.get(requestId)\n if (!e) return // already settled by handlePhotoError\n e.resolve({photoUrl: res.readUrl ?? readUrl, mimeType: \"image/jpeg\", size: -1})\n })\n .catch((err) => {\n const e = this.activeRequests.get(requestId)\n if (e) e.reject(this.toPhotoError(err, \"POLL_FAILED\", \"push\", \"ws\"))\n })\n\n try {\n const result = await outcome\n if (typeof __DEV__ !== \"undefined\" && __DEV__) {\n console.debug(\n `[PhonePhotoCoordinator] takePhoto complete ${Math.round(\n performance.now() - flowStarted,\n )}ms requestId=${requestId}`,\n )\n }\n return {\n photoUrl: result.photoUrl,\n mimeType: result.mimeType,\n size: result.size,\n requestId,\n }\n } finally {\n this.activeRequests.delete(requestId)\n this.bleIdToCloud.delete(bleRequestId)\n }\n }\n\n /** Map a short BLE requestId from photo_response back to the cloud requestId. */\n resolveCloudRequestId(bleOrCloudId: string): string {\n return this.bleIdToCloud.get(bleOrCloudId) ?? bleOrCloudId\n }\n\n /**\n * Pre-warm the glasses camera so the next takePhoto() is near-instant.\n *\n * Pure BLE — NO cloud presign, NO upload, NO long-poll. The phone mints and owns\n * the requestId, sends the warm-up command, and resolves when the camera reports\n * ready (the native promise resolves on the ready status event).\n */\n async warmUpCamera(\n packageName: string,\n opts: {\n size?: \"low\" | \"medium\" | \"high\" | \"max\"\n mode?: \"photo\" | \"text\"\n exposureTimeNs?: number\n durationMs?: number\n zsl?: boolean\n mfnr?: boolean\n },\n ): Promise<void> {\n // Pre-check: if glasses aren't connected, the BLE warm-up command would be\n // sent into the void. Fail fast with a typed error.\n if (!isGlassesConnected(useGlassesStore.getState().connection)) {\n throw new PhotoError(\"GLASSES_NOT_CONNECTED\", \"Glasses are not connected\", \"command\", \"ble\")\n }\n\n let lease: {requestId: string; durationMs: number; expiryTimer?: ReturnType<typeof setTimeout>} | undefined\n try {\n await this.stopWarmUpForApp(packageName)\n const requestId = mintBleRequestId()\n const requestedDuration =\n typeof opts.durationMs === \"number\" && Number.isFinite(opts.durationMs) && opts.durationMs > 0\n ? Math.round(opts.durationMs)\n : CAMERA_WARM_UP_DEFAULT_DURATION_MS\n const durationMs = Math.min(requestedDuration, CAMERA_WARM_UP_MAX_DURATION_MS)\n lease = {requestId, durationMs}\n this.activeWarmUps.set(packageName, lease)\n await BluetoothSdk.warmUpCamera({\n requestId,\n size: normalizePhotoSize(opts.size ?? \"medium\"),\n mode: opts.mode ?? \"photo\",\n exposureTimeNs: opts.exposureTimeNs ?? null,\n durationMs,\n ...(opts.zsl != null ? {zsl: opts.zsl} : {}),\n ...(opts.mfnr != null ? {mfnr: opts.mfnr} : {}),\n })\n if (this.activeWarmUps.get(packageName) === lease) {\n lease.expiryTimer = setTimeout(() => {\n if (this.activeWarmUps.get(packageName) === lease) {\n this.activeWarmUps.delete(packageName)\n }\n }, durationMs)\n }\n } catch (err) {\n if (lease && this.activeWarmUps.get(packageName) === lease) {\n if (lease.expiryTimer) clearTimeout(lease.expiryTimer)\n this.activeWarmUps.delete(packageName)\n }\n throw this.toPhotoError(err, \"WARM_UP_FAILED\", \"command\", \"ble\")\n }\n }\n\n /** Release a miniapp's warm-up even if its original warmUpCamera promise is still opening. */\n async stopWarmUpForApp(packageName: string): Promise<void> {\n const active = this.activeWarmUps.get(packageName)\n if (!active) return\n await BluetoothSdk.stopCameraWarmUp(active.requestId)\n if (this.activeWarmUps.get(packageName) === active) {\n this.activeWarmUps.delete(packageName)\n if (active.expiryTimer) clearTimeout(active.expiryTimer)\n }\n }\n\n /** True iff this requestId (short BLE id or cloud id) is one we're currently waiting on. */\n owns(requestId: string): boolean {\n const cloudId = this.resolveCloudRequestId(requestId)\n return this.activeRequests.has(cloudId)\n }\n\n /**\n * Called by MantleManager's gated photo_response listener when glasses\n * report an error (BATTERY_LOW, CAMERA_BUSY, etc.) for a phone-owned\n * requestId. Rejects the in-flight takePhoto Promise immediately.\n */\n handlePhotoError(requestId: string, errorCode: string, errorMessage: string): void {\n const cloudId = this.resolveCloudRequestId(requestId)\n const entry = this.activeRequests.get(cloudId)\n if (!entry) return\n this.bleIdToCloud.delete(requestId)\n // Abort the in-flight long-poll first so we don't double-resolve.\n entry.abort.abort()\n entry.reject(new PhotoError(errorCode || \"GLASSES_ERROR\", errorMessage || \"Glasses error\", \"capture\", \"ble\"))\n // cloud-v2 pending photo requests TTL out on their own; nothing to free.\n }\n\n private toPhotoError(err: unknown, fallbackCode: string, stage?: PhotoStage, transport?: PhotoTransport): PhotoError {\n if (err instanceof PhotoError) return err\n const code = (err as {code?: string})?.code\n const message = err instanceof Error ? err.message : String(err)\n return new PhotoError(code || fallbackCode, message, stage, transport)\n }\n}\n\n// Singleton — coordinator state is process-wide (mirrors phoneStreamCoordinator).\nexport const phonePhotoCoordinator = new PhonePhotoCoordinator()\n"]}
@@ -3,9 +3,9 @@
3
3
  * `setActive(true)` starts the timer; each tick sends a keep-alive with a
4
4
  * fresh ackId; `maxMissedAcks` consecutive timeouts fire `onTimeout`.
5
5
  *
6
- * Mirror of `cloud/packages/cloud/src/services/streaming/StreamLifecycleController.ts`
7
- * — kept byte-equivalent except for the `LifecycleLogger` interface (pino
8
- * isn't a phone dep). Behavior changes must land in both files.
6
+ * Phone-owned successor to the retired cloud stream lifecycle controller.
7
+ * It uses the local `LifecycleLogger` interface because pino is not a phone
8
+ * dependency.
9
9
  */
10
10
  export interface LifecycleLogger {
11
11
  child(bindings: Record<string, unknown>): LifecycleLogger;
@@ -3,9 +3,9 @@
3
3
  * `setActive(true)` starts the timer; each tick sends a keep-alive with a
4
4
  * fresh ackId; `maxMissedAcks` consecutive timeouts fire `onTimeout`.
5
5
  *
6
- * Mirror of `cloud/packages/cloud/src/services/streaming/StreamLifecycleController.ts`
7
- * — kept byte-equivalent except for the `LifecycleLogger` interface (pino
8
- * isn't a phone dep). Behavior changes must land in both files.
6
+ * Phone-owned successor to the retired cloud stream lifecycle controller.
7
+ * It uses the local `LifecycleLogger` interface because pino is not a phone
8
+ * dependency.
9
9
  */
10
10
  /**
11
11
  * Drives RTMP stream liveliness checks. Activate to start the heartbeat;
@@ -1 +1 @@
1
- {"version":3,"file":"StreamLifecycleController.js","sourceRoot":"","sources":["../../src/services/StreamLifecycleController.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Drives RTMP stream liveliness checks via a keep-alive heartbeat:\n * `setActive(true)` starts the timer; each tick sends a keep-alive with a\n * fresh ackId; `maxMissedAcks` consecutive timeouts fire `onTimeout`.\n *\n * Mirror of `cloud/packages/cloud/src/services/streaming/StreamLifecycleController.ts`\n * — kept byte-equivalent except for the `LifecycleLogger` interface (pino\n * isn't a phone dep). Behavior changes must land in both files.\n */\n\nexport interface LifecycleLogger {\n child(bindings: Record<string, unknown>): LifecycleLogger\n debug(...args: unknown[]): void\n warn(...args: unknown[]): void\n error(...args: unknown[]): void\n}\n\ninterface StreamLifecycleCallbacks {\n sendKeepAlive: (ackId: string) => Promise<void> | void\n onTimeout: () => Promise<void> | void\n onKeepAliveSent?: (ackId: string) => void\n onKeepAliveAcked?: (ackId: string, ageMs: number) => void\n onKeepAliveMissed?: (ackId: string, ageMs: number, missedCount: number) => void\n}\n\nexport interface StreamLifecycleOptions {\n logger: LifecycleLogger\n streamId: string\n keepAliveIntervalMs: number\n ackTimeoutMs: number\n maxMissedAcks: number\n shouldSendKeepAlive?: () => boolean\n now?: () => number\n}\n\ninterface PendingAckInfo {\n sentAt: number\n timeout: ReturnType<typeof setTimeout>\n}\n\n/**\n * Drives RTMP stream liveliness checks. Activate to start the heartbeat;\n * each tick sends a keep-alive with a fresh ackId and arms a timeout. When\n * `maxMissedAcks` consecutive timeouts fire, `onTimeout` is invoked and the\n * caller is expected to tear the stream down.\n */\nexport class StreamLifecycleController {\n private keepAliveTimer?: ReturnType<typeof setInterval>\n private pendingAcks: Map<string, PendingAckInfo> = new Map()\n private missedAcks = 0\n private lastActivityMs: number\n private active = false\n private disposed = false\n\n private readonly logger: LifecycleLogger\n private readonly streamId: string\n private readonly keepAliveIntervalMs: number\n private readonly ackTimeoutMs: number\n private readonly maxMissedAcks: number\n private readonly shouldSendKeepAlive?: () => boolean\n private readonly now: () => number\n\n constructor(\n options: StreamLifecycleOptions,\n private readonly callbacks: StreamLifecycleCallbacks,\n ) {\n this.logger = options.logger.child({component: \"StreamLifecycle\"})\n this.streamId = options.streamId\n this.keepAliveIntervalMs = options.keepAliveIntervalMs\n this.ackTimeoutMs = options.ackTimeoutMs\n this.maxMissedAcks = options.maxMissedAcks\n this.shouldSendKeepAlive = options.shouldSendKeepAlive\n this.now = options.now ?? (() => Date.now())\n this.lastActivityMs = this.now()\n }\n\n setActive(active: boolean): void {\n if (this.disposed || this.active === active) return\n\n this.logger.debug({streamId: this.streamId, active}, \"Updating lifecycle active state\")\n\n this.active = active\n if (active) {\n this.startTimer()\n } else {\n this.stopTimer()\n this.clearPendingAcks()\n this.missedAcks = 0\n }\n }\n\n recordActivity(): void {\n this.lastActivityMs = this.now()\n this.missedAcks = 0\n }\n\n handleAck(ackId: string): void {\n if (this.disposed) return\n\n const ackInfo = this.pendingAcks.get(ackId)\n if (!ackInfo) {\n this.logger.warn({streamId: this.streamId, ackId}, \"Received unknown keep-alive ACK\")\n return\n }\n\n clearTimeout(ackInfo.timeout)\n this.pendingAcks.delete(ackId)\n this.recordActivity()\n\n this.callbacks.onKeepAliveAcked?.(ackId, this.now() - ackInfo.sentAt)\n }\n\n dispose(): void {\n if (this.disposed) return\n\n this.disposed = true\n this.stopTimer()\n this.clearPendingAcks()\n this.logger.debug({streamId: this.streamId}, \"Lifecycle disposed\")\n }\n\n getLastActivityMs(): number {\n return this.lastActivityMs\n }\n\n private startTimer(): void {\n if (this.keepAliveTimer) return\n this.keepAliveTimer = setInterval(() => {\n void this.tick()\n }, this.keepAliveIntervalMs)\n this.logger.debug({streamId: this.streamId}, \"Keep-alive timer started\")\n }\n\n private stopTimer(): void {\n if (this.keepAliveTimer) {\n clearInterval(this.keepAliveTimer)\n this.keepAliveTimer = undefined\n this.logger.debug({streamId: this.streamId}, \"Keep-alive timer stopped\")\n }\n }\n\n private async tick(): Promise<void> {\n if (this.disposed || !this.active) return\n\n if (this.shouldSendKeepAlive && !this.shouldSendKeepAlive()) {\n this.logger.warn(\n {streamId: this.streamId},\n \"Skipping keep-alive send because transport is unavailable\",\n )\n return\n }\n\n const ackId = this.createAckId()\n const sentAt = this.now()\n\n const timeout = setTimeout(() => {\n this.onAckTimeout(ackId, sentAt)\n }, this.ackTimeoutMs)\n\n this.pendingAcks.set(ackId, {sentAt, timeout})\n this.callbacks.onKeepAliveSent?.(ackId)\n\n try {\n await this.callbacks.sendKeepAlive(ackId)\n } catch (error) {\n this.logger.error({streamId: this.streamId, ackId, error}, \"Error sending keep-alive\")\n }\n }\n\n private onAckTimeout(ackId: string, sentAt: number): void {\n if (this.disposed) return\n\n // Race guard: handleAck may have processed the ACK between the timeout\n // being scheduled and this callback running. In that case the ackId is\n // no longer pending — counting it as missed would falsely escalate.\n if (!this.pendingAcks.has(ackId)) return\n\n this.pendingAcks.delete(ackId)\n this.missedAcks += 1\n const ageMs = this.now() - sentAt\n\n this.logger.warn(\n {streamId: this.streamId, ackId, missedAcks: this.missedAcks, ageMs},\n \"Keep-alive ACK timeout\",\n )\n\n this.callbacks.onKeepAliveMissed?.(ackId, ageMs, this.missedAcks)\n\n if (this.missedAcks >= this.maxMissedAcks) {\n this.logger.error(\n {\n streamId: this.streamId,\n missedAcks: this.missedAcks,\n maxMissedAcks: this.maxMissedAcks,\n },\n \"Maximum missed ACKs reached; triggering timeout\",\n )\n\n void this.callbacks.onTimeout()\n }\n }\n\n private clearPendingAcks(): void {\n for (const {timeout} of this.pendingAcks.values()) {\n clearTimeout(timeout)\n }\n this.pendingAcks.clear()\n }\n\n private createAckId(): string {\n return `a${this.now().toString(36).slice(-5)}`\n }\n}\n"]}
1
+ {"version":3,"file":"StreamLifecycleController.js","sourceRoot":"","sources":["../../src/services/StreamLifecycleController.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAgCH;;;;;GAKG;AACH,MAAM,OAAO,yBAAyB;IAkBjB;IAjBX,cAAc,CAAiC;IAC/C,WAAW,GAAgC,IAAI,GAAG,EAAE,CAAA;IACpD,UAAU,GAAG,CAAC,CAAA;IACd,cAAc,CAAQ;IACtB,MAAM,GAAG,KAAK,CAAA;IACd,QAAQ,GAAG,KAAK,CAAA;IAEP,MAAM,CAAiB;IACvB,QAAQ,CAAQ;IAChB,mBAAmB,CAAQ;IAC3B,YAAY,CAAQ;IACpB,aAAa,CAAQ;IACrB,mBAAmB,CAAgB;IACnC,GAAG,CAAc;IAElC,YACE,OAA+B,EACd,SAAmC;QAAnC,cAAS,GAAT,SAAS,CAA0B;QAEpD,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAAC,SAAS,EAAE,iBAAiB,EAAC,CAAC,CAAA;QAClE,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAA;QAChC,IAAI,CAAC,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,CAAA;QACtD,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,YAAY,CAAA;QACxC,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC,aAAa,CAAA;QAC1C,IAAI,CAAC,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,CAAA;QACtD,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAA;QAC5C,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;IAClC,CAAC;IAED,SAAS,CAAC,MAAe;QACvB,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,MAAM,KAAK,MAAM;YAAE,OAAM;QAEnD,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAC,EAAE,iCAAiC,CAAC,CAAA;QAEvF,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;QACpB,IAAI,MAAM,EAAE,CAAC;YACX,IAAI,CAAC,UAAU,EAAE,CAAA;QACnB,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,SAAS,EAAE,CAAA;YAChB,IAAI,CAAC,gBAAgB,EAAE,CAAA;YACvB,IAAI,CAAC,UAAU,GAAG,CAAC,CAAA;QACrB,CAAC;IACH,CAAC;IAED,cAAc;QACZ,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QAChC,IAAI,CAAC,UAAU,GAAG,CAAC,CAAA;IACrB,CAAC;IAED,SAAS,CAAC,KAAa;QACrB,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAM;QAEzB,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QAC3C,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAC,EAAE,iCAAiC,CAAC,CAAA;YACrF,OAAM;QACR,CAAC;QAED,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;QAC7B,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QAC9B,IAAI,CAAC,cAAc,EAAE,CAAA;QAErB,IAAI,CAAC,SAAS,CAAC,gBAAgB,EAAE,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IACvE,CAAC;IAED,OAAO;QACL,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAM;QAEzB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAA;QACpB,IAAI,CAAC,SAAS,EAAE,CAAA;QAChB,IAAI,CAAC,gBAAgB,EAAE,CAAA;QACvB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAC,EAAE,oBAAoB,CAAC,CAAA;IACpE,CAAC;IAED,iBAAiB;QACf,OAAO,IAAI,CAAC,cAAc,CAAA;IAC5B,CAAC;IAEO,UAAU;QAChB,IAAI,IAAI,CAAC,cAAc;YAAE,OAAM;QAC/B,IAAI,CAAC,cAAc,GAAG,WAAW,CAAC,GAAG,EAAE;YACrC,KAAK,IAAI,CAAC,IAAI,EAAE,CAAA;QAClB,CAAC,EAAE,IAAI,CAAC,mBAAmB,CAAC,CAAA;QAC5B,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAC,EAAE,0BAA0B,CAAC,CAAA;IAC1E,CAAC;IAEO,SAAS;QACf,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,aAAa,CAAC,IAAI,CAAC,cAAc,CAAC,CAAA;YAClC,IAAI,CAAC,cAAc,GAAG,SAAS,CAAA;YAC/B,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAC,EAAE,0BAA0B,CAAC,CAAA;QAC1E,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,IAAI;QAChB,IAAI,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,OAAM;QAEzC,IAAI,IAAI,CAAC,mBAAmB,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE,EAAE,CAAC;YAC5D,IAAI,CAAC,MAAM,CAAC,IAAI,CACd,EAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAC,EACzB,2DAA2D,CAC5D,CAAA;YACD,OAAM;QACR,CAAC;QAED,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,CAAA;QAChC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QAEzB,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE;YAC9B,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,MAAM,CAAC,CAAA;QAClC,CAAC,EAAE,IAAI,CAAC,YAAY,CAAC,CAAA;QAErB,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,EAAE,EAAC,MAAM,EAAE,OAAO,EAAC,CAAC,CAAA;QAC9C,IAAI,CAAC,SAAS,CAAC,eAAe,EAAE,CAAC,KAAK,CAAC,CAAA;QAEvC,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,KAAK,CAAC,CAAA;QAC3C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAC,EAAE,0BAA0B,CAAC,CAAA;QACxF,CAAC;IACH,CAAC;IAEO,YAAY,CAAC,KAAa,EAAE,MAAc;QAChD,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAM;QAEzB,uEAAuE;QACvE,uEAAuE;QACvE,oEAAoE;QACpE,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC;YAAE,OAAM;QAExC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QAC9B,IAAI,CAAC,UAAU,IAAI,CAAC,CAAA;QACpB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAA;QAEjC,IAAI,CAAC,MAAM,CAAC,IAAI,CACd,EAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,KAAK,EAAC,EACpE,wBAAwB,CACzB,CAAA;QAED,IAAI,CAAC,SAAS,CAAC,iBAAiB,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,UAAU,CAAC,CAAA;QAEjE,IAAI,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YAC1C,IAAI,CAAC,MAAM,CAAC,KAAK,CACf;gBACE,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,UAAU,EAAE,IAAI,CAAC,UAAU;gBAC3B,aAAa,EAAE,IAAI,CAAC,aAAa;aAClC,EACD,iDAAiD,CAClD,CAAA;YAED,KAAK,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,CAAA;QACjC,CAAC;IACH,CAAC;IAEO,gBAAgB;QACtB,KAAK,MAAM,EAAC,OAAO,EAAC,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,EAAE,CAAC;YAClD,YAAY,CAAC,OAAO,CAAC,CAAA;QACvB,CAAC;QACD,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,CAAA;IAC1B,CAAC;IAEO,WAAW;QACjB,OAAO,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IAChD,CAAC;CACF","sourcesContent":["/**\n * Drives RTMP stream liveliness checks via a keep-alive heartbeat:\n * `setActive(true)` starts the timer; each tick sends a keep-alive with a\n * fresh ackId; `maxMissedAcks` consecutive timeouts fire `onTimeout`.\n *\n * Phone-owned successor to the retired cloud stream lifecycle controller.\n * It uses the local `LifecycleLogger` interface because pino is not a phone\n * dependency.\n */\n\nexport interface LifecycleLogger {\n child(bindings: Record<string, unknown>): LifecycleLogger\n debug(...args: unknown[]): void\n warn(...args: unknown[]): void\n error(...args: unknown[]): void\n}\n\ninterface StreamLifecycleCallbacks {\n sendKeepAlive: (ackId: string) => Promise<void> | void\n onTimeout: () => Promise<void> | void\n onKeepAliveSent?: (ackId: string) => void\n onKeepAliveAcked?: (ackId: string, ageMs: number) => void\n onKeepAliveMissed?: (ackId: string, ageMs: number, missedCount: number) => void\n}\n\nexport interface StreamLifecycleOptions {\n logger: LifecycleLogger\n streamId: string\n keepAliveIntervalMs: number\n ackTimeoutMs: number\n maxMissedAcks: number\n shouldSendKeepAlive?: () => boolean\n now?: () => number\n}\n\ninterface PendingAckInfo {\n sentAt: number\n timeout: ReturnType<typeof setTimeout>\n}\n\n/**\n * Drives RTMP stream liveliness checks. Activate to start the heartbeat;\n * each tick sends a keep-alive with a fresh ackId and arms a timeout. When\n * `maxMissedAcks` consecutive timeouts fire, `onTimeout` is invoked and the\n * caller is expected to tear the stream down.\n */\nexport class StreamLifecycleController {\n private keepAliveTimer?: ReturnType<typeof setInterval>\n private pendingAcks: Map<string, PendingAckInfo> = new Map()\n private missedAcks = 0\n private lastActivityMs: number\n private active = false\n private disposed = false\n\n private readonly logger: LifecycleLogger\n private readonly streamId: string\n private readonly keepAliveIntervalMs: number\n private readonly ackTimeoutMs: number\n private readonly maxMissedAcks: number\n private readonly shouldSendKeepAlive?: () => boolean\n private readonly now: () => number\n\n constructor(\n options: StreamLifecycleOptions,\n private readonly callbacks: StreamLifecycleCallbacks,\n ) {\n this.logger = options.logger.child({component: \"StreamLifecycle\"})\n this.streamId = options.streamId\n this.keepAliveIntervalMs = options.keepAliveIntervalMs\n this.ackTimeoutMs = options.ackTimeoutMs\n this.maxMissedAcks = options.maxMissedAcks\n this.shouldSendKeepAlive = options.shouldSendKeepAlive\n this.now = options.now ?? (() => Date.now())\n this.lastActivityMs = this.now()\n }\n\n setActive(active: boolean): void {\n if (this.disposed || this.active === active) return\n\n this.logger.debug({streamId: this.streamId, active}, \"Updating lifecycle active state\")\n\n this.active = active\n if (active) {\n this.startTimer()\n } else {\n this.stopTimer()\n this.clearPendingAcks()\n this.missedAcks = 0\n }\n }\n\n recordActivity(): void {\n this.lastActivityMs = this.now()\n this.missedAcks = 0\n }\n\n handleAck(ackId: string): void {\n if (this.disposed) return\n\n const ackInfo = this.pendingAcks.get(ackId)\n if (!ackInfo) {\n this.logger.warn({streamId: this.streamId, ackId}, \"Received unknown keep-alive ACK\")\n return\n }\n\n clearTimeout(ackInfo.timeout)\n this.pendingAcks.delete(ackId)\n this.recordActivity()\n\n this.callbacks.onKeepAliveAcked?.(ackId, this.now() - ackInfo.sentAt)\n }\n\n dispose(): void {\n if (this.disposed) return\n\n this.disposed = true\n this.stopTimer()\n this.clearPendingAcks()\n this.logger.debug({streamId: this.streamId}, \"Lifecycle disposed\")\n }\n\n getLastActivityMs(): number {\n return this.lastActivityMs\n }\n\n private startTimer(): void {\n if (this.keepAliveTimer) return\n this.keepAliveTimer = setInterval(() => {\n void this.tick()\n }, this.keepAliveIntervalMs)\n this.logger.debug({streamId: this.streamId}, \"Keep-alive timer started\")\n }\n\n private stopTimer(): void {\n if (this.keepAliveTimer) {\n clearInterval(this.keepAliveTimer)\n this.keepAliveTimer = undefined\n this.logger.debug({streamId: this.streamId}, \"Keep-alive timer stopped\")\n }\n }\n\n private async tick(): Promise<void> {\n if (this.disposed || !this.active) return\n\n if (this.shouldSendKeepAlive && !this.shouldSendKeepAlive()) {\n this.logger.warn(\n {streamId: this.streamId},\n \"Skipping keep-alive send because transport is unavailable\",\n )\n return\n }\n\n const ackId = this.createAckId()\n const sentAt = this.now()\n\n const timeout = setTimeout(() => {\n this.onAckTimeout(ackId, sentAt)\n }, this.ackTimeoutMs)\n\n this.pendingAcks.set(ackId, {sentAt, timeout})\n this.callbacks.onKeepAliveSent?.(ackId)\n\n try {\n await this.callbacks.sendKeepAlive(ackId)\n } catch (error) {\n this.logger.error({streamId: this.streamId, ackId, error}, \"Error sending keep-alive\")\n }\n }\n\n private onAckTimeout(ackId: string, sentAt: number): void {\n if (this.disposed) return\n\n // Race guard: handleAck may have processed the ACK between the timeout\n // being scheduled and this callback running. In that case the ackId is\n // no longer pending — counting it as missed would falsely escalate.\n if (!this.pendingAcks.has(ackId)) return\n\n this.pendingAcks.delete(ackId)\n this.missedAcks += 1\n const ageMs = this.now() - sentAt\n\n this.logger.warn(\n {streamId: this.streamId, ackId, missedAcks: this.missedAcks, ageMs},\n \"Keep-alive ACK timeout\",\n )\n\n this.callbacks.onKeepAliveMissed?.(ackId, ageMs, this.missedAcks)\n\n if (this.missedAcks >= this.maxMissedAcks) {\n this.logger.error(\n {\n streamId: this.streamId,\n missedAcks: this.missedAcks,\n maxMissedAcks: this.maxMissedAcks,\n },\n \"Maximum missed ACKs reached; triggering timeout\",\n )\n\n void this.callbacks.onTimeout()\n }\n }\n\n private clearPendingAcks(): void {\n for (const {timeout} of this.pendingAcks.values()) {\n clearTimeout(timeout)\n }\n this.pendingAcks.clear()\n }\n\n private createAckId(): string {\n return `a${this.now().toString(36).slice(-5)}`\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"localNetworkTransport.d.ts","sourceRoot":"","sources":["../../../src/services/asg/localNetworkTransport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,6BAA6B,CAAA;AAMnD,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAA;AAC9D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,IAAI,CAAC,YAAY,CAAC,CAAA;AAO1D,wBAAgB,2BAA2B,CAAC,EAAE,EAAE,MAAM,EAAE,eAAe,EAAE,OAAO,GAAG,OAAO,CAEzF;AAuBD,eAAO,MAAM,qBAAqB;oCACF,OAAO;oCACP,OAAO;kBAEjB,MAAM,YAAY,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;kBAYtD,OAAO,CAAC,IAAI,CAAC;eAQhB,MAAM,GAAG,GAAG,GAAG,OAAO,SAAS,WAAW,uBAAuB,OAAO,CAAC,QAAQ,CAAC;0BA8B7E,eAAe,GAAG,cAAc;wBA6DlC,MAAM,GAAG,IAAI;CASlC,CAAA"}
1
+ {"version":3,"file":"localNetworkTransport.d.ts","sourceRoot":"","sources":["../../../src/services/asg/localNetworkTransport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,6BAA6B,CAAA;AAMnD,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAA;AAC9D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,IAAI,CAAC,YAAY,CAAC,CAAA;AAO1D,wBAAgB,2BAA2B,CAAC,EAAE,EAAE,MAAM,EAAE,eAAe,EAAE,OAAO,GAAG,OAAO,CAEzF;AAuBD,eAAO,MAAM,qBAAqB;oCACF,OAAO;oCACP,OAAO;kBAEjB,MAAM,YAAY,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;kBAYtD,OAAO,CAAC,IAAI,CAAC;eAQhB,MAAM,GAAG,GAAG,GAAG,OAAO,SAAS,WAAW,uBAAuB,OAAO,CAAC,QAAQ,CAAC;0BA8B7E,eAAe,GAAG,cAAc;wBA8DlC,MAAM,GAAG,IAAI;CASlC,CAAA"}