@mentra/engine 0.1.0-dev.0 → 0.1.0-dev.1

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 (441) hide show
  1. package/README.md +42 -1
  2. package/build/engine.d.ts +11 -3
  3. package/build/engine.d.ts.map +1 -1
  4. package/build/engine.js +7 -2
  5. package/build/engine.js.map +1 -1
  6. package/build/facades/dev.d.ts +3 -2
  7. package/build/facades/dev.d.ts.map +1 -1
  8. package/build/facades/dev.js +18 -12
  9. package/build/facades/dev.js.map +1 -1
  10. package/build/facades/gallery.js +1 -1
  11. package/build/facades/gallery.js.map +1 -1
  12. package/build/facades/glasses.d.ts +1 -0
  13. package/build/facades/glasses.d.ts.map +1 -1
  14. package/build/facades/glasses.js +19 -4
  15. package/build/facades/glasses.js.map +1 -1
  16. package/build/facades/glassesSettings.d.ts.map +1 -1
  17. package/build/facades/glassesSettings.js +1 -0
  18. package/build/facades/glassesSettings.js.map +1 -1
  19. package/build/facades/miniapps.d.ts +4 -0
  20. package/build/facades/miniapps.d.ts.map +1 -1
  21. package/build/facades/miniapps.js +5 -0
  22. package/build/facades/miniapps.js.map +1 -1
  23. package/build/facades/ota.d.ts +11 -2
  24. package/build/facades/ota.d.ts.map +1 -1
  25. package/build/facades/ota.js +19 -4
  26. package/build/facades/ota.js.map +1 -1
  27. package/build/facades/pairing.d.ts +4 -0
  28. package/build/facades/pairing.d.ts.map +1 -1
  29. package/build/facades/pairing.js +10 -8
  30. package/build/facades/pairing.js.map +1 -1
  31. package/build/facades/permissions.d.ts +10 -0
  32. package/build/facades/permissions.d.ts.map +1 -1
  33. package/build/facades/permissions.js +10 -0
  34. package/build/facades/permissions.js.map +1 -1
  35. package/build/facades/phoneNotifications.d.ts +0 -4
  36. package/build/facades/phoneNotifications.d.ts.map +1 -1
  37. package/build/facades/phoneNotifications.js +4 -14
  38. package/build/facades/phoneNotifications.js.map +1 -1
  39. package/build/index.d.ts +6 -3
  40. package/build/index.d.ts.map +1 -1
  41. package/build/index.js +4 -2
  42. package/build/index.js.map +1 -1
  43. package/build/internal.d.ts +3 -2
  44. package/build/internal.d.ts.map +1 -1
  45. package/build/internal.js +2 -1
  46. package/build/internal.js.map +1 -1
  47. package/build/react/MentraLiveOtaFlow.d.ts +38 -0
  48. package/build/react/MentraLiveOtaFlow.d.ts.map +1 -0
  49. package/build/react/MentraLiveOtaFlow.js +528 -0
  50. package/build/react/MentraLiveOtaFlow.js.map +1 -0
  51. package/build/react/index.d.ts +2 -0
  52. package/build/react/index.d.ts.map +1 -0
  53. package/build/react/index.js +2 -0
  54. package/build/react/index.js.map +1 -0
  55. package/build/react/useEngineSnapshot.d.ts +2 -0
  56. package/build/react/useEngineSnapshot.d.ts.map +1 -0
  57. package/build/react/useEngineSnapshot.js +14 -0
  58. package/build/react/useEngineSnapshot.js.map +1 -0
  59. package/build/runtime/bootstrap.d.ts +24 -1
  60. package/build/runtime/bootstrap.d.ts.map +1 -1
  61. package/build/runtime/bootstrap.js +17 -0
  62. package/build/runtime/bootstrap.js.map +1 -1
  63. package/build/runtime/scanQrSeam.d.ts +7 -0
  64. package/build/runtime/scanQrSeam.d.ts.map +1 -0
  65. package/build/runtime/scanQrSeam.js +16 -0
  66. package/build/runtime/scanQrSeam.js.map +1 -0
  67. package/build/services/AppRegistry.d.ts +33 -57
  68. package/build/services/AppRegistry.d.ts.map +1 -1
  69. package/build/services/AppRegistry.js +193 -144
  70. package/build/services/AppRegistry.js.map +1 -1
  71. package/build/services/AudioCloudUplink.d.ts +12 -0
  72. package/build/services/AudioCloudUplink.d.ts.map +1 -1
  73. package/build/services/AudioCloudUplink.js +22 -1
  74. package/build/services/AudioCloudUplink.js.map +1 -1
  75. package/build/services/AudioPlaybackService.d.ts +41 -2
  76. package/build/services/AudioPlaybackService.d.ts.map +1 -1
  77. package/build/services/AudioPlaybackService.js +250 -33
  78. package/build/services/AudioPlaybackService.js.map +1 -1
  79. package/build/services/BlobStore.d.ts +2 -1
  80. package/build/services/BlobStore.d.ts.map +1 -1
  81. package/build/services/BlobStore.js +17 -9
  82. package/build/services/BlobStore.js.map +1 -1
  83. package/build/services/CloudAudioSubscriptionSync.d.ts.map +1 -1
  84. package/build/services/CloudAudioSubscriptionSync.js +13 -1
  85. package/build/services/CloudAudioSubscriptionSync.js.map +1 -1
  86. package/build/services/CloudClientService.d.ts +15 -0
  87. package/build/services/CloudClientService.d.ts.map +1 -1
  88. package/build/services/CloudClientService.js +93 -1
  89. package/build/services/CloudClientService.js.map +1 -1
  90. package/build/services/DeviceEventRouter.js +5 -5
  91. package/build/services/DeviceEventRouter.js.map +1 -1
  92. package/build/services/ForegroundLocationPermission.d.ts +13 -0
  93. package/build/services/ForegroundLocationPermission.d.ts.map +1 -0
  94. package/build/services/ForegroundLocationPermission.js +12 -0
  95. package/build/services/ForegroundLocationPermission.js.map +1 -0
  96. package/build/services/GlassesSettingsSync.d.ts.map +1 -1
  97. package/build/services/GlassesSettingsSync.js +9 -3
  98. package/build/services/GlassesSettingsSync.js.map +1 -1
  99. package/build/services/GlassesStatusProjection.d.ts +2 -1
  100. package/build/services/GlassesStatusProjection.d.ts.map +1 -1
  101. package/build/services/GlassesStatusProjection.js +15 -8
  102. package/build/services/GlassesStatusProjection.js.map +1 -1
  103. package/build/services/HotspotOtaTransport.d.ts +29 -0
  104. package/build/services/HotspotOtaTransport.d.ts.map +1 -0
  105. package/build/services/HotspotOtaTransport.js +146 -0
  106. package/build/services/HotspotOtaTransport.js.map +1 -0
  107. package/build/services/HotspotShutdown.d.ts +17 -0
  108. package/build/services/HotspotShutdown.d.ts.map +1 -0
  109. package/build/services/HotspotShutdown.js +29 -0
  110. package/build/services/HotspotShutdown.js.map +1 -0
  111. package/build/services/LocalDisplayManager.d.ts +15 -0
  112. package/build/services/LocalDisplayManager.d.ts.map +1 -1
  113. package/build/services/LocalDisplayManager.js +106 -20
  114. package/build/services/LocalDisplayManager.js.map +1 -1
  115. package/build/services/LocalMiniappRuntime.d.ts +74 -2
  116. package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
  117. package/build/services/LocalMiniappRuntime.js +785 -291
  118. package/build/services/LocalMiniappRuntime.js.map +1 -1
  119. package/build/services/LocalMiniappStorage.d.ts +32 -0
  120. package/build/services/LocalMiniappStorage.d.ts.map +1 -0
  121. package/build/services/LocalMiniappStorage.js +66 -0
  122. package/build/services/LocalMiniappStorage.js.map +1 -0
  123. package/build/services/LocalMiniappUserIdentity.d.ts +20 -0
  124. package/build/services/LocalMiniappUserIdentity.d.ts.map +1 -0
  125. package/build/services/LocalMiniappUserIdentity.js +43 -0
  126. package/build/services/LocalMiniappUserIdentity.js.map +1 -0
  127. package/build/services/LocalSttFallbackCoordinator.d.ts +9 -6
  128. package/build/services/LocalSttFallbackCoordinator.d.ts.map +1 -1
  129. package/build/services/LocalSttFallbackCoordinator.js +18 -12
  130. package/build/services/LocalSttFallbackCoordinator.js.map +1 -1
  131. package/build/services/MicStateCoordinator.d.ts +42 -1
  132. package/build/services/MicStateCoordinator.d.ts.map +1 -1
  133. package/build/services/MicStateCoordinator.js +141 -3
  134. package/build/services/MicStateCoordinator.js.map +1 -1
  135. package/build/services/MiniappLauncher.d.ts.map +1 -1
  136. package/build/services/MiniappLauncher.js +23 -3
  137. package/build/services/MiniappLauncher.js.map +1 -1
  138. package/build/services/MiniappLiveness.d.ts +12 -0
  139. package/build/services/MiniappLiveness.d.ts.map +1 -0
  140. package/build/services/MiniappLiveness.js +16 -0
  141. package/build/services/MiniappLiveness.js.map +1 -0
  142. package/build/services/NativeHttpResponse.d.ts +3 -0
  143. package/build/services/NativeHttpResponse.d.ts.map +1 -0
  144. package/build/services/NativeHttpResponse.js +6 -0
  145. package/build/services/NativeHttpResponse.js.map +1 -0
  146. package/build/services/OtaArtifactDownloader.d.ts +65 -0
  147. package/build/services/OtaArtifactDownloader.d.ts.map +1 -0
  148. package/build/services/OtaArtifactDownloader.js +226 -0
  149. package/build/services/OtaArtifactDownloader.js.map +1 -0
  150. package/build/services/OtaAutoChain.d.ts +34 -0
  151. package/build/services/OtaAutoChain.d.ts.map +1 -0
  152. package/build/services/OtaAutoChain.js +83 -0
  153. package/build/services/OtaAutoChain.js.map +1 -0
  154. package/build/services/OtaErrorMapping.d.ts +18 -0
  155. package/build/services/OtaErrorMapping.d.ts.map +1 -0
  156. package/build/services/OtaErrorMapping.js +63 -0
  157. package/build/services/OtaErrorMapping.js.map +1 -0
  158. package/build/services/OtaInstallCoordinator.d.ts +94 -14
  159. package/build/services/OtaInstallCoordinator.d.ts.map +1 -1
  160. package/build/services/OtaInstallCoordinator.js +574 -130
  161. package/build/services/OtaInstallCoordinator.js.map +1 -1
  162. package/build/services/OtaService.d.ts.map +1 -1
  163. package/build/services/OtaService.js +8 -1
  164. package/build/services/OtaService.js.map +1 -1
  165. package/build/services/OtaUpdateCheckService.d.ts +38 -3
  166. package/build/services/OtaUpdateCheckService.d.ts.map +1 -1
  167. package/build/services/OtaUpdateCheckService.js +142 -22
  168. package/build/services/OtaUpdateCheckService.js.map +1 -1
  169. package/build/services/PhoneCalendarHelpers.d.ts +32 -0
  170. package/build/services/PhoneCalendarHelpers.d.ts.map +1 -0
  171. package/build/services/PhoneCalendarHelpers.js +157 -0
  172. package/build/services/PhoneCalendarHelpers.js.map +1 -0
  173. package/build/services/PhoneCalendarService.d.ts +5 -0
  174. package/build/services/PhoneCalendarService.d.ts.map +1 -0
  175. package/build/services/PhoneCalendarService.js +45 -0
  176. package/build/services/PhoneCalendarService.js.map +1 -0
  177. package/build/services/PhoneCameraFovCoordinator.d.ts +30 -0
  178. package/build/services/PhoneCameraFovCoordinator.d.ts.map +1 -0
  179. package/build/services/PhoneCameraFovCoordinator.js +194 -0
  180. package/build/services/PhoneCameraFovCoordinator.js.map +1 -0
  181. package/build/services/PhoneNotificationsSync.d.ts.map +1 -1
  182. package/build/services/PhoneNotificationsSync.js +12 -10
  183. package/build/services/PhoneNotificationsSync.js.map +1 -1
  184. package/build/services/PhonePhotoCoordinator.d.ts +26 -2
  185. package/build/services/PhonePhotoCoordinator.d.ts.map +1 -1
  186. package/build/services/PhonePhotoCoordinator.js +79 -9
  187. package/build/services/PhonePhotoCoordinator.js.map +1 -1
  188. package/build/services/PhoneStreamCoordinator.d.ts +8 -1
  189. package/build/services/PhoneStreamCoordinator.d.ts.map +1 -1
  190. package/build/services/PhoneStreamCoordinator.js +125 -13
  191. package/build/services/PhoneStreamCoordinator.js.map +1 -1
  192. package/build/services/PhoneVideoCoordinator.d.ts +2 -0
  193. package/build/services/PhoneVideoCoordinator.d.ts.map +1 -1
  194. package/build/services/PhoneVideoCoordinator.js +10 -0
  195. package/build/services/PhoneVideoCoordinator.js.map +1 -1
  196. package/build/services/SceneRenderer.d.ts.map +1 -1
  197. package/build/services/SceneRenderer.js +15 -0
  198. package/build/services/SceneRenderer.js.map +1 -1
  199. package/build/services/SentenceTtsPipeline.d.ts +31 -0
  200. package/build/services/SentenceTtsPipeline.d.ts.map +1 -0
  201. package/build/services/SentenceTtsPipeline.js +67 -0
  202. package/build/services/SentenceTtsPipeline.js.map +1 -0
  203. package/build/services/SupportProfileSync.d.ts +22 -0
  204. package/build/services/SupportProfileSync.d.ts.map +1 -0
  205. package/build/services/SupportProfileSync.js +213 -0
  206. package/build/services/SupportProfileSync.js.map +1 -0
  207. package/build/services/TranscriptionRouting.d.ts +12 -0
  208. package/build/services/TranscriptionRouting.d.ts.map +1 -0
  209. package/build/services/TranscriptionRouting.js +24 -0
  210. package/build/services/TranscriptionRouting.js.map +1 -0
  211. package/build/services/TtsTextSanitizer.d.ts +5 -0
  212. package/build/services/TtsTextSanitizer.d.ts.map +1 -0
  213. package/build/services/TtsTextSanitizer.js +61 -0
  214. package/build/services/TtsTextSanitizer.js.map +1 -0
  215. package/build/services/WifiAdbRequest.d.ts +17 -0
  216. package/build/services/WifiAdbRequest.d.ts.map +1 -0
  217. package/build/services/WifiAdbRequest.js +35 -0
  218. package/build/services/WifiAdbRequest.js.map +1 -0
  219. package/build/services/asg/asgCameraApi.d.ts +43 -0
  220. package/build/services/asg/asgCameraApi.d.ts.map +1 -1
  221. package/build/services/asg/asgCameraApi.js +509 -26
  222. package/build/services/asg/asgCameraApi.js.map +1 -1
  223. package/build/services/asg/cameraRollExportCoordinator.d.ts +60 -0
  224. package/build/services/asg/cameraRollExportCoordinator.d.ts.map +1 -0
  225. package/build/services/asg/cameraRollExportCoordinator.js +618 -0
  226. package/build/services/asg/cameraRollExportCoordinator.js.map +1 -0
  227. package/build/services/asg/cameraRollExportLedger.d.ts +44 -0
  228. package/build/services/asg/cameraRollExportLedger.d.ts.map +1 -0
  229. package/build/services/asg/cameraRollExportLedger.js +78 -0
  230. package/build/services/asg/cameraRollExportLedger.js.map +1 -0
  231. package/build/services/asg/galleryNotices.d.ts +1 -1
  232. package/build/services/asg/galleryNotices.d.ts.map +1 -1
  233. package/build/services/asg/galleryNotices.js.map +1 -1
  234. package/build/services/asg/galleryPathMigration.d.ts +3 -0
  235. package/build/services/asg/galleryPathMigration.d.ts.map +1 -0
  236. package/build/services/asg/galleryPathMigration.js +26 -0
  237. package/build/services/asg/galleryPathMigration.js.map +1 -0
  238. package/build/services/asg/gallerySettingsService.d.ts +1 -0
  239. package/build/services/asg/gallerySettingsService.d.ts.map +1 -1
  240. package/build/services/asg/gallerySettingsService.js +2 -0
  241. package/build/services/asg/gallerySettingsService.js.map +1 -1
  242. package/build/services/asg/gallerySyncService.d.ts.map +1 -1
  243. package/build/services/asg/gallerySyncService.js +162 -56
  244. package/build/services/asg/gallerySyncService.js.map +1 -1
  245. package/build/services/asg/galleryTransferLedger.d.ts +58 -0
  246. package/build/services/asg/galleryTransferLedger.d.ts.map +1 -0
  247. package/build/services/asg/galleryTransferLedger.js +225 -0
  248. package/build/services/asg/galleryTransferLedger.js.map +1 -0
  249. package/build/services/asg/localNetworkTransport.d.ts +15 -0
  250. package/build/services/asg/localNetworkTransport.d.ts.map +1 -0
  251. package/build/services/asg/localNetworkTransport.js +144 -0
  252. package/build/services/asg/localNetworkTransport.js.map +1 -0
  253. package/build/services/asg/localStorageService.d.ts +18 -0
  254. package/build/services/asg/localStorageService.d.ts.map +1 -1
  255. package/build/services/asg/localStorageService.js +168 -19
  256. package/build/services/asg/localStorageService.js.map +1 -1
  257. package/build/services/asg/mediaProcessingQueue.d.ts +10 -0
  258. package/build/services/asg/mediaProcessingQueue.d.ts.map +1 -1
  259. package/build/services/asg/mediaProcessingQueue.js +193 -62
  260. package/build/services/asg/mediaProcessingQueue.js.map +1 -1
  261. package/build/services/audioPlaybackAssets.d.ts +8 -0
  262. package/build/services/audioPlaybackAssets.d.ts.map +1 -0
  263. package/build/services/audioPlaybackAssets.js +7 -0
  264. package/build/services/audioPlaybackAssets.js.map +1 -0
  265. package/build/services/gallerySyncClock.js +1 -1
  266. package/build/services/gallerySyncClock.js.map +1 -1
  267. package/build/services/glassesClockSync.js +2 -2
  268. package/build/services/glassesClockSync.js.map +1 -1
  269. package/build/services/manifestActions.d.ts +7 -0
  270. package/build/services/manifestActions.d.ts.map +1 -0
  271. package/build/services/manifestActions.js +25 -0
  272. package/build/services/manifestActions.js.map +1 -0
  273. package/build/services/otaDisplayState.d.ts +22 -11
  274. package/build/services/otaDisplayState.d.ts.map +1 -1
  275. package/build/services/otaDisplayState.js +36 -14
  276. package/build/services/otaDisplayState.js.map +1 -1
  277. package/build/services/otaInstallPolicy.d.ts +15 -3
  278. package/build/services/otaInstallPolicy.d.ts.map +1 -1
  279. package/build/services/otaInstallPolicy.js +24 -3
  280. package/build/services/otaInstallPolicy.js.map +1 -1
  281. package/build/services/otaManifestUrl.d.ts +2 -0
  282. package/build/services/otaManifestUrl.d.ts.map +1 -0
  283. package/build/services/{asgOtaVersionUrl.js → otaManifestUrl.js} +15 -5
  284. package/build/services/otaManifestUrl.js.map +1 -0
  285. package/build/services/slimStreamStatus.d.ts +11 -0
  286. package/build/services/slimStreamStatus.d.ts.map +1 -1
  287. package/build/services/slimStreamStatus.js +14 -4
  288. package/build/services/slimStreamStatus.js.map +1 -1
  289. package/build/stores/apps.d.ts.map +1 -1
  290. package/build/stores/apps.js +11 -0
  291. package/build/stores/apps.js.map +1 -1
  292. package/build/stores/bluetoothSettingKeys.d.ts.map +1 -1
  293. package/build/stores/bluetoothSettingKeys.js +2 -0
  294. package/build/stores/bluetoothSettingKeys.js.map +1 -1
  295. package/build/stores/gallerySync.d.ts +2 -0
  296. package/build/stores/gallerySync.d.ts.map +1 -1
  297. package/build/stores/gallerySync.js +2 -0
  298. package/build/stores/gallerySync.js.map +1 -1
  299. package/build/stores/glasses.d.ts.map +1 -1
  300. package/build/stores/glasses.js +3 -0
  301. package/build/stores/glasses.js.map +1 -1
  302. package/build/stores/settings.d.ts +17 -0
  303. package/build/stores/settings.d.ts.map +1 -1
  304. package/build/stores/settings.js +157 -17
  305. package/build/stores/settings.js.map +1 -1
  306. package/build/types/applet.d.ts +2 -0
  307. package/build/types/applet.d.ts.map +1 -1
  308. package/build/types/applet.js.map +1 -1
  309. package/build/types/asg.d.ts +7 -0
  310. package/build/types/asg.d.ts.map +1 -1
  311. package/build/types/asg.js.map +1 -1
  312. package/build/types/enums.d.ts +1 -0
  313. package/build/types/enums.d.ts.map +1 -1
  314. package/build/types/enums.js +1 -0
  315. package/build/types/enums.js.map +1 -1
  316. package/build/types/hardware.d.ts +1 -0
  317. package/build/types/hardware.d.ts.map +1 -1
  318. package/build/types/hardware.js +14 -0
  319. package/build/types/hardware.js.map +1 -1
  320. package/build/utils/configuredDevHost.d.ts +6 -0
  321. package/build/utils/configuredDevHost.d.ts.map +1 -0
  322. package/build/utils/configuredDevHost.js +19 -0
  323. package/build/utils/configuredDevHost.js.map +1 -0
  324. package/build/utils/devMiniappLaunch.d.ts +19 -8
  325. package/build/utils/devMiniappLaunch.d.ts.map +1 -1
  326. package/build/utils/devMiniappLaunch.js +144 -21
  327. package/build/utils/devMiniappLaunch.js.map +1 -1
  328. package/build/utils/diagnosticContext.d.ts.map +1 -1
  329. package/build/utils/diagnosticContext.js +15 -17
  330. package/build/utils/diagnosticContext.js.map +1 -1
  331. package/build/utils/hardware/hardware.d.ts.map +1 -1
  332. package/build/utils/hardware/hardware.js +7 -3
  333. package/build/utils/hardware/hardware.js.map +1 -1
  334. package/build/utils/miniappDiagnostics.d.ts +64 -0
  335. package/build/utils/miniappDiagnostics.d.ts.map +1 -0
  336. package/build/utils/miniappDiagnostics.js +173 -0
  337. package/build/utils/miniappDiagnostics.js.map +1 -0
  338. package/build/utils/permissions/MediaLibraryPermissions.d.ts +11 -0
  339. package/build/utils/permissions/MediaLibraryPermissions.d.ts.map +1 -1
  340. package/build/utils/permissions/MediaLibraryPermissions.js +27 -4
  341. package/build/utils/permissions/MediaLibraryPermissions.js.map +1 -1
  342. package/build/utils/timers/timers.d.ts.map +1 -1
  343. package/build/utils/timers/timers.js +55 -18
  344. package/build/utils/timers/timers.js.map +1 -1
  345. package/package.json +25 -15
  346. package/src/assets/silence.wav +0 -0
  347. package/src/engine.ts +7 -2
  348. package/src/facades/dev.ts +17 -12
  349. package/src/facades/gallery.ts +1 -1
  350. package/src/facades/glasses.ts +17 -4
  351. package/src/facades/glassesSettings.ts +5 -0
  352. package/src/facades/miniapps.ts +7 -0
  353. package/src/facades/ota.ts +19 -4
  354. package/src/facades/pairing.ts +11 -7
  355. package/src/facades/permissions.ts +14 -0
  356. package/src/facades/phoneNotifications.ts +4 -13
  357. package/src/index.ts +26 -2
  358. package/src/internal.ts +3 -1
  359. package/src/react/MentraLiveOtaFlow.tsx +856 -0
  360. package/src/react/index.ts +7 -0
  361. package/src/react/useEngineSnapshot.ts +16 -0
  362. package/src/runtime/bootstrap.ts +33 -1
  363. package/src/runtime/scanQrSeam.ts +20 -0
  364. package/src/services/AppRegistry.ts +224 -149
  365. package/src/services/AudioCloudUplink.ts +23 -1
  366. package/src/services/AudioPlaybackService.ts +287 -38
  367. package/src/services/BlobStore.ts +22 -9
  368. package/src/services/CloudAudioSubscriptionSync.ts +13 -1
  369. package/src/services/CloudClientService.ts +96 -1
  370. package/src/services/DeviceEventRouter.ts +5 -5
  371. package/src/services/ForegroundLocationPermission.ts +24 -0
  372. package/src/services/GlassesSettingsSync.ts +9 -3
  373. package/src/services/GlassesStatusProjection.ts +16 -8
  374. package/src/services/HotspotOtaTransport.ts +184 -0
  375. package/src/services/HotspotShutdown.ts +41 -0
  376. package/src/services/LocalDisplayManager.ts +112 -21
  377. package/src/services/LocalMiniappRuntime.ts +967 -346
  378. package/src/services/LocalMiniappStorage.ts +82 -0
  379. package/src/services/LocalMiniappUserIdentity.ts +46 -0
  380. package/src/services/LocalSttFallbackCoordinator.ts +19 -12
  381. package/src/services/MicStateCoordinator.ts +169 -4
  382. package/src/services/MiniappLauncher.ts +30 -3
  383. package/src/services/MiniappLiveness.ts +24 -0
  384. package/src/services/NativeHttpResponse.ts +6 -0
  385. package/src/services/OtaArtifactDownloader.ts +312 -0
  386. package/src/services/OtaAutoChain.ts +106 -0
  387. package/src/services/OtaErrorMapping.ts +80 -0
  388. package/src/services/OtaInstallCoordinator.ts +645 -143
  389. package/src/services/OtaService.ts +11 -5
  390. package/src/services/OtaUpdateCheckService.ts +180 -20
  391. package/src/services/PhoneCalendarHelpers.ts +212 -0
  392. package/src/services/PhoneCalendarService.ts +65 -0
  393. package/src/services/PhoneCameraFovCoordinator.ts +210 -0
  394. package/src/services/PhoneNotificationsSync.ts +12 -10
  395. package/src/services/PhonePhotoCoordinator.ts +116 -18
  396. package/src/services/PhoneStreamCoordinator.ts +139 -25
  397. package/src/services/PhoneVideoCoordinator.ts +11 -0
  398. package/src/services/SceneRenderer.ts +16 -0
  399. package/src/services/SentenceTtsPipeline.ts +106 -0
  400. package/src/services/SupportProfileSync.ts +218 -0
  401. package/src/services/TranscriptionRouting.ts +37 -0
  402. package/src/services/TtsTextSanitizer.ts +66 -0
  403. package/src/services/WifiAdbRequest.ts +53 -0
  404. package/src/services/asg/asgCameraApi.ts +615 -33
  405. package/src/services/asg/cameraRollExportCoordinator.ts +627 -0
  406. package/src/services/asg/cameraRollExportLedger.ts +122 -0
  407. package/src/services/asg/galleryNotices.ts +1 -0
  408. package/src/services/asg/galleryPathMigration.ts +22 -0
  409. package/src/services/asg/gallerySettingsService.ts +3 -0
  410. package/src/services/asg/gallerySyncService.ts +184 -73
  411. package/src/services/asg/galleryTransferLedger.ts +312 -0
  412. package/src/services/asg/localNetworkTransport.ts +169 -0
  413. package/src/services/asg/localStorageService.ts +179 -24
  414. package/src/services/asg/mediaProcessingQueue.ts +209 -62
  415. package/src/services/audioPlaybackAssets.ts +8 -0
  416. package/src/services/gallerySyncClock.ts +1 -1
  417. package/src/services/glassesClockSync.ts +2 -2
  418. package/src/services/manifestActions.ts +30 -0
  419. package/src/services/otaDisplayState.ts +57 -16
  420. package/src/services/otaInstallPolicy.ts +29 -3
  421. package/src/services/{asgOtaVersionUrl.ts → otaManifestUrl.ts} +16 -4
  422. package/src/services/slimStreamStatus.ts +16 -5
  423. package/src/stores/apps.ts +12 -0
  424. package/src/stores/bluetoothSettingKeys.ts +2 -0
  425. package/src/stores/gallerySync.ts +4 -0
  426. package/src/stores/glasses.ts +3 -0
  427. package/src/stores/settings.ts +179 -18
  428. package/src/types/applet.ts +2 -0
  429. package/src/types/asg.ts +7 -0
  430. package/src/types/enums.ts +2 -1
  431. package/src/types/hardware.ts +17 -0
  432. package/src/utils/configuredDevHost.ts +17 -0
  433. package/src/utils/devMiniappLaunch.ts +151 -23
  434. package/src/utils/diagnosticContext.ts +17 -17
  435. package/src/utils/hardware/hardware.ts +7 -9
  436. package/src/utils/miniappDiagnostics.ts +226 -0
  437. package/src/utils/permissions/MediaLibraryPermissions.ts +35 -4
  438. package/src/utils/timers/timers.ts +57 -21
  439. package/build/services/asgOtaVersionUrl.d.ts +0 -2
  440. package/build/services/asgOtaVersionUrl.d.ts.map +0 -1
  441. package/build/services/asgOtaVersionUrl.js.map +0 -1
@@ -15,11 +15,7 @@ import type {OtaStatus} from "@mentra/bluetooth-sdk/internal"
15
15
  import GlobalEventEmitter from "../utils/GlobalEventEmitter"
16
16
  import {useGlassesStore} from "../stores/glasses"
17
17
  import {handleOtaClockSkewFromGlasses} from "./glassesClockSync"
18
- import {
19
- legacyOtaProgressFromOtaStatusEvent,
20
- normalizeOtaStatusEvent,
21
- otaStatusFromNormalized,
22
- } from "./otaLegacyMapping"
18
+ import {legacyOtaProgressFromOtaStatusEvent, normalizeOtaStatusEvent, otaStatusFromNormalized} from "./otaLegacyMapping"
23
19
 
24
20
  let subs: Array<{remove: () => void}> = []
25
21
 
@@ -40,6 +36,16 @@ export function startOtaService(): void {
40
36
  }),
41
37
  )
42
38
 
39
+ // The glasses process restarted under a live BLE link (sid changed in
40
+ // glasses_ready/version_info_1). The physical connection never dropped, so this is
41
+ // the ONLY reconnect-edge signal the OTA coordinator gets after an APK install.
42
+ subs.push(
43
+ BluetoothSdk.addListener("glasses_session_changed", (event) => {
44
+ console.log(`[OTA] glasses_session_changed ${event.previous_sid || "<none>"} -> ${event.sid}`)
45
+ GlobalEventEmitter.emit("glasses_session_changed", {previousSid: event.previous_sid, sid: event.sid})
46
+ }),
47
+ )
48
+
43
49
  // Install progress / lifecycle. Updates both the new (otaStatus) and the legacy
44
50
  // (otaProgress) store shapes; auto-fixes clock skew on the relevant failures.
45
51
  subs.push(
@@ -1,8 +1,19 @@
1
1
  import BluetoothSdk from "@mentra/bluetooth-sdk/internal"
2
2
  import type {OtaUpdateInfo} from "@mentra/bluetooth-sdk/internal"
3
3
  import {getGlassesSystemTimeMs, isGlassesConnected, useGlassesStore, waitForGlassesState} from "../stores/glasses"
4
+ import {SETTINGS, useSettingsStore} from "../stores/settings"
4
5
  import {maybeFixGlassesClockFromVersionInfo} from "./glassesClockSync"
5
- import {getAsgOtaVersionUrl} from "./asgOtaVersionUrl"
6
+ import {resolveOtaManifestUrl} from "./otaManifestUrl"
7
+
8
+ /**
9
+ * Phone-side mirror of ASG's `DOWNGRADE_FLOOR_VERSION_CODE`. Set to the versionCode of the first
10
+ * release shipping the shared media root (the first downgrade-safe build). A non-positive value
11
+ * means downgrades are disabled, so the phone must NOT offer them — offering a downgrade the
12
+ * glasses' `DowngradeGate` will refuse leaves the version-change session with no install and the
13
+ * glasses reporting a bare "complete", which would otherwise present as false success. Keep this
14
+ * in lockstep with the ASG/recovery constant at release time.
15
+ */
16
+ export const DOWNGRADE_FLOOR_VERSION_CODE = 0
6
17
 
7
18
  export interface VersionInfo {
8
19
  versionCode: number
@@ -18,11 +29,13 @@ export interface MtkPatch {
18
29
  start_firmware: string
19
30
  end_firmware: string
20
31
  url: string
32
+ sha256?: string
21
33
  }
22
34
 
23
35
  export interface BesFirmware {
24
36
  version: string
25
37
  url: string
38
+ sha256?: string
26
39
  }
27
40
 
28
41
  export interface VersionJson {
@@ -46,9 +59,23 @@ export interface OtaCheckResult {
46
59
  updates: string[]
47
60
  mtkPatch: MtkPatch | null
48
61
  besVersion: string | null
62
+ /** True when the pending APK step installs an OLDER build than the glasses currently run. */
63
+ isApkDowngrade: boolean
64
+ /**
65
+ * Raw manifest JSON (stringified) this check actually fetched, or null when the
66
+ * fetch failed. Lets change-watchers baseline on exactly what the check saw,
67
+ * instead of racing it with a second fetch of the same URL.
68
+ */
69
+ manifestBody: string | null
70
+ /**
71
+ * Why the check failed, when it did. "network" is retryable (connection/server
72
+ * trouble); "pin_unavailable" is permanent for this app build (manifest 404/gone,
73
+ * unparseable, or carrying no valid ASG pin) — the remedy is updating the app.
74
+ */
75
+ checkFailureReason?: "network" | "pin_unavailable"
49
76
  }
50
77
 
51
- export type OtaCheckSkippedReason = "disconnected" | "missing_build"
78
+ export type OtaCheckSkippedReason = "disconnected" | "missing_build" | "dev_build"
52
79
 
53
80
  export interface OtaCheckCurrentGlassesResult extends OtaCheckResult {
54
81
  updateInfo: OtaUpdateInfo | null
@@ -66,6 +93,8 @@ export interface OtaCheckCurrentGlassesOptions {
66
93
  waitForMtkVersionMs?: number
67
94
  refreshVersionInfo?: boolean
68
95
  fixClockBeforeCheck?: boolean
96
+ /** Downgrade floor override (defaults to DOWNGRADE_FLOOR_VERSION_CODE); for tests. */
97
+ floorVersionCode?: number
69
98
  }
70
99
 
71
100
  function emptyCheckResult(skippedReason?: OtaCheckSkippedReason): OtaCheckCurrentGlassesResult {
@@ -76,6 +105,8 @@ function emptyCheckResult(skippedReason?: OtaCheckSkippedReason): OtaCheckCurren
76
105
  updates: [],
77
106
  mtkPatch: null,
78
107
  besVersion: null,
108
+ isApkDowngrade: false,
109
+ manifestBody: null,
79
110
  updateInfo: null,
80
111
  isRequired: true,
81
112
  skippedReason,
@@ -86,46 +117,92 @@ function glassesConnectedNow(): boolean {
86
117
  return isGlassesConnected(useGlassesStore.getState().connection)
87
118
  }
88
119
 
89
- export async function fetchVersionInfo(url: string): Promise<VersionJson | null> {
120
+ type ManifestFetchResult = {json: VersionJson | null; failureReason?: "network" | "pin_unavailable"}
121
+
122
+ async function fetchVersionInfoDetailed(url: string): Promise<ManifestFetchResult> {
90
123
  try {
91
124
  const response = await fetch(url)
92
125
  if (!response.ok) {
93
126
  console.error("Failed to fetch version info:", response.status)
94
- return null
127
+ // 4xx: the manifest does not exist (or is gone) — permanent for this app
128
+ // build, retrying cannot help. Everything else is transient server/network
129
+ // trouble and retryable.
130
+ return {json: null, failureReason: response.status >= 400 && response.status < 500 ? "pin_unavailable" : "network"}
131
+ }
132
+ try {
133
+ return {json: await response.json()}
134
+ } catch (parseError) {
135
+ console.error("OTA: manifest is not valid JSON:", parseError)
136
+ return {json: null, failureReason: "pin_unavailable"}
95
137
  }
96
- return await response.json()
97
138
  } catch (error) {
98
139
  console.error("OTA: Error fetching version info:", error)
99
- return null
140
+ return {json: null, failureReason: "network"}
100
141
  }
101
142
  }
102
143
 
144
+ export async function fetchVersionInfo(url: string): Promise<VersionJson | null> {
145
+ return (await fetchVersionInfoDetailed(url)).json
146
+ }
147
+
103
148
  export function checkVersionUpdateAvailable(
104
149
  currentBuildNumber: string | undefined,
105
150
  versionJson: VersionJson | null,
151
+ floorVersionCode: number = DOWNGRADE_FLOOR_VERSION_CODE,
106
152
  ): boolean {
153
+ return getApkUpdateDirection(currentBuildNumber, versionJson, floorVersionCode) !== null
154
+ }
155
+
156
+ /**
157
+ * Direction of the pending APK change, or null when the glasses already match the manifest.
158
+ * Every apps-shaped manifest the phone drives is an exact pin, so both directions are
159
+ * actionable (mirroring the glasses-side `DowngradeGate`); the ancient top-level manifest
160
+ * shape stays upgrade-only, and zeroed rescue pins are never actionable.
161
+ */
162
+ export function getApkUpdateDirection(
163
+ currentBuildNumber: string | undefined,
164
+ versionJson: VersionJson | null,
165
+ floorVersionCode: number = DOWNGRADE_FLOOR_VERSION_CODE,
166
+ ): "upgrade" | "downgrade" | null {
107
167
  if (!currentBuildNumber || !versionJson) {
108
- return false
168
+ return null
109
169
  }
110
170
 
111
171
  const currentVersion = parseInt(currentBuildNumber, 10)
112
172
  if (isNaN(currentVersion)) {
113
- return false
173
+ return null
114
174
  }
115
175
 
116
176
  let serverVersion: number | undefined
177
+ let exactPin = false
117
178
 
118
- if (versionJson.apps?.["com.mentra.asg_client"]) {
119
- serverVersion = versionJson.apps["com.mentra.asg_client"].versionCode
179
+ const appEntry = versionJson.apps?.["com.mentra.asg_client"]
180
+ if (appEntry) {
181
+ serverVersion = appEntry.versionCode
182
+ exactPin = true
120
183
  } else if (versionJson.versionCode) {
121
184
  serverVersion = versionJson.versionCode
122
185
  }
123
186
 
124
187
  if (!serverVersion || isNaN(serverVersion)) {
125
- return false
188
+ return null
126
189
  }
127
190
 
128
- return serverVersion > currentVersion
191
+ if (serverVersion > currentVersion) {
192
+ return "upgrade"
193
+ }
194
+ // Downgrades require an exact pin AND a target at/above the enabled floor. A non-positive floor
195
+ // disables downgrades entirely, matching ASG's fail-closed DowngradeGate — so the phone never
196
+ // offers what the glasses would refuse.
197
+ if (
198
+ serverVersion < currentVersion &&
199
+ exactPin &&
200
+ floorVersionCode > 0 &&
201
+ serverVersion >= floorVersionCode
202
+ ) {
203
+ return "downgrade"
204
+ }
205
+ return null
129
206
  }
130
207
 
131
208
  export function getLatestVersionInfo(versionJson: VersionJson | null): VersionInfo | null {
@@ -184,7 +261,7 @@ export function checkBesUpdate(besFirmware: BesFirmware | undefined, currentVers
184
261
  return compareVersions(besFirmware.version, currentVersion) > 0
185
262
  }
186
263
 
187
- function compareVersions(version1: string, version2: string): number {
264
+ export function compareVersions(version1: string, version2: string): number {
188
265
  if (version1.includes(".") && version2.includes(".")) {
189
266
  const parts1 = version1.split(".")
190
267
  const parts2 = version2.split(".")
@@ -208,14 +285,62 @@ export async function checkForOtaUpdate(
208
285
  currentBuildNumber: string,
209
286
  currentMtkVersion?: string,
210
287
  currentBesVersion?: string,
288
+ floorVersionCode: number = DOWNGRADE_FLOOR_VERSION_CODE,
211
289
  ): Promise<OtaCheckResult> {
212
290
  try {
213
291
  console.log("OTA: Checking for OTA update - URL: " + otaVersionUrl + ", current build: " + currentBuildNumber)
214
- const versionJson = await fetchVersionInfo(otaVersionUrl)
292
+ const fetched = await fetchVersionInfoDetailed(otaVersionUrl)
293
+ const versionJson = fetched.json
294
+ if (!versionJson) {
295
+ return {
296
+ hasCheckCompleted: false,
297
+ updateAvailable: false,
298
+ latestVersionInfo: null,
299
+ updates: [],
300
+ mtkPatch: null,
301
+ besVersion: null,
302
+ isApkDowngrade: false,
303
+ manifestBody: null,
304
+ checkFailureReason: fetched.failureReason ?? "network",
305
+ }
306
+ }
215
307
  const latestVersionInfo = getLatestVersionInfo(versionJson)
216
308
 
217
- const apkUpdateAvailable = checkVersionUpdateAvailable(currentBuildNumber, versionJson)
218
- console.log(`OTA: APK update available: ${apkUpdateAvailable} (current: ${currentBuildNumber})`)
309
+ // A manifest whose ASG pin is missing/zeroed cannot verify anything for modern
310
+ // glasses (zeroed pins are only legitimate in the frozen legacy rescue manifests
311
+ // that pre-39 glasses are checked against). Report the check as FAILED rather
312
+ // than silently completing as "up to date" — an unverifiable state must never
313
+ // present as a verified one.
314
+ const currentVersionNumber = parseInt(currentBuildNumber, 10)
315
+ const pinnedEntry = versionJson?.apps?.["com.mentra.asg_client"]
316
+ const pinnedVersion = pinnedEntry?.versionCode ?? versionJson?.versionCode
317
+ if (
318
+ versionJson &&
319
+ Number.isFinite(currentVersionNumber) &&
320
+ currentVersionNumber >= 39 &&
321
+ !(typeof pinnedVersion === "number" && pinnedVersion > 0)
322
+ ) {
323
+ console.error(
324
+ `OTA: manifest at ${otaVersionUrl} has no valid ASG pin (versionCode=${String(pinnedVersion)}) - treating check as failed`,
325
+ )
326
+ return {
327
+ hasCheckCompleted: false,
328
+ updateAvailable: false,
329
+ latestVersionInfo: null,
330
+ updates: [],
331
+ mtkPatch: null,
332
+ besVersion: null,
333
+ isApkDowngrade: false,
334
+ manifestBody: null,
335
+ checkFailureReason: "pin_unavailable",
336
+ }
337
+ }
338
+
339
+ const apkDirection = getApkUpdateDirection(currentBuildNumber, versionJson, floorVersionCode)
340
+ const apkUpdateAvailable = apkDirection !== null
341
+ console.log(
342
+ `OTA: APK update available: ${apkUpdateAvailable} (current: ${currentBuildNumber}, direction: ${apkDirection ?? "none"})`,
343
+ )
219
344
 
220
345
  const mtkPatch = findMatchingMtkPatch(versionJson?.mtk_patches, currentMtkVersion)
221
346
  const mtkUpdateAvailable = mtkPatch !== null
@@ -243,6 +368,8 @@ export async function checkForOtaUpdate(
243
368
  updates,
244
369
  mtkPatch,
245
370
  besVersion: versionJson?.bes_firmware?.version || null,
371
+ isApkDowngrade: apkDirection === "downgrade",
372
+ manifestBody: versionJson ? JSON.stringify(versionJson) : null,
246
373
  }
247
374
  } catch (error) {
248
375
  console.error("Error checking for OTA update:", error)
@@ -253,6 +380,9 @@ export async function checkForOtaUpdate(
253
380
  updates: [],
254
381
  mtkPatch: null,
255
382
  besVersion: null,
383
+ isApkDowngrade: false,
384
+ manifestBody: null,
385
+ checkFailureReason: "network",
256
386
  }
257
387
  }
258
388
  }
@@ -266,12 +396,25 @@ export async function checkCurrentGlassesForUpdate(
266
396
  waitForMtkVersionMs = 2000,
267
397
  refreshVersionInfo = true,
268
398
  fixClockBeforeCheck = true,
399
+ floorVersionCode = DOWNGRADE_FLOOR_VERSION_CODE,
269
400
  } = options
270
401
 
271
402
  if (!glassesConnectedNow()) {
272
403
  return emptyCheckResult("disconnected")
273
404
  }
274
405
 
406
+ const embeddedManifestUrl = process.env.EXPO_PUBLIC_ASG_OTA_VERSION_URL?.trim()
407
+ const superMode = useSettingsStore.getState().getSetting(SETTINGS.super_mode.key)
408
+ const overrideUrl = useSettingsStore.getState().getSetting(SETTINGS.ota_version_url.key)
409
+ const overrideActive = superMode && typeof overrideUrl === "string" && overrideUrl.trim() !== ""
410
+ if (!embeddedManifestUrl && !overrideActive) {
411
+ console.log("OTA: check skipped - mobile app has no embedded OTA manifest pin")
412
+ return emptyCheckResult("dev_build")
413
+ }
414
+ if (!embeddedManifestUrl) {
415
+ console.log("OTA: no embedded manifest pin, but super-mode manifest override is active - checking anyway")
416
+ }
417
+
275
418
  if (refreshVersionInfo) {
276
419
  void BluetoothSdk.requestVersionInfo().catch((error) => {
277
420
  console.warn("OTA: Failed to request version_info from glasses:", error)
@@ -284,7 +427,10 @@ export async function checkCurrentGlassesForUpdate(
284
427
  buildNumber = useGlassesStore.getState().buildNumber
285
428
  }
286
429
 
287
- if (!buildNumber) {
430
+ if (!buildNumber || !(parseInt(buildNumber, 10) > 0)) {
431
+ // Covers absent, non-numeric, and zero build numbers: a glasses version we
432
+ // cannot parse means nothing is verifiable, so skip rather than ghost an
433
+ // "up to date" result from a comparison that never really ran.
288
434
  return emptyCheckResult("missing_build")
289
435
  }
290
436
 
@@ -325,8 +471,8 @@ export async function checkCurrentGlassesForUpdate(
325
471
  })
326
472
  }
327
473
 
328
- const manifestUrl = getAsgOtaVersionUrl(useGlassesStore.getState().otaVersionUrl, buildNumber)
329
- const result = await checkForOtaUpdate(manifestUrl, buildNumber, mtkFirmwareVersion, besFirmwareVersion)
474
+ const manifestUrl = resolveOtaManifestUrl(useGlassesStore.getState().otaVersionUrl, buildNumber)
475
+ const result = await checkForOtaUpdate(manifestUrl, buildNumber, mtkFirmwareVersion, besFirmwareVersion, floorVersionCode)
330
476
 
331
477
  if (!result.hasCheckCompleted) {
332
478
  return {
@@ -360,6 +506,7 @@ export async function checkCurrentGlassesForUpdate(
360
506
  )
361
507
  }
362
508
  const updateAvailable = filteredUpdates.length > 0 && !!result.latestVersionInfo
509
+ const isApkDowngrade = result.isApkDowngrade && filteredUpdates.includes("apk")
363
510
  const updateInfo = updateAvailable
364
511
  ? {
365
512
  available: true,
@@ -367,6 +514,8 @@ export async function checkCurrentGlassesForUpdate(
367
514
  versionName: result.latestVersionInfo?.versionName || "",
368
515
  updates: filteredUpdates,
369
516
  totalSize: 0,
517
+ ...(filteredUpdates.includes("bes") && result.besVersion ? {besVersion: result.besVersion} : {}),
518
+ isDowngrade: isApkDowngrade,
370
519
  }
371
520
  : null
372
521
 
@@ -376,8 +525,19 @@ export async function checkCurrentGlassesForUpdate(
376
525
  ...result,
377
526
  updateAvailable,
378
527
  updates: filteredUpdates,
528
+ isApkDowngrade,
379
529
  updateInfo,
380
- isRequired: result.latestVersionInfo?.isRequired !== false,
530
+ // Required-update policy: an EXPLICIT isRequired in the manifest is the
531
+ // release-engineering escape hatch and wins in both directions. When absent,
532
+ // upgrades keep the historical forced default, but downgrades are always
533
+ // skippable — a version change that resets glasses settings must be
534
+ // declinable unless someone explicitly forced it.
535
+ isRequired:
536
+ result.latestVersionInfo?.isRequired === true
537
+ ? true
538
+ : result.latestVersionInfo?.isRequired === false
539
+ ? false
540
+ : !isApkDowngrade,
381
541
  manifestUrl,
382
542
  buildNumber,
383
543
  mtkFirmwareVersion,
@@ -0,0 +1,212 @@
1
+ import {MiniappErrorCode, type CalendarEvent} from "@mentra/miniapp"
2
+
3
+ const DEFAULT_LIMIT = 50
4
+ const MAX_LIMIT = 100
5
+ const MAX_RANGE_MS = 31 * 24 * 60 * 60 * 1000
6
+
7
+ export interface CalendarListRequest {
8
+ startsAt?: unknown
9
+ endsAt?: unknown
10
+ limit?: unknown
11
+ }
12
+
13
+ export class PhoneCalendarError extends Error {
14
+ constructor(
15
+ public readonly code: MiniappErrorCode,
16
+ message: string,
17
+ ) {
18
+ super(message)
19
+ this.name = "PhoneCalendarError"
20
+ }
21
+ }
22
+
23
+ export interface ValidatedCalendarListRequest {
24
+ startsAt: Date
25
+ endsAt: Date
26
+ limit: number
27
+ }
28
+
29
+ export interface RawCalendarEvent {
30
+ id?: string
31
+ calendarId?: string
32
+ title?: string | null
33
+ startDate: string | Date
34
+ endDate: string | Date
35
+ timeZone?: string | null
36
+ allDay?: boolean
37
+ location?: string | null
38
+ notes?: string | null
39
+ url?: string | null
40
+ }
41
+
42
+ export function validateCalendarListRequest(request: CalendarListRequest): ValidatedCalendarListRequest {
43
+ const startsAt = parseDate(request.startsAt, "startsAt")
44
+ const endsAt = parseDate(request.endsAt, "endsAt")
45
+ if (endsAt.getTime() <= startsAt.getTime()) {
46
+ throw new PhoneCalendarError(MiniappErrorCode.INVALID_ARGUMENT, "endsAt must be after startsAt")
47
+ }
48
+ if (endsAt.getTime() - startsAt.getTime() > MAX_RANGE_MS) {
49
+ throw new PhoneCalendarError(MiniappErrorCode.INVALID_ARGUMENT, "Calendar query range may not exceed 31 days")
50
+ }
51
+
52
+ const limit = request.limit === undefined ? DEFAULT_LIMIT : request.limit
53
+ if (typeof limit !== "number" || !Number.isInteger(limit) || limit < 1 || limit > MAX_LIMIT) {
54
+ throw new PhoneCalendarError(
55
+ MiniappErrorCode.INVALID_ARGUMENT,
56
+ `limit must be an integer between 1 and ${MAX_LIMIT}`,
57
+ )
58
+ }
59
+ return {startsAt, endsAt, limit}
60
+ }
61
+
62
+ function parseDate(value: unknown, field: string): Date {
63
+ if (typeof value !== "string") {
64
+ throw new PhoneCalendarError(MiniappErrorCode.INVALID_ARGUMENT, `${field} must be an ISO 8601 string`)
65
+ }
66
+ const date = new Date(value)
67
+ if (Number.isNaN(date.getTime())) {
68
+ throw new PhoneCalendarError(MiniappErrorCode.INVALID_ARGUMENT, `${field} must be a valid ISO 8601 string`)
69
+ }
70
+ return date
71
+ }
72
+
73
+ export function extractHttpsLinks(...values: Array<string | null | undefined>): string[] {
74
+ const links = new Set<string>()
75
+ for (const value of values) {
76
+ for (const match of value?.match(/https:\/\/[^\s<>"']+/gi) ?? []) {
77
+ let link = match
78
+ while (".,;:!?)]}".includes(link[link.length - 1] ?? "")) link = link.slice(0, -1)
79
+ if (link) links.add(link)
80
+ }
81
+ }
82
+ return [...links]
83
+ }
84
+
85
+ interface CalendarDateParts {
86
+ year: number
87
+ month: number
88
+ day: number
89
+ hour: number
90
+ minute: number
91
+ second: number
92
+ }
93
+
94
+ function localDateParts(date: Date): CalendarDateParts {
95
+ return {
96
+ year: date.getFullYear(),
97
+ month: date.getMonth() + 1,
98
+ day: date.getDate(),
99
+ hour: date.getHours(),
100
+ minute: date.getMinutes(),
101
+ second: date.getSeconds(),
102
+ }
103
+ }
104
+
105
+ function datePartsInTimeZone(date: Date, timeZone: string): CalendarDateParts | undefined {
106
+ try {
107
+ const parts = new Intl.DateTimeFormat("en-US", {
108
+ timeZone,
109
+ year: "numeric",
110
+ month: "2-digit",
111
+ day: "2-digit",
112
+ hour: "2-digit",
113
+ minute: "2-digit",
114
+ second: "2-digit",
115
+ hourCycle: "h23",
116
+ }).formatToParts(date)
117
+ const value = (type: Intl.DateTimeFormatPartTypes) => Number(parts.find((part) => part.type === type)?.value)
118
+ return {
119
+ year: value("year"),
120
+ month: value("month"),
121
+ day: value("day"),
122
+ hour: value("hour"),
123
+ minute: value("minute"),
124
+ second: value("second"),
125
+ }
126
+ } catch {
127
+ return undefined
128
+ }
129
+ }
130
+
131
+ function timeZoneOffsetMinutes(date: Date, timeZone: string): number | undefined {
132
+ const parts = datePartsInTimeZone(date, timeZone)
133
+ if (!parts) return undefined
134
+ const representedAsUtc = Date.UTC(
135
+ parts.year,
136
+ parts.month - 1,
137
+ parts.day,
138
+ parts.hour,
139
+ parts.minute,
140
+ parts.second,
141
+ date.getUTCMilliseconds(),
142
+ )
143
+ return Math.round((representedAsUtc - date.getTime()) / 60_000)
144
+ }
145
+
146
+ function pad(value: number, length = 2): string {
147
+ return String(value).padStart(length, "0")
148
+ }
149
+
150
+ function formatOffset(offsetMinutes: number): string {
151
+ if (offsetMinutes === 0) return "Z"
152
+ const sign = offsetMinutes < 0 ? "-" : "+"
153
+ const absolute = Math.abs(offsetMinutes)
154
+ return `${sign}${pad(Math.floor(absolute / 60))}:${pad(absolute % 60)}`
155
+ }
156
+
157
+ function formatAllDayDate(value: string | Date, timeZone?: string | null): string {
158
+ const dateOnly = typeof value === "string" ? /^(\d{4})-(\d{2})-(\d{2})$/.exec(value) : null
159
+ if (dateOnly) {
160
+ const [, year, month, day] = dateOnly
161
+ const localMidnight = new Date(Number(year), Number(month) - 1, Number(day))
162
+ let offsetMinutes = -localMidnight.getTimezoneOffset()
163
+ if (timeZone) {
164
+ // Resolve the offset at midnight in the event timezone. Repeating once
165
+ // accounts for the initial UTC guess landing across a DST boundary.
166
+ const wallTime = Date.UTC(Number(year), Number(month) - 1, Number(day))
167
+ let instant = new Date(wallTime)
168
+ for (let attempt = 0; attempt < 2; attempt += 1) {
169
+ const offset = timeZoneOffsetMinutes(instant, timeZone)
170
+ if (offset === undefined) break
171
+ offsetMinutes = offset
172
+ instant = new Date(wallTime - offset * 60_000)
173
+ }
174
+ }
175
+ return `${year}-${month}-${day}T00:00:00.000${formatOffset(offsetMinutes)}`
176
+ }
177
+
178
+ const date = new Date(value)
179
+ const zonedParts = timeZone ? datePartsInTimeZone(date, timeZone) : undefined
180
+ const parts = zonedParts ?? localDateParts(date)
181
+ const offsetMinutes =
182
+ timeZone && zonedParts
183
+ ? (timeZoneOffsetMinutes(date, timeZone) ?? -date.getTimezoneOffset())
184
+ : -date.getTimezoneOffset()
185
+ return `${pad(parts.year, 4)}-${pad(parts.month)}-${pad(parts.day)}T${pad(parts.hour)}:${pad(parts.minute)}:${pad(parts.second)}.${pad(date.getUTCMilliseconds(), 3)}${formatOffset(offsetMinutes)}`
186
+ }
187
+
188
+ export function normalizeCalendarEvent(event: RawCalendarEvent): CalendarEvent {
189
+ const startsAt = event.allDay
190
+ ? formatAllDayDate(event.startDate, event.timeZone)
191
+ : new Date(event.startDate).toISOString()
192
+ const endsAt = event.allDay ? formatAllDayDate(event.endDate, event.timeZone) : new Date(event.endDate).toISOString()
193
+ const calendarId = event.calendarId ?? ""
194
+ const eventId = event.id ?? ""
195
+ return {
196
+ id: `${calendarId}:${eventId}:${startsAt}`,
197
+ calendarId,
198
+ title: event.title ?? "",
199
+ startsAt,
200
+ endsAt,
201
+ ...(event.timeZone ? {timezone: event.timeZone} : {}),
202
+ allDay: !!event.allDay,
203
+ ...(event.location ? {location: event.location} : {}),
204
+ ...(event.notes ? {notes: event.notes} : {}),
205
+ ...(event.url ? {url: event.url} : {}),
206
+ links: extractHttpsLinks(event.url, event.location, event.notes),
207
+ }
208
+ }
209
+
210
+ export function calendarEventOverlapsWindow(event: CalendarEvent, startsAt: Date, endsAt: Date): boolean {
211
+ return Date.parse(event.endsAt) > startsAt.getTime() && Date.parse(event.startsAt) < endsAt.getTime()
212
+ }
@@ -0,0 +1,65 @@
1
+ import * as Calendar from "expo-calendar"
2
+ import {Platform} from "react-native"
3
+ import {check, PERMISSIONS, RESULTS} from "react-native-permissions"
4
+
5
+ import {MiniappErrorCode, type CalendarListResult} from "@mentra/miniapp"
6
+
7
+ import {
8
+ calendarEventOverlapsWindow,
9
+ type CalendarListRequest,
10
+ normalizeCalendarEvent,
11
+ PhoneCalendarError,
12
+ validateCalendarListRequest,
13
+ } from "./PhoneCalendarHelpers"
14
+
15
+ export {PhoneCalendarError} from "./PhoneCalendarHelpers"
16
+
17
+ type CalendarPermissionStatus = Awaited<
18
+ ReturnType<typeof Calendar.getCalendarPermissionsAsync>
19
+ >["status"]
20
+
21
+ /**
22
+ * expo-calendar reports anything below EventKit full access as not granted,
23
+ * so distinguish the states the user can actually fix. "Reopen the miniapp"
24
+ * is only true while the permission is undetermined — once iOS has resolved
25
+ * it (denied or write-only/limited), only Settings can change it.
26
+ */
27
+ async function calendarPermissionMessage(status: CalendarPermissionStatus): Promise<string> {
28
+ if (status === "undetermined") {
29
+ return "Calendar permission has not been granted yet. Reopen the miniapp and allow calendar access."
30
+ }
31
+ if (Platform.OS === "ios") {
32
+ const writeOnly = await check(PERMISSIONS.IOS.CALENDARS_WRITE_ONLY).catch(() => null)
33
+ if (writeOnly === RESULTS.GRANTED || writeOnly === RESULTS.LIMITED) {
34
+ return 'Calendar access is limited to write-only. Enable "Full Access" for Mentra in Settings › Privacy & Security › Calendars.'
35
+ }
36
+ return "Calendar access is denied. Enable it for Mentra in Settings › Privacy & Security › Calendars."
37
+ }
38
+ return "Calendar access is denied. Enable the Calendar permission for Mentra in your phone's app settings."
39
+ }
40
+
41
+ export async function listPhoneCalendarEvents(request: CalendarListRequest): Promise<CalendarListResult> {
42
+ const {startsAt, endsAt, limit} = validateCalendarListRequest(request)
43
+ const permission = await Calendar.getCalendarPermissionsAsync()
44
+ if (permission.status !== "granted") {
45
+ throw new PhoneCalendarError(MiniappErrorCode.PERMISSION_DENIED, await calendarPermissionMessage(permission.status))
46
+ }
47
+
48
+ const calendars = await Calendar.getCalendarsAsync(Calendar.EntityTypes.EVENT)
49
+ if (calendars.length === 0) return {events: [], truncated: false}
50
+
51
+ const rawEvents = await Calendar.getEventsAsync(
52
+ calendars.map((calendar) => calendar.id),
53
+ startsAt,
54
+ endsAt,
55
+ )
56
+ const events = rawEvents
57
+ .map(normalizeCalendarEvent)
58
+ .filter((event) => calendarEventOverlapsWindow(event, startsAt, endsAt))
59
+ .sort((a, b) => Date.parse(a.startsAt) - Date.parse(b.startsAt) || Date.parse(a.endsAt) - Date.parse(b.endsAt))
60
+
61
+ return {
62
+ events: events.slice(0, limit),
63
+ truncated: events.length > limit,
64
+ }
65
+ }