@mentra/sdk 2.1.31-beta.5 → 3.0.0-alpha.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 (169) hide show
  1. package/README.md +54 -9
  2. package/dist/MiniAppServer.d.ts +58 -0
  3. package/dist/MiniAppServer.d.ts.map +1 -0
  4. package/dist/app/server/index.d.ts +208 -70
  5. package/dist/app/server/index.d.ts.map +1 -1
  6. package/dist/app/session/events.d.ts +20 -3
  7. package/dist/app/session/events.d.ts.map +1 -1
  8. package/dist/app/session/index.d.ts +56 -3
  9. package/dist/app/session/index.d.ts.map +1 -1
  10. package/dist/app/session/modules/audio-output-stream.d.ts +108 -0
  11. package/dist/app/session/modules/audio-output-stream.d.ts.map +1 -0
  12. package/dist/app/session/modules/audio.d.ts +36 -1
  13. package/dist/app/session/modules/audio.d.ts.map +1 -1
  14. package/dist/app/session/modules/camera-managed-extension.d.ts +37 -33
  15. package/dist/app/session/modules/camera-managed-extension.d.ts.map +1 -1
  16. package/dist/app/session/modules/camera.d.ts +113 -93
  17. package/dist/app/session/modules/camera.d.ts.map +1 -1
  18. package/dist/app/session/modules/index.d.ts +4 -3
  19. package/dist/app/session/modules/index.d.ts.map +1 -1
  20. package/dist/app/session/modules/led.d.ts.map +1 -1
  21. package/dist/app/session/modules/location.d.ts.map +1 -1
  22. package/dist/app/session/settings.d.ts +5 -1
  23. package/dist/app/session/settings.d.ts.map +1 -1
  24. package/dist/app/webview/index.d.ts +67 -9
  25. package/dist/app/webview/index.d.ts.map +1 -1
  26. package/dist/constants/log-messages/updates.d.ts +32 -9
  27. package/dist/constants/log-messages/updates.d.ts.map +1 -1
  28. package/dist/constants/log-messages/warning.d.ts +12 -0
  29. package/dist/constants/log-messages/warning.d.ts.map +1 -1
  30. package/dist/display-utils.d.ts +3 -1
  31. package/dist/display-utils.d.ts.map +1 -1
  32. package/dist/display-utils.js +443 -26
  33. package/dist/display-utils.js.map +10 -6
  34. package/dist/index.d.ts +22 -14
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +8957 -4428
  37. package/dist/index.js.map +63 -31
  38. package/dist/internal/_SessionManager.d.ts +76 -0
  39. package/dist/internal/_SessionManager.d.ts.map +1 -0
  40. package/dist/logging/clean-transport.d.ts +50 -0
  41. package/dist/logging/clean-transport.d.ts.map +1 -0
  42. package/dist/logging/errors.d.ts +90 -0
  43. package/dist/logging/errors.d.ts.map +1 -0
  44. package/dist/logging/logger.d.ts +72 -1
  45. package/dist/logging/logger.d.ts.map +1 -1
  46. package/dist/logging/telemetry-transport.d.ts +51 -0
  47. package/dist/logging/telemetry-transport.d.ts.map +1 -0
  48. package/dist/session/DataStreamRouter.d.ts +219 -0
  49. package/dist/session/DataStreamRouter.d.ts.map +1 -0
  50. package/dist/session/MentraSession.d.ts +102 -0
  51. package/dist/session/MentraSession.d.ts.map +1 -0
  52. package/dist/session/index.d.ts +2 -0
  53. package/dist/session/index.d.ts.map +1 -0
  54. package/dist/session/internal/_ConnectionManager.d.ts +43 -0
  55. package/dist/session/internal/_ConnectionManager.d.ts.map +1 -0
  56. package/dist/session/internal/_MessageRouter.d.ts +11 -0
  57. package/dist/session/internal/_MessageRouter.d.ts.map +1 -0
  58. package/dist/session/internal/_SubscriptionManager.d.ts +32 -0
  59. package/dist/session/internal/_SubscriptionManager.d.ts.map +1 -0
  60. package/dist/session/internal/_V2AudioStreamShim.d.ts +12 -0
  61. package/dist/session/internal/_V2AudioStreamShim.d.ts.map +1 -0
  62. package/dist/session/internal/_V2CameraShim.d.ts +52 -0
  63. package/dist/session/internal/_V2CameraShim.d.ts.map +1 -0
  64. package/dist/session/internal/_V2EventManagerShim.d.ts +52 -0
  65. package/dist/session/internal/_V2EventManagerShim.d.ts.map +1 -0
  66. package/dist/session/internal/_V2SessionShim.d.ts +169 -0
  67. package/dist/session/internal/_V2SessionShim.d.ts.map +1 -0
  68. package/dist/session/internal/_V2SettingsShim.d.ts +17 -0
  69. package/dist/session/internal/_V2SettingsShim.d.ts.map +1 -0
  70. package/dist/session/managers/CameraManager.d.ts +198 -0
  71. package/dist/session/managers/CameraManager.d.ts.map +1 -0
  72. package/dist/session/managers/DashboardManager.d.ts +131 -0
  73. package/dist/session/managers/DashboardManager.d.ts.map +1 -0
  74. package/dist/session/managers/DeviceManager.d.ts +348 -0
  75. package/dist/session/managers/DeviceManager.d.ts.map +1 -0
  76. package/dist/session/managers/DisplayManager.d.ts +171 -0
  77. package/dist/session/managers/DisplayManager.d.ts.map +1 -0
  78. package/dist/session/managers/LedManager.d.ts +116 -0
  79. package/dist/session/managers/LedManager.d.ts.map +1 -0
  80. package/dist/session/managers/LocationManager.d.ts +224 -0
  81. package/dist/session/managers/LocationManager.d.ts.map +1 -0
  82. package/dist/session/managers/MicManager.d.ts +252 -0
  83. package/dist/session/managers/MicManager.d.ts.map +1 -0
  84. package/dist/session/managers/PermissionsManager.d.ts +139 -0
  85. package/dist/session/managers/PermissionsManager.d.ts.map +1 -0
  86. package/dist/session/managers/PhoneManager.d.ts +351 -0
  87. package/dist/session/managers/PhoneManager.d.ts.map +1 -0
  88. package/dist/session/managers/SpeakerManager.d.ts +285 -0
  89. package/dist/session/managers/SpeakerManager.d.ts.map +1 -0
  90. package/dist/session/managers/StorageManager.d.ts +289 -0
  91. package/dist/session/managers/StorageManager.d.ts.map +1 -0
  92. package/dist/session/managers/TimeUtils.d.ts +175 -0
  93. package/dist/session/managers/TimeUtils.d.ts.map +1 -0
  94. package/dist/session/managers/TranscriptionManager.d.ts +195 -0
  95. package/dist/session/managers/TranscriptionManager.d.ts.map +1 -0
  96. package/dist/session/managers/TranslationManager.d.ts +189 -0
  97. package/dist/session/managers/TranslationManager.d.ts.map +1 -0
  98. package/dist/session.d.ts +41 -0
  99. package/dist/session.d.ts.map +1 -0
  100. package/dist/session.js +4110 -0
  101. package/dist/session.js.map +44 -0
  102. package/dist/transport/Transport.d.ts +119 -0
  103. package/dist/transport/Transport.d.ts.map +1 -0
  104. package/dist/transport/WebSocketTransport.d.ts +73 -0
  105. package/dist/transport/WebSocketTransport.d.ts.map +1 -0
  106. package/dist/types/index.d.ts +31 -5
  107. package/dist/types/index.d.ts.map +1 -1
  108. package/dist/types/message-types.d.ts +25 -9
  109. package/dist/types/message-types.d.ts.map +1 -1
  110. package/dist/types/messages/app-to-cloud.d.ts +113 -16
  111. package/dist/types/messages/app-to-cloud.d.ts.map +1 -1
  112. package/dist/types/messages/cloud-to-app.d.ts +50 -4
  113. package/dist/types/messages/cloud-to-app.d.ts.map +1 -1
  114. package/dist/types/messages/cloud-to-glasses.d.ts +43 -14
  115. package/dist/types/messages/cloud-to-glasses.d.ts.map +1 -1
  116. package/dist/types/messages/glasses-to-cloud.d.ts +5 -5
  117. package/dist/types/messages/glasses-to-cloud.d.ts.map +1 -1
  118. package/dist/types/models.d.ts +17 -0
  119. package/dist/types/models.d.ts.map +1 -1
  120. package/dist/types/rtmp-stream.d.ts +4 -4
  121. package/dist/types/rtmp-stream.d.ts.map +1 -1
  122. package/dist/types/streams.d.ts +6 -1
  123. package/dist/types/streams.d.ts.map +1 -1
  124. package/dist/types/webhooks.d.ts +11 -0
  125. package/dist/types/webhooks.d.ts.map +1 -1
  126. package/dist/utils/error-utils.d.ts +139 -0
  127. package/dist/utils/error-utils.d.ts.map +1 -0
  128. package/dist/utils/permissions-utils.d.ts +30 -7
  129. package/dist/utils/permissions-utils.d.ts.map +1 -1
  130. package/node_modules/@mentra/types/dist/applet.d.ts +5 -1
  131. package/node_modules/@mentra/types/dist/applet.d.ts.map +1 -1
  132. package/node_modules/@mentra/types/dist/capabilities/even-realities-g2.d.ts +12 -0
  133. package/node_modules/@mentra/types/dist/capabilities/even-realities-g2.d.ts.map +1 -0
  134. package/node_modules/@mentra/types/dist/capabilities/even-realities-g2.js +61 -0
  135. package/node_modules/@mentra/types/dist/capabilities/mentra-display.d.ts +12 -0
  136. package/node_modules/@mentra/types/dist/capabilities/mentra-display.d.ts.map +1 -0
  137. package/node_modules/@mentra/types/dist/capabilities/mentra-display.js +54 -0
  138. package/node_modules/@mentra/types/dist/capabilities/none.d.ts +13 -0
  139. package/node_modules/@mentra/types/dist/capabilities/none.d.ts.map +1 -0
  140. package/node_modules/@mentra/types/dist/capabilities/none.js +67 -0
  141. package/node_modules/@mentra/types/dist/enums.d.ts +8 -2
  142. package/node_modules/@mentra/types/dist/enums.d.ts.map +1 -1
  143. package/node_modules/@mentra/types/dist/enums.js +9 -2
  144. package/node_modules/@mentra/types/dist/hardware.d.ts +3 -1
  145. package/node_modules/@mentra/types/dist/hardware.d.ts.map +1 -1
  146. package/node_modules/@mentra/types/dist/hardware.js +12 -2
  147. package/node_modules/@mentra/types/dist/index.d.ts +1 -1
  148. package/node_modules/@mentra/types/dist/index.d.ts.map +1 -1
  149. package/node_modules/@mentra/types/dist/index.js +2 -1
  150. package/node_modules/@mentra/types/package.json +2 -2
  151. package/package.json +24 -12
  152. package/dist/examples/managed-rtmp-streaming-example.d.ts +0 -2
  153. package/dist/examples/managed-rtmp-streaming-example.d.ts.map +0 -1
  154. package/dist/examples/managed-rtmp-streaming-with-restream-example.d.ts +0 -11
  155. package/dist/examples/managed-rtmp-streaming-with-restream-example.d.ts.map +0 -1
  156. package/dist/examples/rtmp-streaming-example.d.ts +0 -2
  157. package/dist/examples/rtmp-streaming-example.d.ts.map +0 -1
  158. package/node_modules/@mentra/types/src/applet.ts +0 -51
  159. package/node_modules/@mentra/types/src/capabilities/even-realities-g1.ts +0 -63
  160. package/node_modules/@mentra/types/src/capabilities/mentra-live.ts +0 -103
  161. package/node_modules/@mentra/types/src/capabilities/simulated-glasses.ts +0 -76
  162. package/node_modules/@mentra/types/src/capabilities/vuzix-z100.ts +0 -60
  163. package/node_modules/@mentra/types/src/cli.ts +0 -169
  164. package/node_modules/@mentra/types/src/device.ts +0 -43
  165. package/node_modules/@mentra/types/src/enums.ts +0 -36
  166. package/node_modules/@mentra/types/src/hardware.ts +0 -172
  167. package/node_modules/@mentra/types/src/index.ts +0 -64
  168. package/node_modules/@mentra/types/tsconfig.json +0 -22
  169. package/node_modules/@mentra/types/tsconfig.tsbuildinfo +0 -1
package/dist/index.js.map CHANGED
@@ -1,47 +1,79 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/types/streams.ts", "../src/types/message-types.ts", "../src/types/dashboard/index.ts", "../src/app/session/dashboard.ts", "../src/app/token/utils.ts", "../src/index.ts", "../src/types/messages/glasses-to-cloud.ts", "../src/types/messages/cloud-to-glasses.ts", "../src/types/messages/app-to-cloud.ts", "../src/utils/bitmap-utils.ts", "../src/utils/animation-utils.ts", "../src/types/messages/cloud-to-app.ts", "../src/types/enums.ts", "../src/types/models.ts", "../src/types/webhooks.ts", "../src/app/server/index.ts", "../src/app/session/index.ts", "../src/app/session/events.ts", "../src/types/index.ts", "../src/constants/log-messages/warning.ts", "../src/constants/log-messages/logos.ts", "../src/utils/permissions-utils.ts", "../src/app/session/layouts.ts", "../src/app/session/settings.ts", "../src/app/session/api-client.ts", "../src/logging/logger.ts", "../src/app/session/modules/location.ts", "../src/app/session/modules/camera.ts", "../src/app/session/modules/camera-managed-extension.ts", "../src/app/session/modules/led.ts", "../src/app/session/modules/audio.ts", "../src/utils/resource-tracker.ts", "../src/app/session/modules/simple-storage.ts", "../src/utils/Observable.ts", "../src/app/session/device-state.ts", "../src/app/webview/index.ts", "../src/constants/log-messages/updates.ts", "../src/app/token/index.ts"],
3
+ "sources": ["../src/logging/clean-transport.ts", "../src/logging/logger.ts", "../src/types/streams.ts", "../src/types/message-types.ts", "../src/types/messages/glasses-to-cloud.ts", "../src/types/messages/cloud-to-glasses.ts", "../src/types/messages/app-to-cloud.ts", "../src/types/messages/cloud-to-app.ts", "../src/types/dashboard/index.ts", "../src/types/enums.ts", "../src/types/models.ts", "../src/types/webhooks.ts", "../src/types/index.ts", "../src/constants/log-messages/warning.ts", "../src/utils/permissions-utils.ts", "../src/app/session/events.ts", "../src/app/session/dashboard.ts", "../src/app/token/utils.ts", "../src/session/MentraSession.ts", "../src/session/managers/CameraManager.ts", "../src/session/managers/DashboardManager.ts", "../src/utils/Observable.ts", "../src/session/managers/DeviceManager.ts", "../src/session/managers/DisplayManager.ts", "../src/session/managers/LedManager.ts", "../src/session/managers/LocationManager.ts", "../src/session/managers/MicManager.ts", "../src/session/managers/PermissionsManager.ts", "../src/session/managers/PhoneManager.ts", "../src/session/managers/SpeakerManager.ts", "../src/session/managers/StorageManager.ts", "../src/session/managers/TimeUtils.ts", "../src/session/managers/TranscriptionManager.ts", "../src/session/managers/TranslationManager.ts", "../src/session/DataStreamRouter.ts", "../src/session/internal/_MessageRouter.ts", "../src/transport/Transport.ts", "../src/session/internal/_ConnectionManager.ts", "../src/session/internal/_SubscriptionManager.ts", "../src/utils/error-utils.ts", "../src/index.ts", "../src/utils/bitmap-utils.ts", "../src/utils/animation-utils.ts", "../src/app/server/index.ts", "../src/constants/log-messages/updates.ts", "../src/app/session/index.ts", "../src/app/session/layouts.ts", "../src/app/session/settings.ts", "../src/app/session/api-client.ts", "../src/app/session/modules/location.ts", "../src/app/session/modules/camera.ts", "../src/app/session/modules/camera-managed-extension.ts", "../src/app/session/modules/led.ts", "../src/app/session/modules/audio.ts", "../src/app/session/modules/audio-output-stream.ts", "../src/utils/resource-tracker.ts", "../src/logging/errors.ts", "../src/logging/telemetry-transport.ts", "../src/app/session/modules/simple-storage.ts", "../src/app/session/device-state.ts", "../src/app/webview/index.ts", "../src/app/token/index.ts", "../src/transport/WebSocketTransport.ts", "../src/session/internal/_V2AudioStreamShim.ts", "../src/session/internal/_V2CameraShim.ts", "../src/session/internal/_V2EventManagerShim.ts", "../src/session/internal/_V2SettingsShim.ts", "../src/session/internal/_V2SessionShim.ts", "../src/internal/_SessionManager.ts", "../src/MiniAppServer.ts"],
4
4
  "sourcesContent": [
5
- "// src/streams.ts\n\n/**\n * Types of streams that Apps can subscribe to\n *\n * These are events and data that Apps can receive from the cloud.\n * Not all message types can be subscribed to as streams.\n */\nexport enum StreamType {\n // Hardware streams\n BUTTON_PRESS = \"button_press\",\n HEAD_POSITION = \"head_position\",\n TOUCH_EVENT = \"touch_event\",\n GLASSES_BATTERY_UPDATE = \"glasses_battery_update\",\n PHONE_BATTERY_UPDATE = \"phone_battery_update\",\n GLASSES_CONNECTION_STATE = \"glasses_connection_state\",\n LOCATION_UPDATE = \"location_update\",\n LOCATION_STREAM = \"location_stream\",\n VPS_COORDINATES = \"vps_coordinates\",\n\n // Audio streams\n TRANSCRIPTION = \"transcription\",\n TRANSLATION = \"translation\",\n VAD = \"VAD\",\n AUDIO_CHUNK = \"audio_chunk\",\n\n // Phone streams\n PHONE_NOTIFICATION = \"phone_notification\",\n PHONE_NOTIFICATION_DISMISSED = \"phone_notification_dismissed\",\n CALENDAR_EVENT = \"calendar_event\",\n\n // System streams\n START_APP = \"start_app\",\n STOP_APP = \"stop_app\",\n OPEN_DASHBOARD = \"open_dashboard\",\n CORE_STATUS_UPDATE = \"core_status_update\",\n\n // Video streams\n VIDEO = \"video\",\n PHOTO_REQUEST = \"photo_request\",\n PHOTO_RESPONSE = \"photo_response\",\n RTMP_STREAM_STATUS = \"rtmp_stream_status\",\n MANAGED_STREAM_STATUS = \"managed_stream_status\",\n\n // Special subscription types\n ALL = \"all\",\n WILDCARD = \"*\",\n\n // New stream type\n MENTRAOS_SETTINGS_UPDATE_REQUEST = \"settings_update_request\",\n CUSTOM_MESSAGE = \"custom_message\",\n PHOTO_TAKEN = \"photo_taken\",\n}\n\n/**\n * Extended StreamType to support language-specific streams\n * This allows us to treat language-specific strings as StreamType values\n */\nexport type ExtendedStreamType = StreamType | string\n\n/**\n * Categories of stream data\n */\nexport enum StreamCategory {\n /** Data from hardware sensors */\n HARDWARE = \"hardware\",\n\n /** Audio processing results */\n AUDIO = \"audio\",\n\n /** Phone-related events */\n PHONE = \"phone\",\n\n /** System-level events */\n SYSTEM = \"system\",\n}\n\n/**\n * Map of stream categories for each stream type\n */\nexport const STREAM_CATEGORIES: Record<StreamType, StreamCategory> = {\n [StreamType.BUTTON_PRESS]: StreamCategory.HARDWARE,\n [StreamType.HEAD_POSITION]: StreamCategory.HARDWARE,\n [StreamType.TOUCH_EVENT]: StreamCategory.HARDWARE,\n [StreamType.GLASSES_BATTERY_UPDATE]: StreamCategory.HARDWARE,\n [StreamType.PHONE_BATTERY_UPDATE]: StreamCategory.HARDWARE,\n [StreamType.GLASSES_CONNECTION_STATE]: StreamCategory.HARDWARE,\n [StreamType.LOCATION_UPDATE]: StreamCategory.HARDWARE,\n [StreamType.LOCATION_STREAM]: StreamCategory.HARDWARE,\n [StreamType.VPS_COORDINATES]: StreamCategory.HARDWARE,\n\n [StreamType.TRANSCRIPTION]: StreamCategory.AUDIO,\n [StreamType.TRANSLATION]: StreamCategory.AUDIO,\n [StreamType.VAD]: StreamCategory.AUDIO,\n [StreamType.AUDIO_CHUNK]: StreamCategory.AUDIO,\n\n [StreamType.PHONE_NOTIFICATION]: StreamCategory.PHONE,\n [StreamType.PHONE_NOTIFICATION_DISMISSED]: StreamCategory.PHONE,\n [StreamType.CALENDAR_EVENT]: StreamCategory.PHONE,\n [StreamType.START_APP]: StreamCategory.SYSTEM,\n [StreamType.STOP_APP]: StreamCategory.SYSTEM,\n [StreamType.OPEN_DASHBOARD]: StreamCategory.SYSTEM,\n [StreamType.CORE_STATUS_UPDATE]: StreamCategory.SYSTEM,\n\n [StreamType.VIDEO]: StreamCategory.HARDWARE,\n [StreamType.PHOTO_REQUEST]: StreamCategory.HARDWARE,\n [StreamType.PHOTO_RESPONSE]: StreamCategory.HARDWARE,\n [StreamType.RTMP_STREAM_STATUS]: StreamCategory.HARDWARE,\n [StreamType.MANAGED_STREAM_STATUS]: StreamCategory.HARDWARE,\n [StreamType.ALL]: StreamCategory.SYSTEM,\n [StreamType.WILDCARD]: StreamCategory.SYSTEM,\n\n [StreamType.MENTRAOS_SETTINGS_UPDATE_REQUEST]: StreamCategory.SYSTEM,\n [StreamType.CUSTOM_MESSAGE]: StreamCategory.SYSTEM,\n [StreamType.PHOTO_TAKEN]: StreamCategory.HARDWARE,\n}\n\n/**\n * Branded type for TypeScript to recognize language-specific stream types\n * This helps maintain type safety when using language-specific streams\n */\nexport type LanguageStreamType<T extends string> = T & {\n __languageStreamBrand: never\n}\n\n/**\n * Create a language-branded stream type\n * This is a type helper to ensure type safety for language-specific streams\n */\nfunction createLanguageStream<T extends string>(type: T): LanguageStreamType<T> {\n return type as LanguageStreamType<T>\n}\n\n/**\n * Structure of a parsed language stream subscription\n */\nexport interface LanguageStreamInfo {\n type: StreamType // Base stream type (e.g., TRANSCRIPTION)\n baseType: string // String representation of base type (e.g., \"transcription\")\n transcribeLanguage: string // Source language code (e.g., \"en-US\")\n translateLanguage?: string // Target language code for translations (e.g., \"es-ES\")\n options?: Record<string, string | boolean> // Query parameters/options\n original: ExtendedStreamType // Original subscription string\n}\n\n/**\n * Check if a string is a valid language code\n * Simple validation for language code format: xx-XX (e.g., en-US)\n */\nexport function isValidLanguageCode(code: string): boolean {\n return /^[a-z]{2,3}-[A-Z]{2}$/.test(code)\n}\n\n/**\n * Parse a subscription string to extract language information\n *\n * @param subscription Subscription string (e.g., \"transcription:en-US\" or \"translation:es-ES-to-en-US\" or \"transcription:en-US?no-language-identification=true\")\n * @returns Parsed language stream info or null if not a language-specific subscription\n */\nexport function parseLanguageStream(subscription: ExtendedStreamType): LanguageStreamInfo | null {\n // console.log(`🎤 Parsing language stream: ${subscription}`);\n\n if (typeof subscription !== \"string\") {\n return null\n }\n\n // Handle transcription format (transcription:en-US or transcription:en-US?options)\n if (subscription.startsWith(`${StreamType.TRANSCRIPTION}:`)) {\n const [baseType, rest] = subscription.split(\":\")\n const [languageCode, queryString] = rest?.split(\"?\") ?? []\n\n if (languageCode && isValidLanguageCode(languageCode)) {\n const options: Record<string, string | boolean> = {}\n\n // Parse query parameters if present\n if (queryString) {\n const params = new URLSearchParams(queryString)\n for (const [key, value] of params.entries()) {\n // Convert string values to boolean when appropriate\n if (value === \"true\") {\n options[key] = true\n } else if (value === \"false\") {\n options[key] = false\n } else {\n options[key] = value\n }\n }\n }\n\n return {\n type: StreamType.TRANSCRIPTION,\n baseType,\n transcribeLanguage: languageCode,\n options: Object.keys(options).length > 0 ? options : undefined,\n original: subscription,\n }\n }\n }\n\n // Handle translation format (translation:es-ES-to-en-US, translation:all-to-en-US, or with ?options)\n if (subscription.startsWith(`${StreamType.TRANSLATION}:`)) {\n const [baseType, rest] = subscription.split(\":\")\n const [languagePair, queryString] = rest?.split(\"?\") ?? []\n const [sourceLanguage, targetLanguage] = languagePair?.split(\"-to-\") ?? []\n\n // Check for \"all-to-LANG\" format (one-way translation from any language)\n const isAllToFormat = sourceLanguage === \"all\" && targetLanguage && isValidLanguageCode(targetLanguage)\n\n // Check for \"LANG-to-LANG\" format (two-way translation between specific languages)\n const isSpecificPairFormat =\n sourceLanguage && targetLanguage && isValidLanguageCode(sourceLanguage) && isValidLanguageCode(targetLanguage)\n\n if (isAllToFormat || isSpecificPairFormat) {\n const options: Record<string, string | boolean> = {}\n\n // Parse query parameters if present\n if (queryString) {\n const params = new URLSearchParams(queryString)\n for (const [key, value] of params.entries()) {\n // Convert string values to boolean when appropriate\n if (value === \"true\") {\n options[key] = true\n } else if (value === \"false\") {\n options[key] = false\n } else {\n options[key] = value\n }\n }\n }\n\n return {\n type: StreamType.TRANSLATION,\n baseType,\n transcribeLanguage: sourceLanguage, // \"all\" for all-to-LANG, or specific language code\n translateLanguage: targetLanguage,\n options: Object.keys(options).length > 0 ? options : undefined,\n original: subscription,\n }\n }\n }\n\n return null\n}\n\n/**\n * Create a transcription stream identifier for a specific language\n * Returns a type-safe stream type that can be used like a StreamType\n *\n * @param language Language code (e.g., \"en-US\") or \"auto\" for automatic detection\n * @param options Optional configuration options\n * @returns Typed stream identifier\n */\nexport function createTranscriptionStream(\n language: string,\n options?: {\n disableLanguageIdentification?: boolean\n hints?: string[]\n },\n): ExtendedStreamType {\n console.log(`🎤 Creating transcription stream for language: ${language}`)\n console.log(`🎤 Options: ${JSON.stringify(options)}`)\n\n // Defensively remove any query string from the language parameter\n const languageCode = language.split(\"?\")[0]\n\n if (languageCode !== \"auto\" && !isValidLanguageCode(languageCode)) {\n throw new Error(`Invalid language code: ${languageCode}`)\n }\n\n const base = `${StreamType.TRANSCRIPTION}:${languageCode}`\n const params = new URLSearchParams()\n\n if (options?.disableLanguageIdentification) {\n params.set(\"no-language-identification\", \"true\")\n }\n\n if (options?.hints && options.hints.length > 0) {\n params.set(\"hints\", options.hints.join(\",\"))\n }\n\n const queryString = params.toString()\n return queryString ? (`${base}?${queryString}` as ExtendedStreamType) : (base as ExtendedStreamType)\n}\n\n/**\n * Create a translation stream identifier for a language pair\n * Returns a type-safe stream type that can be used like a StreamType\n *\n * @param sourceLanguage Source language code (e.g., \"es-ES\") or \"all\" for one-way translation\n * @param targetLanguage Target language code (e.g., \"en-US\")\n * @param options Optional configuration options\n * @returns Typed stream identifier\n */\nexport function createTranslationStream(\n sourceLanguage: string,\n targetLanguage: string,\n options?: {disableLanguageIdentification?: boolean},\n): ExtendedStreamType {\n // Defensively remove any query string from the language parameters\n const cleanSourceLanguage = sourceLanguage.split(\"?\")[0]\n const cleanTargetLanguage = targetLanguage.split(\"?\")[0]\n\n // Support \"all\" as source for one-way translation (all languages → target)\n const isAllToFormat = cleanSourceLanguage === \"all\"\n\n if ((!isAllToFormat && !isValidLanguageCode(cleanSourceLanguage)) || !isValidLanguageCode(cleanTargetLanguage)) {\n throw new Error(`Invalid language code(s): ${cleanSourceLanguage}, ${cleanTargetLanguage}`)\n }\n const base = `${StreamType.TRANSLATION}:${cleanSourceLanguage}-to-${cleanTargetLanguage}`\n if (options?.disableLanguageIdentification) {\n return `${base}?no-language-identification=true` as ExtendedStreamType\n }\n return createLanguageStream(base)\n}\n\n/**\n * Parse a touch event subscription to extract gesture information\n * @param subscription Subscription string (e.g., \"touch_event:forward_swipe\")\n * @returns Gesture name or null if not a gesture-specific subscription\n */\nexport function parseTouchEventStream(subscription: ExtendedStreamType): string | null {\n if (typeof subscription !== \"string\") {\n return null\n }\n\n if (subscription.startsWith(`${StreamType.TOUCH_EVENT}:`)) {\n const [, gestureName] = subscription.split(\":\")\n const validGestures = [\n \"single_tap\",\n \"double_tap\",\n \"triple_tap\",\n \"long_press\",\n \"forward_swipe\",\n \"backward_swipe\",\n \"up_swipe\",\n \"down_swipe\",\n ]\n\n if (gestureName && validGestures.includes(gestureName)) {\n return gestureName\n }\n }\n\n return null\n}\n\n/**\n * Create a touch event subscription for a specific gesture\n * @param gesture Gesture name (e.g., \"forward_swipe\")\n * @returns Typed stream identifier\n */\nexport function createTouchEventStream(gesture: string): ExtendedStreamType {\n const validGestures = [\n \"single_tap\",\n \"double_tap\",\n \"triple_tap\",\n \"long_press\",\n \"forward_swipe\",\n \"backward_swipe\",\n \"up_swipe\",\n \"down_swipe\",\n ]\n\n if (!validGestures.includes(gesture)) {\n throw new Error(`Invalid gesture: ${gesture}`)\n }\n\n return `${StreamType.TOUCH_EVENT}:${gesture}` as ExtendedStreamType\n}\n\n/**\n * Create a universal translation stream identifier that translates from any language to target\n * This is useful when you want to support all languages translating to a single target\n * Returns a type-safe stream type that can be used like a StreamType\n *\n * Example: createUniversalTranslationStream('es-ES') creates \"translation:all-to-es-ES\"\n *\n * @param targetLanguage Target language code (e.g., \"es-ES\")\n * @param options Optional configuration options\n * @returns Typed stream identifier\n */\nexport function createUniversalTranslationStream(\n targetLanguage: string,\n options?: {disableLanguageIdentification?: boolean},\n): ExtendedStreamType {\n const cleanTargetLanguage = targetLanguage.split(\"?\")[0]\n\n if (!isValidLanguageCode(cleanTargetLanguage)) {\n throw new Error(`Invalid target language code: ${cleanTargetLanguage}`)\n }\n\n const base = `${StreamType.TRANSLATION}:all-to-${cleanTargetLanguage}`\n if (options?.disableLanguageIdentification) {\n return `${base}?no-language-identification=true` as ExtendedStreamType\n }\n return createLanguageStream(base)\n}\n\n/**\n * Check if a subscription is a valid stream type\n * This handles both enum-based StreamType values and language-specific stream formats\n *\n * @param subscription Subscription to validate\n * @returns True if valid, false otherwise\n */\nexport function isValidStreamType(subscription: ExtendedStreamType): boolean {\n // Check if it's a standard StreamType\n if (Object.values(StreamType).includes(subscription as StreamType)) {\n return true\n }\n\n // Check if it's a valid language-specific stream\n const languageStream = parseLanguageStream(subscription)\n if (languageStream !== null) {\n return true\n }\n\n // Check if it's a valid gesture-specific stream\n const gestureStream = parseTouchEventStream(subscription)\n if (gestureStream !== null) {\n return true\n }\n\n return false\n}\n\n/**\n * Helper function to check if a stream type is of a particular category\n * Works with both standard and language-specific stream types\n */\nexport function isStreamCategory(streamType: ExtendedStreamType, category: StreamCategory): boolean {\n const baseType = getBaseStreamType(streamType)\n return baseType ? STREAM_CATEGORIES[baseType] === category : false\n}\n\n/**\n * Helper function to get all stream types in a category\n */\nexport function getStreamTypesByCategory(category: StreamCategory): StreamType[] {\n return Object.entries(STREAM_CATEGORIES)\n .filter(([_, cat]) => cat === category)\n .map(([type]) => type as StreamType)\n}\n\n/**\n * Get the base StreamType for a subscription\n * Works with both standard StreamType values and language-specific formats\n *\n * @param subscription Subscription string or StreamType\n * @returns The base StreamType enum value\n */\nexport function getBaseStreamType(subscription: ExtendedStreamType): StreamType | null {\n // Check if it's already a standard StreamType\n if (Object.values(StreamType).includes(subscription as StreamType)) {\n return subscription as StreamType\n }\n\n // Check if it's a language-specific stream\n const languageStream = parseLanguageStream(subscription)\n if (languageStream) {\n return languageStream.type\n }\n\n // Check if it's a gesture-specific stream\n const gestureStream = parseTouchEventStream(subscription)\n if (gestureStream) {\n return StreamType.TOUCH_EVENT\n }\n\n return null\n}\n\n/**\n * Check if a stream is a language-specific stream\n */\nexport function isLanguageStream(subscription: ExtendedStreamType): boolean {\n return parseLanguageStream(subscription) !== null\n}\n\n/**\n * Get language information from a stream type\n * Returns null for regular stream types\n */\nexport function getLanguageInfo(subscription: ExtendedStreamType): LanguageStreamInfo | null {\n return parseLanguageStream(subscription)\n}\n\n// this is the blueprint for our new rich subscription object\n// it allows a developer to specify a rate for the location stream\nexport interface LocationStreamRequest {\n stream: \"location_stream\"\n rate: \"standard\" | \"high\" | \"realtime\" | \"tenMeters\" | \"hundredMeters\" | \"kilometer\" | \"threeKilometers\" | \"reduced\"\n}\n",
6
- "// src/message-types.ts\n\nimport { StreamType } from \"./streams\";\n\n/**\n * Types of messages from glasses to cloud\n */\nexport enum GlassesToCloudMessageType {\n // Control actions\n CONNECTION_INIT = \"connection_init\",\n REQUEST_SETTINGS = \"request_settings\",\n\n START_APP = StreamType.START_APP,\n STOP_APP = StreamType.STOP_APP,\n\n DASHBOARD_STATE = \"dashboard_state\",\n OPEN_DASHBOARD = StreamType.OPEN_DASHBOARD,\n\n // Mentra Live\n PHOTO_RESPONSE = StreamType.PHOTO_RESPONSE,\n\n // Local Transcription\n LOCAL_TRANSCRIPTION = \"local_transcription\",\n\n // RTMP streaming\n RTMP_STREAM_STATUS = StreamType.RTMP_STREAM_STATUS,\n KEEP_ALIVE_ACK = \"keep_alive_ack\",\n\n BUTTON_PRESS = StreamType.BUTTON_PRESS,\n HEAD_POSITION = StreamType.HEAD_POSITION,\n TOUCH_EVENT = StreamType.TOUCH_EVENT,\n GLASSES_BATTERY_UPDATE = StreamType.GLASSES_BATTERY_UPDATE,\n PHONE_BATTERY_UPDATE = StreamType.PHONE_BATTERY_UPDATE,\n GLASSES_CONNECTION_STATE = StreamType.GLASSES_CONNECTION_STATE,\n LOCATION_UPDATE = StreamType.LOCATION_UPDATE,\n\n // TODO(isaiah): Remove VPS_COORDINATES once confirmed we don't use this system.\n VPS_COORDINATES = StreamType.VPS_COORDINATES,\n VAD = StreamType.VAD,\n\n // TODO(isaiah): Remove PHONE_NOTIFICATION, and PHONE_NOTIFICATION_DISMISSED after moving to REST request.\n PHONE_NOTIFICATION = StreamType.PHONE_NOTIFICATION,\n PHONE_NOTIFICATION_DISMISSED = StreamType.PHONE_NOTIFICATION_DISMISSED,\n\n // TODO(isaiah): Remove CALENDAR_EVENT after moving to REST request.\n CALENDAR_EVENT = StreamType.CALENDAR_EVENT,\n MENTRAOS_SETTINGS_UPDATE_REQUEST = StreamType.MENTRAOS_SETTINGS_UPDATE_REQUEST,\n\n // TODO(isaiah): Remove CORE_STATUS_UPDATE after moving to REST request.\n CORE_STATUS_UPDATE = StreamType.CORE_STATUS_UPDATE,\n\n PHOTO_TAKEN = StreamType.PHOTO_TAKEN,\n AUDIO_PLAY_RESPONSE = \"audio_play_response\",\n\n // RGB LED control\n RGB_LED_CONTROL_RESPONSE = \"rgb_led_control_response\",\n\n // LiveKit handshake\n LIVEKIT_INIT = \"livekit_init\",\n\n // UDP audio\n UDP_REGISTER = \"udp_register\",\n UDP_UNREGISTER = \"udp_unregister\",\n}\n\n/**\n * Types of messages from cloud to glasses\n */\nexport enum CloudToGlassesMessageType {\n // Responses\n CONNECTION_ACK = \"connection_ack\",\n CONNECTION_ERROR = \"connection_error\",\n AUTH_ERROR = \"auth_error\",\n\n // Updates\n DISPLAY_EVENT = \"display_event\",\n APP_STATE_CHANGE = \"app_state_change\",\n MICROPHONE_STATE_CHANGE = \"microphone_state_change\",\n SETTINGS_UPDATE = \"settings_update\",\n\n // Requests\n PHOTO_REQUEST = \"photo_request\",\n AUDIO_PLAY_REQUEST = \"audio_play_request\",\n AUDIO_STOP_REQUEST = \"audio_stop_request\",\n RGB_LED_CONTROL = \"rgb_led_control\",\n SHOW_WIFI_SETUP = \"show_wifi_setup\",\n\n // RTMP streaming\n START_RTMP_STREAM = \"start_rtmp_stream\",\n STOP_RTMP_STREAM = \"stop_rtmp_stream\",\n KEEP_RTMP_STREAM_ALIVE = \"keep_rtmp_stream_alive\",\n\n // Dashboard updates\n DASHBOARD_MODE_CHANGE = \"dashboard_mode_change\",\n DASHBOARD_ALWAYS_ON_CHANGE = \"dashboard_always_on_change\",\n\n // Location Service\n SET_LOCATION_TIER = \"set_location_tier\",\n REQUEST_SINGLE_LOCATION = \"request_single_location\",\n\n WEBSOCKET_ERROR = \"websocket_error\",\n\n // LiveKit info (URL, room, token)\n LIVEKIT_INFO = \"livekit_info\",\n\n // UDP audio\n UDP_PING_ACK = \"udp_ping_ack\",\n}\n\n/**\n * Types of messages from Apps to cloud\n */\nexport enum AppToCloudMessageType {\n // Commands\n CONNECTION_INIT = \"tpa_connection_init\",\n SUBSCRIPTION_UPDATE = \"subscription_update\",\n LOCATION_POLL_REQUEST = \"location_poll_request\",\n\n // Requests\n DISPLAY_REQUEST = \"display_event\",\n PHOTO_REQUEST = \"photo_request\",\n AUDIO_PLAY_REQUEST = \"audio_play_request\",\n AUDIO_STOP_REQUEST = \"audio_stop_request\",\n RGB_LED_CONTROL = \"rgb_led_control\",\n REQUEST_WIFI_SETUP = \"request_wifi_setup\",\n\n // RTMP streaming\n RTMP_STREAM_REQUEST = \"rtmp_stream_request\",\n RTMP_STREAM_STOP = \"rtmp_stream_stop\",\n\n // Managed RTMP streaming\n MANAGED_STREAM_REQUEST = \"managed_stream_request\",\n MANAGED_STREAM_STOP = \"managed_stream_stop\",\n\n // Stream status check (both managed and unmanaged)\n STREAM_STATUS_CHECK = \"stream_status_check\",\n\n // Dashboard requests\n DASHBOARD_CONTENT_UPDATE = \"dashboard_content_update\",\n DASHBOARD_MODE_CHANGE = \"dashboard_mode_change\",\n DASHBOARD_SYSTEM_UPDATE = \"dashboard_system_update\",\n\n // TODO(isaiah): Remove after confirming not in use.\n // App-to-App Communication\n APP_BROADCAST_MESSAGE = \"app_broadcast_message\",\n APP_DIRECT_MESSAGE = \"app_direct_message\",\n APP_USER_DISCOVERY = \"app_user_discovery\",\n APP_ROOM_JOIN = \"app_room_join\",\n APP_ROOM_LEAVE = \"app_room_leave\",\n\n // Session lifecycle\n OWNERSHIP_RELEASE = \"ownership_release\",\n}\n\n/**\n * Types of messages from cloud to Apps\n */\nexport enum CloudToAppMessageType {\n // Responses\n CONNECTION_ACK = \"tpa_connection_ack\",\n CONNECTION_ERROR = \"tpa_connection_error\",\n\n // Updates\n APP_STOPPED = \"app_stopped\",\n SETTINGS_UPDATE = \"settings_update\",\n CAPABILITIES_UPDATE = \"capabilities_update\",\n DEVICE_STATE_UPDATE = \"device_state_update\",\n\n // Dashboard updates\n DASHBOARD_MODE_CHANGED = \"dashboard_mode_changed\",\n DASHBOARD_ALWAYS_ON_CHANGED = \"dashboard_always_on_changed\",\n\n // Stream data\n DATA_STREAM = \"data_stream\",\n\n // Media responses\n PHOTO_RESPONSE = \"photo_response\",\n AUDIO_PLAY_RESPONSE = \"audio_play_response\",\n RGB_LED_CONTROL_RESPONSE = \"rgb_led_control_response\",\n RTMP_STREAM_STATUS = \"rtmp_stream_status\",\n MANAGED_STREAM_STATUS = \"managed_stream_status\",\n STREAM_STATUS_CHECK_RESPONSE = \"stream_status_check_response\",\n\n WEBSOCKET_ERROR = \"websocket_error\",\n\n // Permissions\n PERMISSION_ERROR = \"permission_error\",\n\n // General purpose messaging\n CUSTOM_MESSAGE = \"custom_message\",\n\n // TODO(isaiah): Remove after confirming not in use.\n // App-to-App Communication Responses\n APP_MESSAGE_RECEIVED = \"app_message_received\",\n APP_USER_JOINED = \"app_user_joined\",\n APP_USER_LEFT = \"app_user_left\",\n APP_ROOM_UPDATED = \"app_room_updated\",\n APP_DIRECT_MESSAGE_RESPONSE = \"app_direct_message_response\",\n}\n\n/**\n * Control action message types (subset of GlassesToCloudMessageType)\n */\nexport const ControlActionTypes = [\n GlassesToCloudMessageType.CONNECTION_INIT,\n GlassesToCloudMessageType.START_APP,\n GlassesToCloudMessageType.STOP_APP,\n GlassesToCloudMessageType.DASHBOARD_STATE,\n GlassesToCloudMessageType.OPEN_DASHBOARD,\n] as const;\n\n/**\n * Event message types (subset of GlassesToCloudMessageType)\n */\nexport const EventTypes = [\n GlassesToCloudMessageType.BUTTON_PRESS,\n GlassesToCloudMessageType.HEAD_POSITION,\n GlassesToCloudMessageType.GLASSES_BATTERY_UPDATE,\n GlassesToCloudMessageType.PHONE_BATTERY_UPDATE,\n GlassesToCloudMessageType.GLASSES_CONNECTION_STATE,\n GlassesToCloudMessageType.LOCATION_UPDATE,\n GlassesToCloudMessageType.VPS_COORDINATES,\n GlassesToCloudMessageType.VAD,\n GlassesToCloudMessageType.PHONE_NOTIFICATION,\n GlassesToCloudMessageType.PHONE_NOTIFICATION_DISMISSED,\n GlassesToCloudMessageType.CALENDAR_EVENT,\n GlassesToCloudMessageType.MENTRAOS_SETTINGS_UPDATE_REQUEST,\n GlassesToCloudMessageType.CORE_STATUS_UPDATE,\n GlassesToCloudMessageType.LOCAL_TRANSCRIPTION,\n] as const;\n\n/**\n * Response message types (subset of CloudToGlassesMessageType)\n */\nexport const ResponseTypes = [\n CloudToGlassesMessageType.CONNECTION_ACK,\n CloudToGlassesMessageType.CONNECTION_ERROR,\n CloudToGlassesMessageType.AUTH_ERROR,\n] as const;\n\n/**\n * Update message types (subset of CloudToGlassesMessageType)\n */\nexport const UpdateTypes = [\n CloudToGlassesMessageType.DISPLAY_EVENT,\n CloudToGlassesMessageType.APP_STATE_CHANGE,\n CloudToGlassesMessageType.MICROPHONE_STATE_CHANGE,\n CloudToGlassesMessageType.PHOTO_REQUEST,\n CloudToGlassesMessageType.AUDIO_PLAY_REQUEST,\n CloudToGlassesMessageType.AUDIO_STOP_REQUEST,\n CloudToGlassesMessageType.RGB_LED_CONTROL,\n CloudToGlassesMessageType.SETTINGS_UPDATE,\n CloudToGlassesMessageType.DASHBOARD_MODE_CHANGE,\n CloudToGlassesMessageType.DASHBOARD_ALWAYS_ON_CHANGE,\n CloudToGlassesMessageType.START_RTMP_STREAM,\n CloudToGlassesMessageType.STOP_RTMP_STREAM,\n CloudToGlassesMessageType.KEEP_RTMP_STREAM_ALIVE,\n CloudToGlassesMessageType.LIVEKIT_INFO,\n] as const;\n\n/**\n * Dashboard message types\n */\nexport const DashboardMessageTypes = [\n AppToCloudMessageType.DASHBOARD_CONTENT_UPDATE,\n AppToCloudMessageType.DASHBOARD_MODE_CHANGE,\n AppToCloudMessageType.DASHBOARD_SYSTEM_UPDATE,\n CloudToAppMessageType.DASHBOARD_MODE_CHANGED,\n CloudToAppMessageType.DASHBOARD_ALWAYS_ON_CHANGED,\n] as const;\n",
5
+ "/**\n * Clean Transport for MentraOS SDK\n *\n * A lightweight pino-compatible writable stream that formats log output as\n * single-line colored messages. This is the default console transport.\n *\n * Output format:\n * MentraOS ✓ App server running on port 7010\n * MentraOS ⚠ Connection lost, reconnecting (2/3)...\n * MentraOS ✗ Invalid API key\n *\n * Color scheme:\n * - \"MentraOS\" prefix: dim gray (always present, visually skippable)\n * - ✓ (info): green — success events\n * - ⚠ (warn): yellow — warnings, reconnecting, deprecations\n * - ✗ (error/fatal): red — failures requiring action\n * - · (debug/trace): dim — internal details (only visible at debug level)\n * - Message text: default terminal color\n *\n * SDK internal log filtering:\n * Logs tagged with `_sdk: true` (from managers and internal subsystems) are\n * only shown in the terminal at warn level and above. This keeps the developer's\n * terminal clean — their own `session.logger.info(...)` calls always show,\n * while internal SDK chatter (subscription updates, ping, handler registration)\n * is hidden unless the developer sets MENTRA_VERBOSE=true.\n *\n * BetterStack always receives ALL logs regardless of the _sdk tag — the\n * filtering only applies to the terminal/clean transport.\n *\n * All structured context fields (app, packageName, service, userId, err, etc.)\n * are intentionally hidden — they're only visible in verbose mode or in BetterStack.\n * The `msg` field is the only thing shown to the developer.\n */\n\nimport { Writable } from \"stream\";\nimport chalk from \"chalk\";\n\n/**\n * Pino log level numbers mapped to display symbols and colors.\n * See: https://getpino.io/#/docs/api?id=loggerlevel-string-gettersetter\n */\nconst LEVEL_CONFIG: Record<number, { symbol: string; color: (s: string) => string }> = {\n 10: { symbol: \"·\", color: chalk.dim }, // trace\n 20: { symbol: \"·\", color: chalk.dim }, // debug\n 30: { symbol: \"✓\", color: chalk.green }, // info\n 40: { symbol: \"⚠\", color: chalk.yellow }, // warn\n 50: { symbol: \"✗\", color: chalk.red }, // error\n 60: { symbol: \"✗\", color: chalk.red }, // fatal\n};\n\n/** Whether verbose mode is enabled — when true, SDK internal logs are shown in terminal. */\nconst VERBOSE = process.env.MENTRA_VERBOSE === \"true\" || process.env.MENTRA_VERBOSE === \"1\";\n\n/** Pino level number for warn — SDK internal logs below this are hidden from terminal (unless verbose). */\nconst WARN_LEVEL = 40;\n\nconst DEFAULT_LEVEL_CONFIG = { symbol: \"·\", color: chalk.dim };\n\nconst PREFIX = chalk.dim(\"MentraOS\");\n\n/**\n * Creates a writable stream that formats pino JSON log lines as single-line\n * colored terminal output.\n *\n * Pino writes newline-delimited JSON to this stream. Each line is parsed,\n * and only the `level` and `msg` fields are used for formatting. Everything\n * else (structured context, error objects, timestamps) is ignored — those\n * fields still flow to BetterStack via the separate @logtail/pino transport.\n *\n * Output is written to `process.stderr` by convention (keeps stdout clean\n * for program output if someone pipes it).\n *\n * @returns A Node.js Writable stream compatible with pino's multistream.\n */\nexport function createCleanStream(): Writable {\n return new Writable({\n write(chunk: Buffer, _encoding: string, callback: () => void) {\n try {\n const line = chunk.toString().trim();\n if (!line) {\n callback();\n return;\n }\n\n const obj = JSON.parse(line);\n const level: number = obj.level ?? 30;\n const msg: string = obj.msg ?? \"\";\n\n // Skip empty messages — these are pino internal events or structured-only logs\n // that have no human-readable message component.\n if (!msg) {\n callback();\n return;\n }\n\n // SDK internal log filtering:\n // Logs from managers and internal subsystems are tagged with _sdk: true.\n // In the terminal, only show these at warn level and above.\n // This keeps the developer's terminal clean — their own session.logger.info()\n // calls always show, while SDK plumbing noise is hidden.\n // BetterStack still gets everything (it's a separate transport).\n if (obj._sdk === true && level < WARN_LEVEL && !VERBOSE) {\n callback();\n return;\n }\n\n const config = LEVEL_CONFIG[level] ?? DEFAULT_LEVEL_CONFIG;\n const symbol = config.color(config.symbol);\n const formatted = `${PREFIX} ${symbol} ${msg}\\n`;\n\n process.stderr.write(formatted);\n } catch {\n // If JSON parse fails (shouldn't happen with pino), write the raw chunk\n // so the developer still sees something rather than silent swallowing.\n process.stderr.write(chunk);\n }\n callback();\n },\n });\n}\n",
6
+ "/**\n * MentraOS SDK Logger\n *\n * Factory-based logger that supports two modes:\n *\n * **Clean mode** (default): Single-line colored output via the clean transport.\n * MentraOS ✓ App server running on port 7010\n * MentraOS Invalid API key\n *\n * **Verbose mode**: Full pino-pretty structured output (today's behavior).\n * Activated via `verbose: true` in config or `MENTRA_VERBOSE=true` env var.\n *\n * The BetterStack transport always runs at debug level when BETTERSTACK_SOURCE_TOKEN\n * is set, regardless of the console transport level. This is intentional — it's\n * an undocumented internal feature for Mentra's own apps.\n *\n * Resolution order for log level:\n * 1. MENTRA_VERBOSE=true → verbose mode, debug level\n * 2. MENTRA_LOG_LEVEL env var → sets level (debug implies verbose)\n * 3. config.verbose: true → verbose mode, debug level\n * 4. config.logLevel → sets level\n * 5. Default → info level, clean mode\n */\n\nimport pino from \"pino\";\nimport type { Logger, Level } from \"pino\";\nimport { Writable } from \"stream\";\nimport { createCleanStream } from \"./clean-transport\";\n\n// ─── Public Types ────────────────────────────────────────────────────────────\n\nexport type MentraLogLevel = \"none\" | \"error\" | \"warn\" | \"info\" | \"debug\";\n\nexport interface LoggerConfig {\n /**\n * SDK console log level. Default: 'info'.\n * - 'none': Suppress all SDK console output\n * - 'error': Only errors\n * - 'warn': Errors + warnings\n * - 'info': Errors + warnings + lifecycle events (default)\n * - 'debug': Everything (verbose structured output)\n *\n * Can be overridden with MENTRA_LOG_LEVEL env var.\n */\n logLevel?: MentraLogLevel;\n\n /**\n * Enable verbose internal logging (full pino-pretty structured output).\n * Useful when debugging SDK issues Mentra support may ask you to enable this.\n * Can also be enabled with MENTRA_VERBOSE=true env var.\n * Default: false\n */\n verbose?: boolean;\n}\n\n// ─── Internal Helpers ────────────────────────────────────────────────────────\n\n/** Map our public level names to pino level names. 'none' → pino 'silent'. */\nfunction toPinoLevel(level: MentraLogLevel): string {\n return level === \"none\" ? \"silent\" : level;\n}\n\nconst VALID_LEVELS: MentraLogLevel[] = [\"none\", \"error\", \"warn\", \"info\", \"debug\"];\n\n/**\n * Resolve the effective logging configuration from env vars + passed config.\n * Env vars take precedence over programmatic config so that Mentra support\n * can tell a developer \"set MENTRA_VERBOSE=true and send us the output\"\n * without requiring code changes.\n */\nfunction resolveConfig(config?: LoggerConfig): { pinoLevel: string; verbose: boolean } {\n const envVerbose = process.env.MENTRA_VERBOSE === \"true\" || process.env.MENTRA_VERBOSE === \"1\";\n const envLevel = process.env.MENTRA_LOG_LEVEL as MentraLogLevel | undefined;\n\n // 1. MENTRA_VERBOSE takes highest precedence\n if (envVerbose) {\n return { pinoLevel: \"debug\", verbose: true };\n }\n\n // 2. MENTRA_LOG_LEVEL overrides config\n if (envLevel && VALID_LEVELS.includes(envLevel)) {\n const pinoLevel = toPinoLevel(envLevel);\n return { pinoLevel, verbose: envLevel === \"debug\" };\n }\n\n // 3. Config verbose\n if (config?.verbose) {\n return { pinoLevel: \"debug\", verbose: true };\n }\n\n // 4. Config logLevel\n if (config?.logLevel && VALID_LEVELS.includes(config.logLevel)) {\n const pinoLevel = toPinoLevel(config.logLevel);\n return { pinoLevel, verbose: config.logLevel === \"debug\" };\n }\n\n // 5. Default: info level, clean mode\n // Developers expect session.logger.info(\"...\") to be visible in their terminal.\n // The log level system controls SDK internal noise (debug/trace), not developer logs.\n return { pinoLevel: \"info\", verbose: false };\n}\n\n/** A no-op writable stream for when all console output is suppressed. */\nfunction createNoopStream(): Writable {\n return new Writable({\n write(_chunk: unknown, _encoding: string, callback: () => void) {\n callback();\n },\n });\n}\n\n/**\n * Attempt to create the pino-pretty transport for verbose mode.\n * Returns null if pino-pretty is not installed (it's an optional dependency).\n */\nfunction tryCreatePrettyTransport(): pino.StreamEntry | null {\n try {\n const prettyTransport = pino.transport({\n target: \"pino-pretty\",\n options: {\n colorize: true,\n translateTime: \"SYS:standard\",\n ignore: \"pid,hostname,env,module,server\",\n messageFormat: \"{msg}\",\n errorProps: \"*\",\n },\n });\n return { stream: prettyTransport, level: \"debug\" } as pino.StreamEntry;\n } catch {\n // pino-pretty not installed caller should fall back\n return null;\n }\n}\n\n/**\n * Attempt to create the BetterStack (@logtail/pino) transport.\n * Returns null if the token is not set or the transport fails to load.\n *\n * The BetterStack transport always runs at debug level regardless of the\n * console transport level. This is intentional for Mentra's internal apps.\n * Third-party devs don't set BETTERSTACK_SOURCE_TOKEN so they never see this.\n */\nfunction tryCreateBetterStackTransport(): pino.StreamEntry | null {\n const token = process.env.BETTERSTACK_SOURCE_TOKEN;\n if (!token) return null;\n\n const endpoint = process.env.BETTERSTACK_ENDPOINT || \"https://s1311181.eu-nbg-2.betterstackdata.com\";\n\n try {\n const transport = pino.transport({\n target: \"@logtail/pino\",\n options: {\n sourceToken: token,\n options: { endpoint },\n },\n });\n return { stream: transport, level: \"debug\" } as pino.StreamEntry;\n } catch {\n // Silently skip — don't pollute the dev terminal with BetterStack setup errors.\n return null;\n }\n}\n\n// ─── Public API ──────────────────────────────────────────────────────────────\n\n/**\n * Create a configured pino Logger instance.\n *\n * This is the primary entry point. Each AppServer creates one root logger\n * via this function and then derives child loggers for sessions and modules.\n *\n * @param config - Optional logging configuration. If omitted, defaults to\n * warn-level clean output (env vars can still override).\n * @returns A pino Logger instance with the appropriate transports.\n *\n * @example\n * ```typescript\n * // Default: clean output at warn level\n * const logger = createLogger();\n *\n * // Verbose for debugging\n * const logger = createLogger({ verbose: true });\n *\n * // Quiet — errors only\n * const logger = createLogger({ logLevel: 'error' });\n *\n * // Silent suppress all console output (BetterStack still receives logs)\n * const logger = createLogger({ logLevel: 'none' });\n * ```\n */\nexport function createLogger(config?: LoggerConfig): Logger {\n const { pinoLevel, verbose } = resolveConfig(config);\n\n const NODE_ENV = process.env.NODE_ENV || \"development\";\n const PORTER_APP_NAME = process.env.PORTER_APP_NAME || \"cloud-local\";\n\n const streams: pino.StreamEntry[] = [];\n\n // ── Console transport ──────────────────────────────────────────────────\n if (pinoLevel !== \"silent\") {\n if (verbose) {\n // Verbose mode: try pino-pretty, fall back to JSON on stdout\n const pretty = tryCreatePrettyTransport();\n if (pretty) {\n pretty.level = pinoLevel as Level;\n streams.push(pretty);\n } else {\n // pino-pretty not available — fall back to raw JSON on stdout\n // and emit a one-time notice so the dev knows why output looks different\n process.stderr.write(\n \"[MentraOS] pino-pretty not installed verbose output will be JSON. \" +\n \"Install it with: bun add -d pino-pretty\\n\",\n );\n streams.push({ stream: process.stdout, level: pinoLevel as Level });\n }\n } else {\n // Clean mode: single-line colored output\n streams.push({ stream: createCleanStream(), level: pinoLevel as Level });\n }\n }\n\n // ── BetterStack transport ──────────────────────────────────────────────\n // Always at debug level, independent of the console transport.\n const betterStack = tryCreateBetterStackTransport();\n if (betterStack) {\n streams.push(betterStack);\n }\n\n // ── Safety net ─────────────────────────────────────────────────────────\n // If no streams were added (console=silent, no BetterStack), add a no-op\n // stream to prevent pino from defaulting to stdout.\n if (streams.length === 0) {\n streams.push({ stream: createNoopStream(), level: \"silent\" as Level });\n }\n\n const multistream = pino.multistream(streams);\n\n // The pino instance level is set to the absolute minimum ('debug') so that\n // individual streams can independently control their own minimum levels.\n // For example: console at 'warn' + BetterStack at 'debug' from the same logger.\n return pino(\n {\n level: \"debug\",\n base: {\n env: NODE_ENV,\n server: PORTER_APP_NAME,\n },\n timestamp: pino.stdTimeFunctions.isoTime,\n },\n multistream,\n );\n}\n\n// ─── Default Export (backward compat) ────────────────────────────────────────\n//\n// Some files (e.g., settings.ts) import the root logger directly:\n// import { logger } from \"../../logging/logger\";\n//\n// This default instance preserves the current behavior (verbose, NODE_ENV-based\n// level) so those files don't break during incremental migration. Once all\n// consumers receive the logger via DI, this can be removed.\n\nexport const logger = createLogger({\n logLevel: (process.env.NODE_ENV === \"production\" ? \"info\" : \"debug\") as MentraLogLevel,\n verbose: true,\n});\n\nexport default logger;\n",
7
+ "// src/streams.ts\n\n/**\n * Types of streams that Apps can subscribe to\n *\n * These are events and data that Apps can receive from the cloud.\n * Not all message types can be subscribed to as streams.\n */\nexport enum StreamType {\n // Hardware streams\n BUTTON_PRESS = \"button_press\",\n HEAD_POSITION = \"head_position\",\n TOUCH_EVENT = \"touch_event\",\n GLASSES_BATTERY_UPDATE = \"glasses_battery_update\",\n PHONE_BATTERY_UPDATE = \"phone_battery_update\",\n GLASSES_CONNECTION_STATE = \"glasses_connection_state\",\n LOCATION_UPDATE = \"location_update\",\n LOCATION_STREAM = \"location_stream\",\n VPS_COORDINATES = \"vps_coordinates\",\n\n // Audio streams\n TRANSCRIPTION = \"transcription\",\n TRANSLATION = \"translation\",\n VAD = \"VAD\",\n AUDIO_CHUNK = \"audio_chunk\",\n\n // Phone streams\n PHONE_NOTIFICATION = \"phone_notification\",\n PHONE_NOTIFICATION_DISMISSED = \"phone_notification_dismissed\",\n CALENDAR_EVENT = \"calendar_event\",\n\n // System streams\n START_APP = \"start_app\",\n STOP_APP = \"stop_app\",\n OPEN_DASHBOARD = \"open_dashboard\",\n CORE_STATUS_UPDATE = \"core_status_update\",\n\n // Video streams\n VIDEO = \"video\",\n PHOTO_REQUEST = \"photo_request\",\n PHOTO_RESPONSE = \"photo_response\",\n STREAM_STATUS = \"stream_status\",\n MANAGED_STREAM_STATUS = \"managed_stream_status\",\n\n // Special subscription types\n ALL = \"all\",\n WILDCARD = \"*\",\n\n // New stream type\n MENTRAOS_SETTINGS_UPDATE_REQUEST = \"settings_update_request\",\n /**\n * @deprecated Use the settings system (mentraosSettings) instead.\n * This stream type was used for datetime updates but is no longer needed.\n * Will be removed in a future version.\n */\n CUSTOM_MESSAGE = \"custom_message\",\n PHOTO_TAKEN = \"photo_taken\",\n}\n\n/**\n * Extended StreamType to support language-specific streams\n * This allows us to treat language-specific strings as StreamType values\n */\nexport type ExtendedStreamType = StreamType | string;\n\n/**\n * Categories of stream data\n */\nexport enum StreamCategory {\n /** Data from hardware sensors */\n HARDWARE = \"hardware\",\n\n /** Audio processing results */\n AUDIO = \"audio\",\n\n /** Phone-related events */\n PHONE = \"phone\",\n\n /** System-level events */\n SYSTEM = \"system\",\n}\n\n/**\n * Map of stream categories for each stream type\n */\nexport const STREAM_CATEGORIES: Record<StreamType, StreamCategory> = {\n [StreamType.BUTTON_PRESS]: StreamCategory.HARDWARE,\n [StreamType.HEAD_POSITION]: StreamCategory.HARDWARE,\n [StreamType.TOUCH_EVENT]: StreamCategory.HARDWARE,\n [StreamType.GLASSES_BATTERY_UPDATE]: StreamCategory.HARDWARE,\n [StreamType.PHONE_BATTERY_UPDATE]: StreamCategory.HARDWARE,\n [StreamType.GLASSES_CONNECTION_STATE]: StreamCategory.HARDWARE,\n [StreamType.LOCATION_UPDATE]: StreamCategory.HARDWARE,\n [StreamType.LOCATION_STREAM]: StreamCategory.HARDWARE,\n [StreamType.VPS_COORDINATES]: StreamCategory.HARDWARE,\n\n [StreamType.TRANSCRIPTION]: StreamCategory.AUDIO,\n [StreamType.TRANSLATION]: StreamCategory.AUDIO,\n [StreamType.VAD]: StreamCategory.AUDIO,\n [StreamType.AUDIO_CHUNK]: StreamCategory.AUDIO,\n\n [StreamType.PHONE_NOTIFICATION]: StreamCategory.PHONE,\n [StreamType.PHONE_NOTIFICATION_DISMISSED]: StreamCategory.PHONE,\n [StreamType.CALENDAR_EVENT]: StreamCategory.PHONE,\n [StreamType.START_APP]: StreamCategory.SYSTEM,\n [StreamType.STOP_APP]: StreamCategory.SYSTEM,\n [StreamType.OPEN_DASHBOARD]: StreamCategory.SYSTEM,\n [StreamType.CORE_STATUS_UPDATE]: StreamCategory.SYSTEM,\n\n [StreamType.VIDEO]: StreamCategory.HARDWARE,\n [StreamType.PHOTO_REQUEST]: StreamCategory.HARDWARE,\n [StreamType.PHOTO_RESPONSE]: StreamCategory.HARDWARE,\n [StreamType.STREAM_STATUS]: StreamCategory.HARDWARE,\n [StreamType.MANAGED_STREAM_STATUS]: StreamCategory.HARDWARE,\n [StreamType.ALL]: StreamCategory.SYSTEM,\n [StreamType.WILDCARD]: StreamCategory.SYSTEM,\n\n [StreamType.MENTRAOS_SETTINGS_UPDATE_REQUEST]: StreamCategory.SYSTEM,\n [StreamType.CUSTOM_MESSAGE]: StreamCategory.SYSTEM,\n [StreamType.PHOTO_TAKEN]: StreamCategory.HARDWARE,\n};\n\n/**\n * Branded type for TypeScript to recognize language-specific stream types\n * This helps maintain type safety when using language-specific streams\n */\nexport type LanguageStreamType<T extends string> = T & {\n __languageStreamBrand: never;\n};\n\n/**\n * Create a language-branded stream type\n * This is a type helper to ensure type safety for language-specific streams\n */\nfunction createLanguageStream<T extends string>(type: T): LanguageStreamType<T> {\n return type as LanguageStreamType<T>;\n}\n\n/**\n * Structure of a parsed language stream subscription\n */\nexport interface LanguageStreamInfo {\n type: StreamType; // Base stream type (e.g., TRANSCRIPTION)\n baseType: string; // String representation of base type (e.g., \"transcription\")\n transcribeLanguage: string; // Source language code (e.g., \"en-US\")\n translateLanguage?: string; // Target language code for translations (e.g., \"es-ES\")\n options?: Record<string, string | boolean>; // Query parameters/options\n original: ExtendedStreamType; // Original subscription string\n}\n\n/**\n * Check if a string is a valid language code\n * Simple validation for language code format: xx-XX (e.g., en-US)\n */\nexport function isValidLanguageCode(code: string): boolean {\n return /^[a-z]{2,3}-[A-Z]{2}$/.test(code);\n}\n\n/**\n * Parse a subscription string to extract language information\n *\n * @param subscription Subscription string (e.g., \"transcription:en-US\" or \"translation:es-ES-to-en-US\" or \"transcription:en-US?no-language-identification=true\")\n * @returns Parsed language stream info or null if not a language-specific subscription\n */\nexport function parseLanguageStream(subscription: ExtendedStreamType): LanguageStreamInfo | null {\n // console.log(`🎤 Parsing language stream: ${subscription}`);\n\n if (typeof subscription !== \"string\") {\n return null;\n }\n\n // Handle transcription format (transcription:en-US, transcription:auto, or with ?options)\n if (subscription.startsWith(`${StreamType.TRANSCRIPTION}:`)) {\n const [baseType, rest] = subscription.split(\":\");\n const [languageCode, queryString] = rest?.split(\"?\") ?? [];\n\n if (languageCode && (languageCode === \"auto\" || isValidLanguageCode(languageCode))) {\n const options: Record<string, string | boolean> = {};\n\n // Parse query parameters if present\n if (queryString) {\n const params = new URLSearchParams(queryString);\n for (const [key, value] of params.entries()) {\n // Convert string values to boolean when appropriate\n if (value === \"true\") {\n options[key] = true;\n } else if (value === \"false\") {\n options[key] = false;\n } else {\n options[key] = value;\n }\n }\n }\n\n return {\n type: StreamType.TRANSCRIPTION,\n baseType,\n transcribeLanguage: languageCode,\n options: Object.keys(options).length > 0 ? options : undefined,\n original: subscription,\n };\n }\n }\n\n // Handle translation format (translation:es-ES-to-en-US, translation:all-to-en-US, or with ?options)\n if (subscription.startsWith(`${StreamType.TRANSLATION}:`)) {\n const [baseType, rest] = subscription.split(\":\");\n const [languagePair, queryString] = rest?.split(\"?\") ?? [];\n const [sourceLanguage, targetLanguage] = languagePair?.split(\"-to-\") ?? [];\n\n // Check for \"all-to-LANG\" format (one-way translation from any language)\n const isAllToFormat = sourceLanguage === \"all\" && targetLanguage && isValidLanguageCode(targetLanguage);\n\n // Check for \"LANG-to-LANG\" format (two-way translation between specific languages)\n const isSpecificPairFormat =\n sourceLanguage && targetLanguage && isValidLanguageCode(sourceLanguage) && isValidLanguageCode(targetLanguage);\n\n if (isAllToFormat || isSpecificPairFormat) {\n const options: Record<string, string | boolean> = {};\n\n // Parse query parameters if present\n if (queryString) {\n const params = new URLSearchParams(queryString);\n for (const [key, value] of params.entries()) {\n // Convert string values to boolean when appropriate\n if (value === \"true\") {\n options[key] = true;\n } else if (value === \"false\") {\n options[key] = false;\n } else {\n options[key] = value;\n }\n }\n }\n\n return {\n type: StreamType.TRANSLATION,\n baseType,\n transcribeLanguage: sourceLanguage, // \"all\" for all-to-LANG, or specific language code\n translateLanguage: targetLanguage,\n options: Object.keys(options).length > 0 ? options : undefined,\n original: subscription,\n };\n }\n }\n\n return null;\n}\n\n/**\n * Create a transcription stream identifier for a specific language\n * Returns a type-safe stream type that can be used like a StreamType\n *\n * @param language Language code (e.g., \"en-US\") or \"auto\" for automatic detection\n * @param options Optional configuration options\n * @returns Typed stream identifier\n */\nexport function createTranscriptionStream(\n language: string,\n options?: {\n disableLanguageIdentification?: boolean;\n hints?: string[];\n },\n): ExtendedStreamType {\n console.log(`🎤 Creating transcription stream for language: ${language}`);\n console.log(`🎤 Options: ${JSON.stringify(options)}`);\n\n // Defensively remove any query string from the language parameter\n const languageCode = language.split(\"?\")[0];\n\n if (languageCode !== \"auto\" && !isValidLanguageCode(languageCode)) {\n throw new Error(`Invalid language code: ${languageCode}`);\n }\n\n const base = `${StreamType.TRANSCRIPTION}:${languageCode}`;\n const params = new URLSearchParams();\n\n if (options?.disableLanguageIdentification) {\n params.set(\"no-language-identification\", \"true\");\n }\n\n if (options?.hints && options.hints.length > 0) {\n params.set(\"hints\", options.hints.join(\",\"));\n }\n\n const queryString = params.toString();\n return queryString ? (`${base}?${queryString}` as ExtendedStreamType) : (base as ExtendedStreamType);\n}\n\n/**\n * Create a translation stream identifier for a language pair\n * Returns a type-safe stream type that can be used like a StreamType\n *\n * @param sourceLanguage Source language code (e.g., \"es-ES\") or \"all\" for one-way translation\n * @param targetLanguage Target language code (e.g., \"en-US\")\n * @param options Optional configuration options\n * @returns Typed stream identifier\n */\nexport function createTranslationStream(\n sourceLanguage: string,\n targetLanguage: string,\n options?: { disableLanguageIdentification?: boolean },\n): ExtendedStreamType {\n // Defensively remove any query string from the language parameters\n const cleanSourceLanguage = sourceLanguage.split(\"?\")[0];\n const cleanTargetLanguage = targetLanguage.split(\"?\")[0];\n\n // Support \"all\" as source for one-way translation (all languages → target)\n const isAllToFormat = cleanSourceLanguage === \"all\";\n\n if ((!isAllToFormat && !isValidLanguageCode(cleanSourceLanguage)) || !isValidLanguageCode(cleanTargetLanguage)) {\n throw new Error(`Invalid language code(s): ${cleanSourceLanguage}, ${cleanTargetLanguage}`);\n }\n const base = `${StreamType.TRANSLATION}:${cleanSourceLanguage}-to-${cleanTargetLanguage}`;\n if (options?.disableLanguageIdentification) {\n return `${base}?no-language-identification=true` as ExtendedStreamType;\n }\n return createLanguageStream(base);\n}\n\n/**\n * Parse a touch event subscription to extract gesture information\n * @param subscription Subscription string (e.g., \"touch_event:forward_swipe\")\n * @returns Gesture name or null if not a gesture-specific subscription\n */\nexport function parseTouchEventStream(subscription: ExtendedStreamType): string | null {\n if (typeof subscription !== \"string\") {\n return null;\n }\n\n if (subscription.startsWith(`${StreamType.TOUCH_EVENT}:`)) {\n const [, gestureName] = subscription.split(\":\");\n const validGestures = [\n \"single_tap\",\n \"double_tap\",\n \"triple_tap\",\n \"long_press\",\n \"forward_swipe\",\n \"backward_swipe\",\n \"up_swipe\",\n \"down_swipe\",\n ];\n\n if (gestureName && validGestures.includes(gestureName)) {\n return gestureName;\n }\n }\n\n return null;\n}\n\n/**\n * Create a touch event subscription for a specific gesture\n * @param gesture Gesture name (e.g., \"forward_swipe\")\n * @returns Typed stream identifier\n */\nexport function createTouchEventStream(gesture: string): ExtendedStreamType {\n const validGestures = [\n \"single_tap\",\n \"double_tap\",\n \"triple_tap\",\n \"long_press\",\n \"forward_swipe\",\n \"backward_swipe\",\n \"up_swipe\",\n \"down_swipe\",\n ];\n\n if (!validGestures.includes(gesture)) {\n throw new Error(`Invalid gesture: ${gesture}`);\n }\n\n return `${StreamType.TOUCH_EVENT}:${gesture}` as ExtendedStreamType;\n}\n\n/**\n * Create a universal translation stream identifier that translates from any language to target\n * This is useful when you want to support all languages translating to a single target\n * Returns a type-safe stream type that can be used like a StreamType\n *\n * Example: createUniversalTranslationStream('es-ES') creates \"translation:all-to-es-ES\"\n *\n * @param targetLanguage Target language code (e.g., \"es-ES\")\n * @param options Optional configuration options\n * @returns Typed stream identifier\n */\nexport function createUniversalTranslationStream(\n targetLanguage: string,\n options?: { disableLanguageIdentification?: boolean },\n): ExtendedStreamType {\n const cleanTargetLanguage = targetLanguage.split(\"?\")[0];\n\n if (!isValidLanguageCode(cleanTargetLanguage)) {\n throw new Error(`Invalid target language code: ${cleanTargetLanguage}`);\n }\n\n const base = `${StreamType.TRANSLATION}:all-to-${cleanTargetLanguage}`;\n if (options?.disableLanguageIdentification) {\n return `${base}?no-language-identification=true` as ExtendedStreamType;\n }\n return createLanguageStream(base);\n}\n\n/**\n * Check if a subscription is a valid stream type\n * This handles both enum-based StreamType values and language-specific stream formats\n *\n * @param subscription Subscription to validate\n * @returns True if valid, false otherwise\n */\nexport function isValidStreamType(subscription: ExtendedStreamType): boolean {\n // Check if it's a standard StreamType\n if (Object.values(StreamType).includes(subscription as StreamType)) {\n return true;\n }\n\n // Check if it's a valid language-specific stream\n const languageStream = parseLanguageStream(subscription);\n if (languageStream !== null) {\n return true;\n }\n\n // Check if it's a valid gesture-specific stream\n const gestureStream = parseTouchEventStream(subscription);\n if (gestureStream !== null) {\n return true;\n }\n\n return false;\n}\n\n/**\n * Helper function to check if a stream type is of a particular category\n * Works with both standard and language-specific stream types\n */\nexport function isStreamCategory(streamType: ExtendedStreamType, category: StreamCategory): boolean {\n const baseType = getBaseStreamType(streamType);\n return baseType ? STREAM_CATEGORIES[baseType] === category : false;\n}\n\n/**\n * Helper function to get all stream types in a category\n */\nexport function getStreamTypesByCategory(category: StreamCategory): StreamType[] {\n return Object.entries(STREAM_CATEGORIES)\n .filter(([_, cat]) => cat === category)\n .map(([type]) => type as StreamType);\n}\n\n/**\n * Get the base StreamType for a subscription\n * Works with both standard StreamType values and language-specific formats\n *\n * @param subscription Subscription string or StreamType\n * @returns The base StreamType enum value\n */\nexport function getBaseStreamType(subscription: ExtendedStreamType): StreamType | null {\n // Check if it's already a standard StreamType\n if (Object.values(StreamType).includes(subscription as StreamType)) {\n return subscription as StreamType;\n }\n\n // Check if it's a language-specific stream\n const languageStream = parseLanguageStream(subscription);\n if (languageStream) {\n return languageStream.type;\n }\n\n // Check if it's a gesture-specific stream\n const gestureStream = parseTouchEventStream(subscription);\n if (gestureStream) {\n return StreamType.TOUCH_EVENT;\n }\n\n return null;\n}\n\n/**\n * Check if a stream is a language-specific stream\n */\nexport function isLanguageStream(subscription: ExtendedStreamType): boolean {\n return parseLanguageStream(subscription) !== null;\n}\n\n/**\n * Get language information from a stream type\n * Returns null for regular stream types\n */\nexport function getLanguageInfo(subscription: ExtendedStreamType): LanguageStreamInfo | null {\n return parseLanguageStream(subscription);\n}\n\n// this is the blueprint for our new rich subscription object\n// it allows a developer to specify a rate for the location stream\nexport interface LocationStreamRequest {\n stream: \"location_stream\";\n rate: \"standard\" | \"high\" | \"realtime\" | \"tenMeters\" | \"hundredMeters\" | \"kilometer\" | \"threeKilometers\" | \"reduced\";\n}\n",
8
+ "// src/message-types.ts\n\nimport { StreamType } from \"./streams\";\n/**\n * Types of messages from glasses to cloud\n */\nexport enum GlassesToCloudMessageType {\n // Control actions\n CONNECTION_INIT = \"connection_init\",\n REQUEST_SETTINGS = \"request_settings\",\n\n START_APP = StreamType.START_APP,\n STOP_APP = StreamType.STOP_APP,\n\n DASHBOARD_STATE = \"dashboard_state\",\n OPEN_DASHBOARD = StreamType.OPEN_DASHBOARD,\n\n // Mentra Live\n PHOTO_RESPONSE = StreamType.PHOTO_RESPONSE,\n\n // Local Transcription\n LOCAL_TRANSCRIPTION = \"local_transcription\",\n\n // Streaming\n STREAM_STATUS = StreamType.STREAM_STATUS,\n KEEP_ALIVE_ACK = \"keep_alive_ack\",\n\n BUTTON_PRESS = StreamType.BUTTON_PRESS,\n HEAD_POSITION = StreamType.HEAD_POSITION,\n TOUCH_EVENT = StreamType.TOUCH_EVENT,\n GLASSES_BATTERY_UPDATE = StreamType.GLASSES_BATTERY_UPDATE,\n PHONE_BATTERY_UPDATE = StreamType.PHONE_BATTERY_UPDATE,\n GLASSES_CONNECTION_STATE = StreamType.GLASSES_CONNECTION_STATE,\n LOCATION_UPDATE = StreamType.LOCATION_UPDATE,\n\n // TODO(isaiah): Remove VPS_COORDINATES once confirmed we don't use this system.\n VPS_COORDINATES = StreamType.VPS_COORDINATES,\n VAD = StreamType.VAD,\n\n // TODO(isaiah): Remove PHONE_NOTIFICATION, and PHONE_NOTIFICATION_DISMISSED after moving to REST request.\n PHONE_NOTIFICATION = StreamType.PHONE_NOTIFICATION,\n PHONE_NOTIFICATION_DISMISSED = StreamType.PHONE_NOTIFICATION_DISMISSED,\n\n // TODO(isaiah): Remove CALENDAR_EVENT after moving to REST request.\n CALENDAR_EVENT = StreamType.CALENDAR_EVENT,\n MENTRAOS_SETTINGS_UPDATE_REQUEST = StreamType.MENTRAOS_SETTINGS_UPDATE_REQUEST,\n\n // TODO(isaiah): Remove CORE_STATUS_UPDATE after moving to REST request.\n CORE_STATUS_UPDATE = StreamType.CORE_STATUS_UPDATE,\n\n PHOTO_TAKEN = StreamType.PHOTO_TAKEN,\n AUDIO_PLAY_RESPONSE = \"audio_play_response\",\n\n // RGB LED control\n RGB_LED_CONTROL_RESPONSE = \"rgb_led_control_response\",\n\n // LiveKit handshake\n LIVEKIT_INIT = \"livekit_init\",\n\n // UDP audio\n UDP_REGISTER = \"udp_register\",\n UDP_UNREGISTER = \"udp_unregister\",\n}\n\n/**\n * Types of messages from cloud to glasses\n */\nexport enum CloudToGlassesMessageType {\n // Responses\n CONNECTION_ACK = \"connection_ack\",\n CONNECTION_ERROR = \"connection_error\",\n AUTH_ERROR = \"auth_error\",\n\n // Updates\n DISPLAY_EVENT = \"display_event\",\n APP_STATE_CHANGE = \"app_state_change\",\n MICROPHONE_STATE_CHANGE = \"microphone_state_change\",\n SETTINGS_UPDATE = \"settings_update\",\n\n // Requests\n PHOTO_REQUEST = \"photo_request\",\n AUDIO_PLAY_REQUEST = \"audio_play_request\",\n AUDIO_STOP_REQUEST = \"audio_stop_request\",\n RGB_LED_CONTROL = \"rgb_led_control\",\n CAMERA_FOV_SET = \"camera_fov_set\",\n SHOW_WIFI_SETUP = \"show_wifi_setup\",\n\n // Streaming\n START_STREAM = \"start_stream\",\n STOP_STREAM = \"stop_stream\",\n KEEP_STREAM_ALIVE = \"keep_stream_alive\",\n\n // Dashboard updates\n DASHBOARD_MODE_CHANGE = \"dashboard_mode_change\",\n DASHBOARD_ALWAYS_ON_CHANGE = \"dashboard_always_on_change\",\n\n // Location Service\n SET_LOCATION_TIER = \"set_location_tier\",\n REQUEST_SINGLE_LOCATION = \"request_single_location\",\n\n WEBSOCKET_ERROR = \"websocket_error\",\n\n // LiveKit info (URL, room, token)\n LIVEKIT_INFO = \"livekit_info\",\n\n // UDP audio\n UDP_PING_ACK = \"udp_ping_ack\",\n}\n\n/**\n * Types of messages from Apps to cloud\n */\nexport enum AppToCloudMessageType {\n // Commands\n CONNECTION_INIT = \"tpa_connection_init\",\n RECONNECT = \"reconnect\",\n SUBSCRIPTION_UPDATE = \"subscription_update\",\n LOCATION_POLL_REQUEST = \"location_poll_request\",\n\n // Requests\n DISPLAY_REQUEST = \"display_event\",\n PHOTO_REQUEST = \"photo_request\",\n AUDIO_PLAY_REQUEST = \"audio_play_request\",\n AUDIO_STOP_REQUEST = \"audio_stop_request\",\n AUDIO_STREAM_START = \"audio_stream_start\",\n AUDIO_STREAM_END = \"audio_stream_end\",\n RGB_LED_CONTROL = \"rgb_led_control\",\n CAMERA_FOV_SET = \"camera_fov_set\",\n REQUEST_WIFI_SETUP = \"request_wifi_setup\",\n\n // Streaming\n STREAM_REQUEST = \"stream_request\",\n STREAM_STOP = \"stream_stop\",\n\n // Managed RTMP streaming\n MANAGED_STREAM_REQUEST = \"managed_stream_request\",\n MANAGED_STREAM_STOP = \"managed_stream_stop\",\n\n // Stream status check (both managed and unmanaged)\n STREAM_STATUS_CHECK = \"stream_status_check\",\n\n // Dashboard requests\n DASHBOARD_CONTENT_UPDATE = \"dashboard_content_update\",\n DASHBOARD_MODE_CHANGE = \"dashboard_mode_change\",\n DASHBOARD_SYSTEM_UPDATE = \"dashboard_system_update\",\n\n // TODO(isaiah): Remove after confirming not in use.\n // App-to-App Communication\n APP_BROADCAST_MESSAGE = \"app_broadcast_message\",\n APP_DIRECT_MESSAGE = \"app_direct_message\",\n APP_USER_DISCOVERY = \"app_user_discovery\",\n APP_ROOM_JOIN = \"app_room_join\",\n APP_ROOM_LEAVE = \"app_room_leave\",\n\n // Session lifecycle\n OWNERSHIP_RELEASE = \"ownership_release\",\n\n // Telemetry (for incident debugging)\n TELEMETRY_RESPONSE = \"telemetry_response\",\n}\n\n/**\n * Types of messages from cloud to Apps\n */\nexport enum CloudToAppMessageType {\n // Responses\n CONNECTION_ACK = \"tpa_connection_ack\",\n CONNECTION_ERROR = \"tpa_connection_error\",\n RECONNECT_ACK = \"reconnect_ack\",\n RECONNECT_REJECTED = \"reconnect_rejected\",\n RECONNECT_DEFERRED = \"reconnect_deferred\",\n\n // Updates\n APP_STOPPED = \"app_stopped\",\n SETTINGS_UPDATE = \"settings_update\",\n CAPABILITIES_UPDATE = \"capabilities_update\",\n DEVICE_STATE_UPDATE = \"device_state_update\",\n\n // Dashboard updates\n DASHBOARD_MODE_CHANGED = \"dashboard_mode_changed\",\n DASHBOARD_ALWAYS_ON_CHANGED = \"dashboard_always_on_changed\",\n\n // Stream data\n DATA_STREAM = \"data_stream\",\n\n // Media responses\n PHOTO_RESPONSE = \"photo_response\",\n AUDIO_PLAY_RESPONSE = \"audio_play_response\",\n AUDIO_STREAM_READY = \"audio_stream_ready\",\n RGB_LED_CONTROL_RESPONSE = \"rgb_led_control_response\",\n STREAM_STATUS = \"stream_status\",\n MANAGED_STREAM_STATUS = \"managed_stream_status\",\n STREAM_STATUS_CHECK_RESPONSE = \"stream_status_check_response\",\n\n WEBSOCKET_ERROR = \"websocket_error\",\n\n // Permissions\n PERMISSION_ERROR = \"permission_error\",\n\n // Telemetry (for incident debugging)\n REQUEST_TELEMETRY = \"request_telemetry\",\n\n /**\n * @deprecated Use the settings system (mentraosSettings) instead.\n * This message type was used for datetime updates but is no longer needed.\n * Will be removed in a future version.\n */\n CUSTOM_MESSAGE = \"custom_message\",\n\n // TODO(isaiah): Remove after confirming not in use.\n // App-to-App Communication Responses\n APP_MESSAGE_RECEIVED = \"app_message_received\",\n APP_USER_JOINED = \"app_user_joined\",\n APP_USER_LEFT = \"app_user_left\",\n APP_ROOM_UPDATED = \"app_room_updated\",\n APP_DIRECT_MESSAGE_RESPONSE = \"app_direct_message_response\",\n}\n\n/**\n * Control action message types (subset of GlassesToCloudMessageType)\n */\nexport const ControlActionTypes = [\n GlassesToCloudMessageType.CONNECTION_INIT,\n GlassesToCloudMessageType.START_APP,\n GlassesToCloudMessageType.STOP_APP,\n GlassesToCloudMessageType.DASHBOARD_STATE,\n GlassesToCloudMessageType.OPEN_DASHBOARD,\n] as const;\n\n/**\n * Event message types (subset of GlassesToCloudMessageType)\n */\nexport const EventTypes = [\n GlassesToCloudMessageType.BUTTON_PRESS,\n GlassesToCloudMessageType.HEAD_POSITION,\n GlassesToCloudMessageType.GLASSES_BATTERY_UPDATE,\n GlassesToCloudMessageType.PHONE_BATTERY_UPDATE,\n GlassesToCloudMessageType.GLASSES_CONNECTION_STATE,\n GlassesToCloudMessageType.LOCATION_UPDATE,\n GlassesToCloudMessageType.VPS_COORDINATES,\n GlassesToCloudMessageType.VAD,\n GlassesToCloudMessageType.PHONE_NOTIFICATION,\n GlassesToCloudMessageType.PHONE_NOTIFICATION_DISMISSED,\n GlassesToCloudMessageType.CALENDAR_EVENT,\n GlassesToCloudMessageType.MENTRAOS_SETTINGS_UPDATE_REQUEST,\n GlassesToCloudMessageType.CORE_STATUS_UPDATE,\n GlassesToCloudMessageType.LOCAL_TRANSCRIPTION,\n] as const;\n\n/**\n * Response message types (subset of CloudToGlassesMessageType)\n */\nexport const ResponseTypes = [\n CloudToGlassesMessageType.CONNECTION_ACK,\n CloudToGlassesMessageType.CONNECTION_ERROR,\n CloudToGlassesMessageType.AUTH_ERROR,\n] as const;\n\n/**\n * Update message types (subset of CloudToGlassesMessageType)\n */\nexport const UpdateTypes = [\n CloudToGlassesMessageType.DISPLAY_EVENT,\n CloudToGlassesMessageType.APP_STATE_CHANGE,\n CloudToGlassesMessageType.MICROPHONE_STATE_CHANGE,\n CloudToGlassesMessageType.PHOTO_REQUEST,\n CloudToGlassesMessageType.AUDIO_PLAY_REQUEST,\n CloudToGlassesMessageType.AUDIO_STOP_REQUEST,\n CloudToGlassesMessageType.RGB_LED_CONTROL,\n CloudToGlassesMessageType.SETTINGS_UPDATE,\n CloudToGlassesMessageType.DASHBOARD_MODE_CHANGE,\n CloudToGlassesMessageType.DASHBOARD_ALWAYS_ON_CHANGE,\n CloudToGlassesMessageType.START_STREAM,\n CloudToGlassesMessageType.STOP_STREAM,\n CloudToGlassesMessageType.KEEP_STREAM_ALIVE,\n CloudToGlassesMessageType.LIVEKIT_INFO,\n] as const;\n\n/**\n * Dashboard message types\n */\nexport const DashboardMessageTypes = [\n AppToCloudMessageType.DASHBOARD_CONTENT_UPDATE,\n AppToCloudMessageType.DASHBOARD_MODE_CHANGE,\n AppToCloudMessageType.DASHBOARD_SYSTEM_UPDATE,\n CloudToAppMessageType.DASHBOARD_MODE_CHANGED,\n CloudToAppMessageType.DASHBOARD_ALWAYS_ON_CHANGED,\n] as const;\n",
9
+ "// src/messages/glasses-to-cloud.ts\n\nimport { GlassesToCloudMessageType, ControlActionTypes, EventTypes } from \"../message-types\";\nimport { StreamType } from \"../streams\";\n\nimport { BaseMessage } from \"./base\";\n\n//===========================================================\n// Control actions\n//===========================================================\n\n/**\n * Connection initialization from glasses\n */\nexport interface ConnectionInit extends BaseMessage {\n type: GlassesToCloudMessageType.CONNECTION_INIT;\n userId?: string;\n coreToken?: string;\n}\n\n/**\n * Client requests LiveKit info (url, room, token)\n */\nexport interface LiveKitInit extends BaseMessage {\n type: GlassesToCloudMessageType.LIVEKIT_INIT;\n mode?: \"publish\" | \"subscribe\"; // Optional mode - defaults to 'publish' for backward compatibility\n}\n\nexport interface RequestSettings extends BaseMessage {\n type: GlassesToCloudMessageType.REQUEST_SETTINGS;\n sessionId: string;\n}\n\n/**\n * Start app request from glasses\n */\nexport interface StartApp extends BaseMessage {\n type: GlassesToCloudMessageType.START_APP;\n packageName: string;\n}\n\n/**\n * Stop app request from glasses\n */\nexport interface StopApp extends BaseMessage {\n type: GlassesToCloudMessageType.STOP_APP;\n packageName: string;\n}\n\n/**\n * Dashboard state update from glasses\n */\nexport interface DashboardState extends BaseMessage {\n type: GlassesToCloudMessageType.DASHBOARD_STATE;\n isOpen: boolean;\n}\n\n/**\n * Open dashboard request from glasses\n */\nexport interface OpenDashboard extends BaseMessage {\n type: GlassesToCloudMessageType.OPEN_DASHBOARD;\n}\n\n//===========================================================\n// Events and data\n//===========================================================\n\n/**\n * Button press event from glasses\n */\nexport interface ButtonPress extends BaseMessage {\n type: GlassesToCloudMessageType.BUTTON_PRESS;\n buttonId: string;\n pressType: \"short\" | \"long\";\n}\n\n/**\n * Head position event from glasses\n */\nexport interface HeadPosition extends BaseMessage {\n type: GlassesToCloudMessageType.HEAD_POSITION;\n position: \"up\" | \"down\";\n}\n\n/**\n * Touch gesture event from glasses\n */\nexport interface TouchEvent extends BaseMessage {\n type: GlassesToCloudMessageType.TOUCH_EVENT;\n device_model: string;\n gesture_name: string;\n timestamp: Date;\n}\n\n/**\n * Glasses battery update from glasses\n */\nexport interface GlassesBatteryUpdate extends BaseMessage {\n type: GlassesToCloudMessageType.GLASSES_BATTERY_UPDATE;\n level: number; // 0-100\n charging: boolean;\n timeRemaining?: number; // minutes\n}\n\n/**\n * Phone battery update from glasses\n */\nexport interface PhoneBatteryUpdate extends BaseMessage {\n type: GlassesToCloudMessageType.PHONE_BATTERY_UPDATE;\n level: number; // 0-100\n charging: boolean;\n timeRemaining?: number; // minutes\n}\n\n/**\n * Glasses connection state from glasses\n */\nexport interface GlassesConnectionState extends BaseMessage {\n type: GlassesToCloudMessageType.GLASSES_CONNECTION_STATE;\n modelName: string;\n status: string;\n\n // Optional WiFi details (only present for WiFi-capable glasses)\n wifi?: {\n connected: boolean;\n ssid?: string | null;\n };\n}\n\n/**\n * Location update from glasses\n */\nexport interface LocationUpdate extends BaseMessage {\n type: GlassesToCloudMessageType.LOCATION_UPDATE | StreamType.LOCATION_UPDATE;\n lat: number;\n lng: number;\n accuracy?: number; // Accuracy in meters\n correlationId?: string; // for poll responses\n}\n\n/**\n * VPS coordinates update from glasses\n */\nexport interface VpsCoordinates extends BaseMessage {\n type: GlassesToCloudMessageType.VPS_COORDINATES | StreamType.VPS_COORDINATES;\n deviceModel: string;\n requestId: string;\n x: number;\n y: number;\n z: number;\n qx: number;\n qy: number;\n qz: number;\n qw: number;\n confidence: number;\n}\n\nexport interface LocalTranscription extends BaseMessage {\n type: GlassesToCloudMessageType.LOCAL_TRANSCRIPTION;\n text: string;\n isFinal: boolean;\n startTime: number;\n endTime: number;\n speakerId: number;\n transcribeLanguage: string;\n provider: string;\n}\n\nexport interface CalendarEvent extends BaseMessage {\n type: GlassesToCloudMessageType.CALENDAR_EVENT | StreamType.CALENDAR_EVENT;\n eventId: string;\n title: string;\n dtStart: string;\n dtEnd: string;\n timezone: string;\n timeStamp: string;\n}\n\n/**\n * Voice activity detection from glasses\n */\nexport interface Vad extends BaseMessage {\n type: GlassesToCloudMessageType.VAD;\n status: boolean | \"true\" | \"false\";\n}\n\n/**\n * Phone notification from glasses\n */\nexport interface PhoneNotification extends BaseMessage {\n type: GlassesToCloudMessageType.PHONE_NOTIFICATION;\n notificationId: string;\n app: string;\n title: string;\n content: string;\n priority: \"low\" | \"normal\" | \"high\";\n}\n\n/**\n * Notification dismissed from glasses\n */\nexport interface PhoneNotificationDismissed extends BaseMessage {\n type: GlassesToCloudMessageType.PHONE_NOTIFICATION_DISMISSED;\n notificationId: string;\n app: string;\n title: string;\n content: string;\n notificationKey: string;\n}\n\n/**\n * MentraOS settings update from glasses\n */\nexport interface MentraosSettingsUpdateRequest extends BaseMessage {\n type: GlassesToCloudMessageType.MENTRAOS_SETTINGS_UPDATE_REQUEST;\n}\nexport interface MentraosSettingsUpdateRequest extends BaseMessage {\n type: GlassesToCloudMessageType.MENTRAOS_SETTINGS_UPDATE_REQUEST;\n}\n\n/**\n * Core status update from glasses\n */\nexport interface CoreStatusUpdate extends BaseMessage {\n type: GlassesToCloudMessageType.CORE_STATUS_UPDATE;\n status: string;\n details?: Record<string, any>;\n}\n\n// ===========================================================\n// Mentra Live\n// ===========================================================\n\n/**\n * Photo error codes for detailed error reporting\n */\nexport enum PhotoErrorCode {\n CAMERA_INIT_FAILED = \"CAMERA_INIT_FAILED\",\n CAMERA_CAPTURE_FAILED = \"CAMERA_CAPTURE_FAILED\",\n CAMERA_TIMEOUT = \"CAMERA_TIMEOUT\",\n CAMERA_BUSY = \"CAMERA_BUSY\",\n UPLOAD_FAILED = \"UPLOAD_FAILED\",\n UPLOAD_TIMEOUT = \"UPLOAD_TIMEOUT\",\n BLE_TRANSFER_FAILED = \"BLE_TRANSFER_FAILED\",\n BLE_TRANSFER_BUSY = \"BLE_TRANSFER_BUSY\",\n BLE_TRANSFER_FAILED_TO_START = \"BLE_TRANSFER_FAILED_TO_START\",\n BLE_TRANSFER_TIMEOUT = \"BLE_TRANSFER_TIMEOUT\",\n COMPRESSION_FAILED = \"COMPRESSION_FAILED\",\n PERMISSION_DENIED = \"PERMISSION_DENIED\",\n STORAGE_FULL = \"STORAGE_FULL\",\n NETWORK_ERROR = \"NETWORK_ERROR\",\n // Phone-side error codes\n PHONE_GLASSES_NOT_CONNECTED = \"PHONE_GLASSES_NOT_CONNECTED\",\n PHONE_BLE_TRANSFER_FAILED = \"PHONE_BLE_TRANSFER_FAILED\",\n PHONE_UPLOAD_FAILED = \"PHONE_UPLOAD_FAILED\",\n PHONE_TIMEOUT = \"PHONE_TIMEOUT\",\n UNKNOWN_ERROR = \"UNKNOWN_ERROR\",\n}\n\n/**\n * Photo processing stages for error context\n */\nexport enum PhotoStage {\n REQUEST_RECEIVED = \"REQUEST_RECEIVED\",\n CAMERA_INIT = \"CAMERA_INIT\",\n PHOTO_CAPTURE = \"PHOTO_CAPTURE\",\n COMPRESSION = \"COMPRESSION\",\n UPLOAD_START = \"UPLOAD_START\",\n UPLOAD_PROGRESS = \"UPLOAD_PROGRESS\",\n BLE_TRANSFER = \"BLE_TRANSFER\",\n RESPONSE_SENT = \"RESPONSE_SENT\",\n}\n\n/**\n * Connection state information for error diagnostics\n */\nexport interface ConnectionState {\n wifi: {\n connected: boolean;\n ssid?: string;\n hasInternet: boolean;\n };\n ble: {\n connected: boolean;\n transferInProgress: boolean;\n };\n camera: {\n available: boolean;\n initialized: boolean;\n };\n storage: {\n availableSpace: number;\n totalSpace: number;\n };\n}\n\n/**\n * Detailed error information for photo failures\n */\nexport interface PhotoErrorDetails {\n stage: PhotoStage;\n connectionState?: ConnectionState;\n retryable: boolean;\n suggestedAction?: string;\n diagnosticInfo?: {\n timestamp: number;\n duration: number;\n retryCount: number;\n lastSuccessfulStage?: PhotoStage;\n };\n}\n\n/**\n * Enhanced photo response with error support\n */\nexport interface PhotoResponse extends BaseMessage {\n type: GlassesToCloudMessageType.PHOTO_RESPONSE;\n requestId: string; // Unique ID for the photo request\n success: boolean; // Explicit success/failure flag\n\n // Success fields (only present when success = true)\n photoUrl?: string; // URL of the uploaded photo\n savedToGallery?: boolean; // Whether the photo was saved to gallery\n\n // Error fields (only present when success = false)\n error?: {\n code: PhotoErrorCode;\n message: string;\n details?: PhotoErrorDetails;\n };\n}\n\n/**\n * RGB LED control response from glasses\n */\nexport interface RgbLedControlResponse extends BaseMessage {\n type: GlassesToCloudMessageType.RGB_LED_CONTROL_RESPONSE;\n requestId: string;\n success: boolean;\n error?: string;\n}\n\n/**\n * Stream status update from glasses\n */\nexport interface StreamStatus extends BaseMessage {\n type: GlassesToCloudMessageType.STREAM_STATUS;\n streamId?: string; // Unique identifier for the stream\n status:\n | \"initializing\"\n | \"connecting\"\n | \"reconnecting\"\n | \"streaming\"\n | \"error\"\n | \"stopped\"\n | \"active\"\n | \"stopping\"\n | \"disconnected\"\n | \"timeout\"\n | \"reconnected\"\n | \"reconnect_failed\";\n errorDetails?: string;\n appId?: string; // ID of the app that requested the stream\n stats?: {\n bitrate: number;\n fps: number;\n droppedFrames: number;\n duration: number;\n };\n}\n\n/**\n * Keep-alive acknowledgment from glasses\n */\nexport interface KeepAliveAck extends BaseMessage {\n type: GlassesToCloudMessageType.KEEP_ALIVE_ACK;\n streamId: string; // ID of the stream being kept alive\n ackId: string; // Acknowledgment ID that was sent by cloud\n}\n\n/**\n * Photo taken event from glasses\n */\nexport interface PhotoTaken extends BaseMessage {\n type: GlassesToCloudMessageType.PHOTO_TAKEN;\n photoData: ArrayBuffer;\n mimeType: string;\n timestamp: Date;\n}\n\n/**\n * Audio play response from glasses/core\n */\nexport interface AudioPlayResponse extends BaseMessage {\n type: GlassesToCloudMessageType.AUDIO_PLAY_RESPONSE;\n requestId: string;\n success: boolean;\n error?: string;\n duration?: number;\n}\n\n/**\n * UDP audio registration request from glasses/phone\n * Mobile sends this to register its userIdHash for UDP audio routing\n */\nexport interface UdpRegister extends BaseMessage {\n type: GlassesToCloudMessageType.UDP_REGISTER;\n userIdHash: number; // FNV-1a 32-bit hash of userId\n}\n\n/**\n * UDP audio unregistration request from glasses/phone\n * Mobile sends this when stopping UDP audio\n */\nexport interface UdpUnregister extends BaseMessage {\n type: GlassesToCloudMessageType.UDP_UNREGISTER;\n userIdHash: number; // FNV-1a 32-bit hash of userId\n}\n\n/**\n * Union type for all messages from glasses to cloud\n */\nexport type GlassesToCloudMessage =\n | ConnectionInit\n | LiveKitInit\n | RequestSettings\n | StartApp\n | StopApp\n | DashboardState\n | OpenDashboard\n | ButtonPress\n | HeadPosition\n | TouchEvent\n | GlassesBatteryUpdate\n | PhoneBatteryUpdate\n | GlassesConnectionState\n | LocationUpdate\n | VpsCoordinates\n | CalendarEvent\n | Vad\n | PhoneNotification\n | PhoneNotificationDismissed\n | MentraosSettingsUpdateRequest\n | CoreStatusUpdate\n | StreamStatus\n | KeepAliveAck\n | PhotoResponse\n | RgbLedControlResponse\n | PhotoTaken\n | AudioPlayResponse\n | LocalTranscription\n | UdpRegister\n | UdpUnregister;\n\n//===========================================================\n// Type guards\n//===========================================================\n\nexport function isControlAction(message: GlassesToCloudMessage): boolean {\n return ControlActionTypes.includes(message.type as any);\n}\n\nexport function isEvent(message: GlassesToCloudMessage): boolean {\n return EventTypes.includes(message.type as any);\n}\n\n// Individual type guards\nexport function isConnectionInit(message: GlassesToCloudMessage): message is ConnectionInit {\n return message.type === GlassesToCloudMessageType.CONNECTION_INIT;\n}\n\nexport function isRequestSettings(message: GlassesToCloudMessage): message is RequestSettings {\n return message.type === GlassesToCloudMessageType.REQUEST_SETTINGS;\n}\n\nexport function isStartApp(message: GlassesToCloudMessage): message is StartApp {\n return message.type === GlassesToCloudMessageType.START_APP;\n}\n\nexport function isStopApp(message: GlassesToCloudMessage): message is StopApp {\n return message.type === GlassesToCloudMessageType.STOP_APP;\n}\n\nexport function isButtonPress(message: GlassesToCloudMessage): message is ButtonPress {\n return message.type === GlassesToCloudMessageType.BUTTON_PRESS;\n}\n\nexport function isHeadPosition(message: GlassesToCloudMessage): message is HeadPosition {\n return message.type === GlassesToCloudMessageType.HEAD_POSITION;\n}\n\nexport function isGlassesBatteryUpdate(message: GlassesToCloudMessage): message is GlassesBatteryUpdate {\n return message.type === GlassesToCloudMessageType.GLASSES_BATTERY_UPDATE;\n}\n\nexport function isPhoneBatteryUpdate(message: GlassesToCloudMessage): message is PhoneBatteryUpdate {\n return message.type === GlassesToCloudMessageType.PHONE_BATTERY_UPDATE;\n}\n\nexport function isGlassesConnectionState(message: GlassesToCloudMessage): message is GlassesConnectionState {\n return message.type === GlassesToCloudMessageType.GLASSES_CONNECTION_STATE;\n}\n\nexport function isLocationUpdate(message: GlassesToCloudMessage): message is LocationUpdate {\n return message.type === GlassesToCloudMessageType.LOCATION_UPDATE;\n}\n\nexport function isCalendarEvent(message: GlassesToCloudMessage): message is CalendarEvent {\n return message.type === GlassesToCloudMessageType.CALENDAR_EVENT;\n}\n\nexport function isVad(message: GlassesToCloudMessage): message is Vad {\n return message.type === GlassesToCloudMessageType.VAD;\n}\n\nexport function isPhoneNotification(message: GlassesToCloudMessage): message is PhoneNotification {\n return message.type === GlassesToCloudMessageType.PHONE_NOTIFICATION;\n}\n\nexport function isPhoneNotificationDismissed(message: GlassesToCloudMessage): message is PhoneNotificationDismissed {\n return message.type === GlassesToCloudMessageType.PHONE_NOTIFICATION_DISMISSED;\n}\n\nexport function isStreamStatus(message: GlassesToCloudMessage): message is StreamStatus {\n return message.type === GlassesToCloudMessageType.STREAM_STATUS;\n}\n\nexport function isPhotoResponse(message: GlassesToCloudMessage): message is PhotoResponse {\n return message.type === GlassesToCloudMessageType.PHOTO_RESPONSE;\n}\n\nexport function isRgbLedControlResponse(message: GlassesToCloudMessage): message is RgbLedControlResponse {\n return message.type === GlassesToCloudMessageType.RGB_LED_CONTROL_RESPONSE;\n}\n\nexport function isKeepAliveAck(message: GlassesToCloudMessage): message is KeepAliveAck {\n return message.type === GlassesToCloudMessageType.KEEP_ALIVE_ACK;\n}\n\nexport function isPhotoTaken(message: GlassesToCloudMessage): message is PhotoTaken {\n return message.type === GlassesToCloudMessageType.PHOTO_TAKEN;\n}\n\nexport function isAudioPlayResponse(message: GlassesToCloudMessage): message is AudioPlayResponse {\n return message.type === GlassesToCloudMessageType.AUDIO_PLAY_RESPONSE;\n}\n\nexport function isLocalTranscription(message: GlassesToCloudMessage): message is LocalTranscription {\n return message.type === GlassesToCloudMessageType.LOCAL_TRANSCRIPTION;\n}\n\nexport function isUdpRegister(message: GlassesToCloudMessage): message is UdpRegister {\n return message.type === GlassesToCloudMessageType.UDP_REGISTER;\n}\n\nexport function isUdpUnregister(message: GlassesToCloudMessage): message is UdpUnregister {\n return message.type === GlassesToCloudMessageType.UDP_UNREGISTER;\n}\n",
10
+ "// src/messages/cloud-to-glasses.ts\n\nimport { Layout } from \"../layouts\";\nimport { CloudToGlassesMessageType, ResponseTypes, UpdateTypes } from \"../message-types\";\nimport { CameraRoiPosition } from \"./app-to-cloud\";\n\nimport { BaseMessage } from \"./base\";\n// import { UserSession } from \"../user-session\";\n\n//===========================================================\n// Responses\n//===========================================================\n\n/**\n * Connection acknowledgment to glasses\n */\nexport interface ConnectionAck extends BaseMessage {\n type: CloudToGlassesMessageType.CONNECTION_ACK;\n // userSession: Partial<UserSession>;\n sessionId: string;\n livekit?: {\n url: string;\n roomName: string;\n token: string;\n };\n /**\n * UDP encryption info - only present if client requested encryption via ?udpEncryption=true\n * Client uses this symmetric key to encrypt UDP audio packets with secretbox\n */\n udpEncryption?: {\n /** Symmetric key for XSalsa20-Poly1305 encryption (base64 encoded, 32 bytes) */\n key: string;\n /** Encryption algorithm being used */\n algorithm: \"xsalsa20-poly1305\";\n };\n}\n\n/**\n * Connection error to glasses\n */\nexport interface ConnectionError extends BaseMessage {\n type: CloudToGlassesMessageType.CONNECTION_ERROR;\n code?: string;\n message: string;\n}\n\n/**\n * Authentication error to glasses\n */\nexport interface AuthError extends BaseMessage {\n type: CloudToGlassesMessageType.AUTH_ERROR;\n message: string;\n}\n\n//===========================================================\n// Updates\n//===========================================================\n\n/**\n * Display update to glasses\n */\nexport interface DisplayEvent extends BaseMessage {\n type: CloudToGlassesMessageType.DISPLAY_EVENT;\n layout: Layout;\n durationMs?: number;\n}\n\n/**\n * App state change to glasses\n */\nexport interface AppStateChange extends BaseMessage {\n type: CloudToGlassesMessageType.APP_STATE_CHANGE;\n // userSession: Partial<UserSession>;\n error?: string;\n}\n\n/**\n * Microphone state change to glasses\n */\nexport interface MicrophoneStateChange extends BaseMessage {\n type: CloudToGlassesMessageType.MICROPHONE_STATE_CHANGE;\n // userSession: Partial<UserSession>;\n isMicrophoneEnabled: boolean;\n requiredData: Array<\"pcm\" | \"transcription\" | \"pcm_or_transcription\">;\n bypassVad?: boolean; // NEW: PCM subscription bypass\n}\n\n/**\n * Photo request to glasses\n */\nexport interface PhotoRequestToGlasses extends BaseMessage {\n type: CloudToGlassesMessageType.PHOTO_REQUEST;\n // userSession: Partial<UserSession>;\n requestId: string;\n appId: string;\n saveToGallery?: boolean;\n webhookUrl?: string; // URL where ASG should send the photo directly\n authToken?: string; // Auth token for webhook authentication\n /** Desired capture size to guide device resolution selection */\n size?: \"small\" | \"medium\" | \"large\" | \"full\";\n /** Image compression level: none, medium, or heavy */\n compress?: \"none\" | \"medium\" | \"heavy\";\n /** Controls front-facing privacy flash LED. Cloud-controlled based on packageName. */\n flash?: boolean;\n /** Controls shutter/video sounds. */\n sound?: boolean;\n}\n\n/**\n * LED color type for RGB LED control\n */\nexport type LedColor = \"red\" | \"green\" | \"blue\" | \"orange\" | \"white\";\n\n/**\n * RGB LED control request to glasses\n */\nexport interface RgbLedControlToGlasses extends BaseMessage {\n type: CloudToGlassesMessageType.RGB_LED_CONTROL;\n requestId: string;\n appId: string;\n action: \"on\" | \"off\"; // Only low-level on/off actions\n color?: LedColor; // LED color name\n ontime?: number;\n offtime?: number;\n count?: number;\n}\n\n/**\n * Camera FOV set request to glasses/mobile\n */\nexport interface CameraFovSetToGlasses extends BaseMessage {\n type: CloudToGlassesMessageType.CAMERA_FOV_SET;\n requestId: string;\n appId: string;\n fov: number;\n roiPosition: CameraRoiPosition;\n}\n\n// TODO(isaiah): Deprecated, remove this after new mobile client refactor complete, and we migrate to SettingsStateChange.\n/**\n * Settings update to glasses\n */\nexport interface SettingsUpdate extends BaseMessage {\n type: CloudToGlassesMessageType.SETTINGS_UPDATE;\n sessionId: string;\n settings: {\n useOnboardMic: boolean;\n contextualDashboard: boolean;\n metricSystemEnabled: boolean;\n headUpAngle: number;\n brightness: number;\n autoBrightness: boolean;\n sensingEnabled: boolean;\n alwaysOnStatusBar: boolean;\n bypassVad: boolean;\n bypassAudioEncoding: boolean;\n };\n}\n\n/**\n * LiveKit info for client to connect & publish\n */\nexport interface LiveKitInfo extends BaseMessage {\n type: CloudToGlassesMessageType.LIVEKIT_INFO;\n url: string;\n roomName: string;\n token: string;\n}\n\n//===========================================================\n// Streaming Commands (RTMP / SRT / WHIP)\n//===========================================================\n\n/**\n * Start stream command to glasses\n */\nexport interface StartStream extends BaseMessage {\n type: CloudToGlassesMessageType.START_STREAM;\n streamUrl: string;\n appId: string;\n streamId?: string;\n video?: any; // Video configuration\n audio?: any; // Audio configuration\n stream?: any; // Stream configuration\n /** Controls front-facing privacy flash LED. Cloud-controlled. */\n flash?: boolean;\n /** Controls stream start/stop sounds. */\n sound?: boolean;\n}\n\n/**\n * Stop stream command to glasses\n */\nexport interface StopStream extends BaseMessage {\n type: CloudToGlassesMessageType.STOP_STREAM;\n appId: string;\n streamId?: string;\n}\n\n/**\n * Keep stream alive command to glasses\n */\nexport interface KeepStreamAlive extends BaseMessage {\n type: CloudToGlassesMessageType.KEEP_STREAM_ALIVE;\n streamId: string;\n ackId: string;\n}\n\n//===========================================================\n// Location Service Commands\n//===========================================================\n\n/**\n * Sets the continuous location update tier on the device.\n */\nexport interface SetLocationTier extends BaseMessage {\n type: CloudToGlassesMessageType.SET_LOCATION_TIER;\n tier: \"realtime\" | \"high\" | \"tenMeters\" | \"hundredMeters\" | \"kilometer\" | \"threeKilometers\" | \"reduced\" | \"standard\";\n}\n\n/**\n * Requests a single, on-demand location fix from the device.\n */\nexport interface RequestSingleLocation extends BaseMessage {\n type: CloudToGlassesMessageType.REQUEST_SINGLE_LOCATION;\n accuracy: string; // The accuracy tier requested by the app\n correlationId: string; // To match the response with the poll request\n}\n\n/**\n * Audio play request to glasses\n */\nexport interface AudioPlayRequestToGlasses extends BaseMessage {\n type: CloudToGlassesMessageType.AUDIO_PLAY_REQUEST;\n // userSession: Partial<UserSession>;\n requestId: string;\n appId: string;\n audioUrl: string; // URL to audio file for download and play\n volume?: number; // Volume level 0.0-1.0, defaults to 1.0\n stopOtherAudio?: boolean; // Whether to stop other audio playback, defaults to true\n}\n\n/**\n * Audio stop request to glasses\n */\nexport interface AudioStopRequestToGlasses extends BaseMessage {\n type: CloudToGlassesMessageType.AUDIO_STOP_REQUEST;\n // userSession: Partial<UserSession>;\n appId: string;\n}\n\n/**\n * WiFi setup request to glasses/mobile\n */\nexport interface ShowWifiSetup extends BaseMessage {\n type: CloudToGlassesMessageType.SHOW_WIFI_SETUP;\n reason?: string;\n appPackageName?: string;\n}\n\n/**\n * UDP ping acknowledgment from cloud to mobile\n * Sent when the Go UDP listener receives a ping from the mobile client\n */\nexport interface UdpPingAck extends BaseMessage {\n type: CloudToGlassesMessageType.UDP_PING_ACK;\n}\n\n/**\n * Union type for all messages from cloud to glasses\n */\nexport type CloudToGlassesMessage =\n | ConnectionAck\n | ConnectionError\n | AuthError\n | DisplayEvent\n | AppStateChange\n | MicrophoneStateChange\n | PhotoRequestToGlasses\n | RgbLedControlToGlasses\n | CameraFovSetToGlasses\n | AudioPlayRequestToGlasses\n | AudioStopRequestToGlasses\n | SettingsUpdate\n | StartStream\n | StopStream\n | KeepStreamAlive\n | SetLocationTier\n | RequestSingleLocation\n | LiveKitInfo\n | ShowWifiSetup\n | UdpPingAck;\n\n//===========================================================\n// Type guards\n//===========================================================\n\nexport function isResponse(message: CloudToGlassesMessage): boolean {\n return ResponseTypes.includes(message.type as any);\n}\n\nexport function isUpdate(message: CloudToGlassesMessage): boolean {\n return UpdateTypes.includes(message.type as any);\n}\n\n// Individual type guards\nexport function isConnectionAck(message: CloudToGlassesMessage): message is ConnectionAck {\n return message.type === CloudToGlassesMessageType.CONNECTION_ACK;\n}\n\nexport function isConnectionError(message: CloudToGlassesMessage): message is ConnectionError {\n return message.type === CloudToGlassesMessageType.CONNECTION_ERROR;\n}\n\nexport function isAuthError(message: CloudToGlassesMessage): message is AuthError {\n return message.type === CloudToGlassesMessageType.AUTH_ERROR;\n}\n\nexport function isDisplayEvent(message: CloudToGlassesMessage): message is DisplayEvent {\n return message.type === CloudToGlassesMessageType.DISPLAY_EVENT;\n}\n\nexport function isAppStateChange(message: CloudToGlassesMessage): message is AppStateChange {\n return message.type === CloudToGlassesMessageType.APP_STATE_CHANGE;\n}\n\nexport function isMicrophoneStateChange(message: CloudToGlassesMessage): message is MicrophoneStateChange {\n return message.type === CloudToGlassesMessageType.MICROPHONE_STATE_CHANGE;\n}\n\nexport function isPhotoRequest(message: CloudToGlassesMessage): message is PhotoRequestToGlasses {\n return message.type === CloudToGlassesMessageType.PHOTO_REQUEST;\n}\n\nexport function isRgbLedControl(message: CloudToGlassesMessage): message is RgbLedControlToGlasses {\n return message.type === CloudToGlassesMessageType.RGB_LED_CONTROL;\n}\n\nexport function isSettingsUpdate(message: CloudToGlassesMessage): message is SettingsUpdate {\n return message.type === CloudToGlassesMessageType.SETTINGS_UPDATE;\n}\n\nexport function isStartStream(message: CloudToGlassesMessage): message is StartStream {\n return message.type === CloudToGlassesMessageType.START_STREAM;\n}\n\nexport function isStopStream(message: CloudToGlassesMessage): message is StopStream {\n return message.type === CloudToGlassesMessageType.STOP_STREAM;\n}\n\nexport function isKeepStreamAlive(message: CloudToGlassesMessage): message is KeepStreamAlive {\n return message.type === CloudToGlassesMessageType.KEEP_STREAM_ALIVE;\n}\n\nexport function isAudioPlayRequestToGlasses(message: CloudToGlassesMessage): message is AudioPlayRequestToGlasses {\n return message.type === CloudToGlassesMessageType.AUDIO_PLAY_REQUEST;\n}\n\nexport function isAudioStopRequestToGlasses(message: CloudToGlassesMessage): message is AudioStopRequestToGlasses {\n return message.type === CloudToGlassesMessageType.AUDIO_STOP_REQUEST;\n}\n",
11
+ "// src/messages/app-to-cloud.ts\n\nimport { BaseMessage } from \"./base\";\nimport { AppToCloudMessageType } from \"../message-types\";\nimport { ExtendedStreamType, LocationStreamRequest } from \"../streams\";\nimport { DisplayRequest } from \"../layouts\";\nimport { DashboardContentUpdate, DashboardModeChange, DashboardSystemUpdate } from \"../dashboard\";\nimport type { VideoConfig, AudioConfig, StreamConfig } from \"../rtmp-stream\";\nimport { LedColor } from \"./cloud-to-glasses\";\n\n// a subscription can now be either a simple string or our new rich object\nexport type SubscriptionRequest = ExtendedStreamType | LocationStreamRequest;\n\n/**\n * Connection initialization from App\n */\nexport interface AppConnectionInit extends BaseMessage {\n type: AppToCloudMessageType.CONNECTION_INIT;\n packageName: string;\n sessionId?: string;\n apiKey: string;\n sdkVersion?: string;\n}\n\n/**\n * Reconnect request from App\n */\nexport interface AppReconnect extends BaseMessage {\n type: AppToCloudMessageType.RECONNECT;\n sessionId: string;\n sdkVersion: string;\n}\n\n/**\n * Subscription update from App\n */\nexport interface AppSubscriptionUpdate extends BaseMessage {\n type: AppToCloudMessageType.SUBSCRIPTION_UPDATE;\n packageName: string;\n subscriptions: SubscriptionRequest[];\n}\n\n/**\n * Photo request from App\n */\nexport interface PhotoRequest extends BaseMessage {\n type: AppToCloudMessageType.PHOTO_REQUEST;\n packageName: string;\n requestId: string; // SDK-generated request ID to track the request\n saveToGallery?: boolean;\n customWebhookUrl?: string; // Custom webhook URL to override TPA's default\n authToken?: string; // Auth token for custom webhook authentication\n /** Desired photo size sent by App. Defaults to 'medium' if omitted. */\n size?: \"small\" | \"medium\" | \"large\" | \"full\";\n /** Image compression level: none, medium, or heavy. Defaults to none. */\n compress?: \"none\" | \"medium\" | \"heavy\";\n /** Controls shutter sound. Defaults to true if omitted. */\n sound?: boolean;\n}\n\n/**\n * RGB LED control request from App\n */\nexport interface RgbLedControlRequest extends BaseMessage {\n type: AppToCloudMessageType.RGB_LED_CONTROL;\n packageName: string;\n requestId: string; // SDK-generated request ID to track the request\n action: \"on\" | \"off\"; // Only low-level on/off actions\n color?: LedColor; // LED color name\n ontime?: number; // LED on duration in ms\n offtime?: number; // LED off duration in ms\n count?: number; // Number of on/off cycles\n}\n\n// Video, Audio and Stream configuration interfaces are imported from '../rtmp-stream'\n\n/**\n * Stream request from App (supports RTMP, SRT, WHIP)\n */\nexport interface StreamRequest extends BaseMessage {\n type: AppToCloudMessageType.STREAM_REQUEST;\n packageName: string;\n streamUrl: string;\n video?: VideoConfig;\n audio?: AudioConfig;\n stream?: StreamConfig;\n /** Controls stream start/stop sounds. Defaults to true if omitted. */\n sound?: boolean;\n}\n\n/**\n * Stream stop request from App\n */\nexport interface StreamStopRequest extends BaseMessage {\n type: AppToCloudMessageType.STREAM_STOP;\n packageName: string;\n streamId?: string; // Optional stream ID to specify which stream to stop\n}\n\n// defines the structure for our new on-demand location poll command\nexport interface AppLocationPollRequest extends BaseMessage {\n type: AppToCloudMessageType.LOCATION_POLL_REQUEST;\n packageName: string;\n sessionId: string;\n accuracy: string;\n correlationId: string;\n}\n\n/**\n * Re-stream destination for managed streams\n */\nexport interface RestreamDestination {\n /** RTMP URL like rtmp://youtube.com/live/STREAM-KEY */\n url: string;\n /** Optional friendly name like \"YouTube\" or \"Twitch\" */\n name?: string;\n}\n\n/**\n * Managed stream request from App.\n * By default, managed streams use WebRTC (WHIP ingest → WHEP playback) for low latency.\n * If restreamDestinations are provided, falls back to SRT ingest with HLS/DASH playback.\n */\nexport interface ManagedStreamRequest extends BaseMessage {\n type: AppToCloudMessageType.MANAGED_STREAM_REQUEST;\n packageName: string;\n quality?: \"720p\" | \"1080p\";\n enableWebRTC?: boolean;\n video?: VideoConfig;\n audio?: AudioConfig;\n stream?: StreamConfig;\n /** Optional RTMP destinations to re-stream to (YouTube, Twitch, etc).\n * When present, stream uses SRT ingest + HLS/DASH playback instead of WebRTC. */\n restreamDestinations?: RestreamDestination[];\n /** Controls stream start/stop sounds. Defaults to true if omitted. */\n sound?: boolean;\n}\n\n/**\n * Managed RTMP stream stop request from App\n */\nexport interface ManagedStreamStopRequest extends BaseMessage {\n type: AppToCloudMessageType.MANAGED_STREAM_STOP;\n packageName: string;\n}\n\n/**\n * Stream status check request from App\n * Checks if there are any existing streams (managed or unmanaged) for the current user\n */\nexport interface StreamStatusCheckRequest extends BaseMessage {\n type: AppToCloudMessageType.STREAM_STATUS_CHECK;\n packageName: string;\n sessionId: string;\n}\n\n/**\n * Audio play request from App\n */\nexport interface AudioPlayRequest extends BaseMessage {\n type: AppToCloudMessageType.AUDIO_PLAY_REQUEST;\n packageName: string;\n requestId: string; // SDK-generated request ID to track the request\n audioUrl: string; // URL to audio file for download and play\n volume?: number; // Volume level 0.0-1.0, defaults to 1.0\n stopOtherAudio?: boolean; // Whether to stop other audio playback, defaults to true\n /**\n * Track ID for audio playback (defaults to 0)\n * - 0: speaker (default audio playback)\n * - 1: app_audio (app-specific audio)\n * - 2: tts (text-to-speech audio)\n * Use different track IDs to play multiple audio streams simultaneously (mixing)\n */\n trackId?: number;\n}\n\n/**\n * Audio stop request from App\n */\nexport interface AudioStopRequest extends BaseMessage {\n type: AppToCloudMessageType.AUDIO_STOP_REQUEST;\n packageName: string;\n /**\n * Track ID to stop (optional)\n * 0: speaker (default audio playback)\n * 1: app_audio (app-specific audio)\n * 2: tts (text-to-speech audio)\n * If omitted, stops all tracks\n */\n trackId?: number;\n sessionId?: string; // Session ID for routing\n}\n\n/**\n * Audio output stream start request from App.\n * Creates an HTTP streaming relay on the cloud. The cloud responds with\n * AUDIO_STREAM_READY containing the relay URL. The SDK then sends binary\n * WS frames (streamId header + MP3 data) and tells the phone to play\n * the relay URL via AUDIO_PLAY_REQUEST.\n */\nexport interface AudioStreamStart extends BaseMessage {\n type: AppToCloudMessageType.AUDIO_STREAM_START;\n packageName: string;\n sessionId: string;\n streamId: string;\n /** MIME type of the audio being streamed (default: audio/mpeg) */\n contentType?: string;\n}\n\n/**\n * Audio output stream end signal from App.\n * Tells the cloud to close the HTTP relay response for this stream.\n */\nexport interface AudioStreamEnd extends BaseMessage {\n type: AppToCloudMessageType.AUDIO_STREAM_END;\n packageName: string;\n sessionId: string;\n streamId: string;\n}\n\n/**\n * ROI crop position for camera FOV control\n */\nexport type CameraRoiPosition = \"center\" | \"top\" | \"bottom\";\n\n/**\n * Camera FOV set request from App\n */\nexport interface CameraFovSetRequest extends BaseMessage {\n type: AppToCloudMessageType.CAMERA_FOV_SET;\n packageName: string;\n sessionId: string;\n requestId: string;\n /** Field of view in degrees (82-118). 118 means no crop (full sensor). */\n fov: number;\n /** ROI crop position. Ignored when fov is 118. Defaults to \"center\". */\n roiPosition: CameraRoiPosition;\n}\n\n/**\n * WiFi setup request from App\n */\nexport interface RequestWifiSetup extends BaseMessage {\n type: AppToCloudMessageType.REQUEST_WIFI_SETUP;\n packageName: string;\n sessionId: string;\n reason?: string;\n}\n\n/**\n * Ownership release message from App\n * Sent before intentional disconnect to signal clean handoff (no resurrection needed)\n */\nexport interface OwnershipReleaseMessage extends BaseMessage {\n type: AppToCloudMessageType.OWNERSHIP_RELEASE;\n packageName: string;\n sessionId: string;\n reason: \"switching_clouds\" | \"clean_shutdown\" | \"user_logout\";\n}\n\n/**\n * Union type for all messages from Apps to cloud\n */\nexport type AppToCloudMessage =\n | AppConnectionInit\n | AppReconnect\n | AppSubscriptionUpdate\n | AppLocationPollRequest\n | DisplayRequest\n | PhotoRequest\n | RgbLedControlRequest\n | CameraFovSetRequest\n | AudioPlayRequest\n | AudioStopRequest\n | AudioStreamStart\n | AudioStreamEnd\n | StreamRequest\n | StreamStopRequest\n | ManagedStreamRequest\n | ManagedStreamStopRequest\n | StreamStatusCheckRequest\n | DashboardContentUpdate\n | DashboardModeChange\n | DashboardSystemUpdate\n | RequestWifiSetup\n // Session lifecycle\n | OwnershipReleaseMessage\n // New App-to-App communication messages\n | AppBroadcastMessage\n | AppDirectMessage\n | AppUserDiscovery\n | AppRoomJoin\n | AppRoomLeave\n // Telemetry response (for incident debugging)\n | TelemetryResponse;\n\n/**\n * Type guard to check if a message is a App connection init\n */\nexport function isAppConnectionInit(message: AppToCloudMessage): message is AppConnectionInit {\n return message.type === AppToCloudMessageType.CONNECTION_INIT;\n}\n\n/**\n * Type guard to check if a message is a App reconnect request\n */\nexport function isAppReconnect(message: AppToCloudMessage): message is AppReconnect {\n return message.type === AppToCloudMessageType.RECONNECT;\n}\n\n/**\n * Type guard to check if a message is a App subscription update\n */\nexport function isAppSubscriptionUpdate(message: AppToCloudMessage): message is AppSubscriptionUpdate {\n return message.type === AppToCloudMessageType.SUBSCRIPTION_UPDATE;\n}\n\n/**\n * Type guard to check if a message is a App display request\n */\nexport function isDisplayRequest(message: AppToCloudMessage): message is DisplayRequest {\n return message.type === AppToCloudMessageType.DISPLAY_REQUEST;\n}\n\n/**\n * Type guard to check if a message is a App photo request\n */\nexport function isPhotoRequest(message: AppToCloudMessage): message is PhotoRequest {\n return message.type === AppToCloudMessageType.PHOTO_REQUEST;\n}\n\n/**\n * Type guard to check if a message is a RGB LED control request\n */\nexport function isRgbLedControlRequest(message: AppToCloudMessage): message is RgbLedControlRequest {\n return message.type === AppToCloudMessageType.RGB_LED_CONTROL;\n}\n\n/**\n * Type guard to check if a message is a camera FOV set request\n */\nexport function isCameraFovSetRequest(message: AppToCloudMessage): message is CameraFovSetRequest {\n return message.type === AppToCloudMessageType.CAMERA_FOV_SET;\n}\n\n/**\n * Type guard to check if a message is a App audio play request\n */\nexport function isAudioPlayRequest(message: AppToCloudMessage): message is AudioPlayRequest {\n return message.type === AppToCloudMessageType.AUDIO_PLAY_REQUEST;\n}\n\n/**\n * Type guard to check if a message is a App audio stop request\n */\nexport function isAudioStopRequest(message: AppToCloudMessage): message is AudioStopRequest {\n return message.type === AppToCloudMessageType.AUDIO_STOP_REQUEST;\n}\n\n/**\n * Type guard to check if a message is a dashboard content update\n */\nexport function isDashboardContentUpdate(message: AppToCloudMessage): message is DashboardContentUpdate {\n return message.type === AppToCloudMessageType.DASHBOARD_CONTENT_UPDATE;\n}\n\n/**\n * Type guard to check if a message is a dashboard mode change\n */\nexport function isDashboardModeChange(message: AppToCloudMessage): message is DashboardModeChange {\n return message.type === AppToCloudMessageType.DASHBOARD_MODE_CHANGE;\n}\n\n/**\n * Type guard to check if a message is a dashboard system update\n */\nexport function isDashboardSystemUpdate(message: AppToCloudMessage): message is DashboardSystemUpdate {\n return message.type === AppToCloudMessageType.DASHBOARD_SYSTEM_UPDATE;\n}\n\n/**\n * Type guard to check if a message is a managed stream request\n */\nexport function isManagedStreamRequest(message: AppToCloudMessage): message is ManagedStreamRequest {\n return message.type === AppToCloudMessageType.MANAGED_STREAM_REQUEST;\n}\n\n/**\n * Type guard to check if a message is a managed stream stop request\n */\nexport function isManagedStreamStopRequest(message: AppToCloudMessage): message is ManagedStreamStopRequest {\n return message.type === AppToCloudMessageType.MANAGED_STREAM_STOP;\n}\n\n//===========================================================\n// App-to-App Communication Messages\n//===========================================================\n\n/**\n * Broadcast message to all users with the same App active\n */\nexport interface AppBroadcastMessage extends BaseMessage {\n type: AppToCloudMessageType.APP_BROADCAST_MESSAGE;\n packageName: string;\n sessionId: string;\n payload: any;\n messageId: string;\n senderUserId: string;\n}\n\n/**\n * Direct message to a specific user with the same App active\n */\nexport interface AppDirectMessage extends BaseMessage {\n type: AppToCloudMessageType.APP_DIRECT_MESSAGE;\n packageName: string;\n sessionId: string;\n targetUserId: string;\n payload: any;\n messageId: string;\n senderUserId: string;\n}\n\n/**\n * Request to discover other users with the same App active\n */\nexport interface AppUserDiscovery extends BaseMessage {\n type: AppToCloudMessageType.APP_USER_DISCOVERY;\n packageName: string;\n sessionId: string;\n includeUserProfiles?: boolean;\n}\n\n/**\n * Join a communication room for group messaging\n */\nexport interface AppRoomJoin extends BaseMessage {\n type: AppToCloudMessageType.APP_ROOM_JOIN;\n packageName: string;\n sessionId: string;\n roomId: string;\n roomConfig?: {\n maxUsers?: number;\n isPrivate?: boolean;\n metadata?: any;\n };\n}\n\n/**\n * Leave a communication room\n */\nexport interface AppRoomLeave extends BaseMessage {\n type: AppToCloudMessageType.APP_ROOM_LEAVE;\n packageName: string;\n sessionId: string;\n roomId: string;\n}\n\n/**\n * Type guard to check if a message is a stream request\n */\nexport function isStreamRequest(message: AppToCloudMessage): message is StreamRequest {\n return message.type === AppToCloudMessageType.STREAM_REQUEST;\n}\n\n/**\n * Type guard to check if a message is a stream stop request\n */\nexport function isStreamStopRequest(message: AppToCloudMessage): message is StreamStopRequest {\n return message.type === AppToCloudMessageType.STREAM_STOP;\n}\n\n/**\n * Type guard to check if a message is an ownership release message\n */\nexport function isOwnershipRelease(message: AppToCloudMessage): message is OwnershipReleaseMessage {\n return message.type === AppToCloudMessageType.OWNERSHIP_RELEASE;\n}\n\n//===========================================================\n// Telemetry messages (for incident debugging)\n//===========================================================\n\n/**\n * Telemetry log entry from an App server\n */\nexport interface TelemetryLogEntry {\n timestamp: number;\n level: \"debug\" | \"info\" | \"warn\" | \"error\";\n message: string;\n source?: string;\n data?: unknown;\n}\n\n/**\n * Telemetry response from App server to cloud.\n * Sent in response to REQUEST_TELEMETRY message.\n */\nexport interface TelemetryResponse extends BaseMessage {\n type: AppToCloudMessageType.TELEMETRY_RESPONSE;\n incidentId: string;\n packageName: string;\n logs: TelemetryLogEntry[];\n /** Metadata about the telemetry collection */\n metadata?: {\n bufferSize?: number;\n oldestEntryMs?: number;\n newestEntryMs?: number;\n };\n}\n\n/**\n * Type guard to check if a message is a telemetry response\n */\nexport function isTelemetryResponse(message: AppToCloudMessage): message is TelemetryResponse {\n return message.type === AppToCloudMessageType.TELEMETRY_RESPONSE;\n}\n",
12
+ "// src/messages/cloud-to-app.ts\n\nimport { BaseMessage } from \"./base\";\nimport { CloudToAppMessageType, GlassesToCloudMessageType } from \"../message-types\";\nimport { ExtendedStreamType, StreamType } from \"../streams\";\nimport type { AppSettings, AppConfig } from \"../models\";\nimport type { DashboardMode } from \"../dashboard\";\nimport type { Capabilities } from \"../capabilities\";\nimport type {\n LocationUpdate,\n CalendarEvent,\n StreamStatus,\n PhotoResponse,\n RgbLedControlResponse,\n} from \"./glasses-to-cloud\";\nimport type { AppSession } from \"../../app/session\";\nimport type { GlassesInfo } from \"@mentra/types\";\n\n//===========================================================\n// Responses\n//===========================================================\n\n/**\n * Connection acknowledgment to App\n */\nexport interface AppConnectionAck extends BaseMessage {\n type: CloudToAppMessageType.CONNECTION_ACK;\n settings?: AppSettings;\n mentraosSettings?: Record<string, any>; // MentraOS system settings\n config?: AppConfig; // App config sent from cloud\n capabilities?: Capabilities; // Device capability profile\n}\n\n/**\n * Connection error to App\n */\nexport interface AppConnectionError extends BaseMessage {\n type: CloudToAppMessageType.CONNECTION_ERROR;\n message: string;\n code?: string;\n}\n\nexport interface ReconnectAck extends BaseMessage {\n type: CloudToAppMessageType.RECONNECT_ACK;\n sessionId: string;\n subscriptions?: string[];\n userId?: string;\n email?: string;\n}\n\nexport interface ReconnectRejected extends BaseMessage {\n type: CloudToAppMessageType.RECONNECT_REJECTED;\n code: \"SESSION_NOT_FOUND\" | \"SESSION_STOPPED\" | \"SESSION_EXPIRED\" | \"NOT_RUNNING\" | \"BOOT_TIMEOUT\";\n message: string;\n}\n\nexport interface ReconnectDeferred extends BaseMessage {\n type: CloudToAppMessageType.RECONNECT_DEFERRED;\n code: \"BOOTING\" | \"AWAITING_APP_RESTORE\";\n message: string;\n timeoutMs?: number;\n}\n\n//===========================================================\n// Permission messages\n//===========================================================\n\n/**\n * Permission error detail for a specific stream\n */\nexport interface PermissionErrorDetail {\n /** The stream type that was rejected */\n stream: string;\n /** The permission required for this stream */\n requiredPermission: string;\n /** Detailed message explaining the rejection */\n message: string;\n}\n\n/**\n * Permission error notification to App\n * Sent when subscriptions are rejected due to missing permissions\n */\nexport interface PermissionError extends BaseMessage {\n type: CloudToAppMessageType.PERMISSION_ERROR;\n /** General error message */\n message: string;\n /** Array of details for each rejected stream */\n details: PermissionErrorDetail[];\n}\n\n//===========================================================\n// Updates\n//===========================================================\n\n/**\n * App stopped notification to App\n */\nexport interface AppStopped extends BaseMessage {\n type: CloudToAppMessageType.APP_STOPPED;\n reason: \"user_disabled\" | \"system_stop\" | \"error\";\n message?: string;\n}\n\n/**\n * Settings update to App\n */\nexport interface SettingsUpdate extends BaseMessage {\n type: CloudToAppMessageType.SETTINGS_UPDATE;\n packageName: string;\n settings: AppSettings;\n}\n\n/**\n * Device capabilities update to App\n * Sent when the connected glasses model changes or capabilities are updated\n */\nexport interface CapabilitiesUpdate extends BaseMessage {\n type: CloudToAppMessageType.CAPABILITIES_UPDATE;\n capabilities: Capabilities | null;\n modelName: string | null;\n}\n\n/**\n * Device state update to App\n * Sent when any device state changes (WiFi, battery, hotspot, connection, etc.)\n * Apps receive this automatically - no subscription needed\n */\nexport interface DeviceStateUpdate extends BaseMessage {\n type: CloudToAppMessageType.DEVICE_STATE_UPDATE;\n state: Partial<GlassesInfo>; // Only changed fields (or full snapshot)\n fullSnapshot?: boolean; // True on initial connection or reconnection\n timestamp: Date;\n}\n\n/**\n * MentraOS settings update to App\n */\nexport interface MentraosSettingsUpdate extends BaseMessage {\n type: \"augmentos_settings_update\";\n sessionId: string;\n settings: Record<string, any>;\n timestamp: Date;\n}\n\n//===========================================================\n// Audio-related data types\n//===========================================================\n/**\n * Transcription data\n */\nexport interface TranscriptionData extends BaseMessage {\n type: StreamType.TRANSCRIPTION;\n text: string; // The transcribed text\n isFinal: boolean; // Whether this is a final transcription\n utteranceId?: string; // Unique ID for this speech segment - interim and final for same utterance share the same ID\n transcribeLanguage?: string; // Subscription language code (used for routing, e.g., \"en-US\")\n detectedLanguage?: string; // Actual detected language from speech recognition (e.g., \"ja\", \"en\")\n startTime: number; // Start time in milliseconds\n endTime: number; // End time in milliseconds\n speakerId?: string; // ID of the speaker if available (from diarization)\n duration?: number; // Audio duration in milliseconds\n provider?: string; // The transcription provider (e.g., \"azure\", \"soniox\")\n confidence?: number; // Confidence score (0-1)\n metadata?: TranscriptionMetadata; // Token-level metadata (always included)\n}\n\n/**\n * Metadata for transcription containing token-level details\n */\nexport interface TranscriptionMetadata {\n provider: \"soniox\" | \"azure\" | string;\n soniox?: {\n tokens: SonioxToken[];\n };\n azure?: {\n // Azure-specific metadata can be added later\n tokens?: any[];\n };\n alibaba?: {\n // Alibaba-specific metadata can be added later\n tokens?: any[];\n };\n}\n\n/**\n * Soniox token with word-level details\n */\nexport interface SonioxToken {\n text: string;\n startMs?: number;\n endMs?: number;\n confidence: number;\n isFinal: boolean;\n speaker?: string;\n}\n\n/**\n * Translation data\n */\nexport interface TranslationData extends BaseMessage {\n type: StreamType.TRANSLATION;\n text: string; // The transcribed text\n originalText?: string; // The original transcribed text before translation\n isFinal: boolean; // Whether this is a final transcription\n startTime: number; // Start time in milliseconds\n endTime: number; // End time in milliseconds\n speakerId?: string; // ID of the speaker if available\n duration?: number; // Audio duration in milliseconds\n transcribeLanguage?: string; // The language code of the transcribed text\n translateLanguage?: string; // The language code of the translated text\n didTranslate?: boolean; // Whether the text was translated\n provider?: string; // The translation provider (e.g., \"azure\", \"google\")\n confidence?: number; // Confidence score (0-1)\n}\n\n/**\n * Audio chunk data\n */\nexport interface AudioChunk extends BaseMessage {\n type: StreamType.AUDIO_CHUNK;\n arrayBuffer: ArrayBufferLike; // The audio data\n sampleRate?: number; // Audio sample rate (e.g., 16000 Hz)\n}\n\n/**\n * Tool call from cloud to App\n * Represents a tool invocation with filled parameters\n */\nexport interface ToolCall {\n toolId: string; // The ID of the tool that was called\n toolParameters: Record<string, string | number | boolean>; // The parameters of the tool that was called\n timestamp: Date; // Timestamp when the tool was called\n userId: string; // ID of the user who triggered the tool call\n activeSession: AppSession | null;\n}\n\n//===========================================================\n// Stream data\n//===========================================================\n\n/**\n * Stream data to App\n */\nexport interface DataStream extends BaseMessage {\n type: CloudToAppMessageType.DATA_STREAM;\n streamType: ExtendedStreamType;\n data: unknown; // Type depends on the streamType\n}\n\n//===========================================================\n// Dashboard messages\n//===========================================================\n\n/**\n * Dashboard mode changed notification\n */\nexport interface DashboardModeChanged extends BaseMessage {\n type: CloudToAppMessageType.DASHBOARD_MODE_CHANGED;\n mode: DashboardMode;\n}\n\n/**\n * Dashboard always-on state changed notification\n */\nexport interface DashboardAlwaysOnChanged extends BaseMessage {\n type: CloudToAppMessageType.DASHBOARD_ALWAYS_ON_CHANGED;\n enabled: boolean;\n}\n\n/**\n * Standard connection error (for server compatibility)\n */\nexport interface StandardConnectionError extends BaseMessage {\n type: \"connection_error\";\n message: string;\n}\n\n/**\n * Custom message for general-purpose communication (cloud to App)\n */\nexport interface CustomMessage extends BaseMessage {\n type: CloudToAppMessageType.CUSTOM_MESSAGE;\n action: string; // Identifies the specific action/message type\n payload: any; // Custom data payload\n}\n\n/**\n * Output status for a re-stream destination\n */\nexport interface OutputStatus {\n /** The destination URL */\n url: string;\n /** Friendly name if provided */\n name?: string;\n /** Status of this output */\n status: \"active\" | \"error\" | \"stopped\";\n /** Error message if status is error */\n error?: string;\n}\n\n/**\n * Managed RTMP stream status update\n * Sent when managed stream status changes or URLs are ready\n */\nexport interface ManagedStreamStatus extends BaseMessage {\n type: CloudToAppMessageType.MANAGED_STREAM_STATUS;\n status: \"initializing\" | \"preparing\" | \"active\" | \"stopping\" | \"stopped\" | \"error\";\n hlsUrl?: string;\n dashUrl?: string;\n webrtcUrl?: string;\n /** Cloudflare Stream player/preview URL for embedding */\n previewUrl?: string;\n /** Thumbnail image URL */\n thumbnailUrl?: string;\n message?: string;\n streamId?: string;\n /** Status of re-stream outputs if configured */\n outputs?: OutputStatus[];\n}\n\n/**\n * Stream status check response\n * Returns information about any existing streams for the user\n */\nexport interface StreamStatusCheckResponse extends BaseMessage {\n type: CloudToAppMessageType.STREAM_STATUS_CHECK_RESPONSE;\n hasActiveStream: boolean;\n streamInfo?: {\n type: \"managed\" | \"unmanaged\";\n streamId: string;\n status: string;\n createdAt: Date;\n // For managed streams\n hlsUrl?: string;\n dashUrl?: string;\n webrtcUrl?: string;\n previewUrl?: string;\n thumbnailUrl?: string;\n activeViewers?: number;\n // For unmanaged streams\n streamUrl?: string;\n requestingAppId?: string;\n };\n}\n\n/**\n * Audio play response to App\n */\nexport interface AudioPlayResponse extends BaseMessage {\n type: CloudToAppMessageType.AUDIO_PLAY_RESPONSE;\n requestId: string;\n success: boolean;\n error?: string; // Error message (if failed)\n duration?: number; // Duration of audio in milliseconds (if successful)\n}\n\n/**\n * Audio stream ready response from cloud.\n * Sent after the cloud creates an HTTP streaming relay in response to\n * AUDIO_STREAM_START. Contains the relay URL that the phone should play.\n */\nexport interface AudioStreamReady extends BaseMessage {\n type: CloudToAppMessageType.AUDIO_STREAM_READY;\n streamId: string;\n /** HTTP URL the phone can play to receive the chunked MP3 stream */\n streamUrl: string;\n}\n\n/**\n * Request from cloud to App server to send back recent telemetry logs.\n * Sent by the incident pipeline when a user files a bug report.\n */\nexport interface RequestTelemetry extends BaseMessage {\n type: CloudToAppMessageType.REQUEST_TELEMETRY;\n /** Incident ID to correlate the telemetry upload */\n incidentId: string;\n /** JWT upload token — passed back in the TelemetryResponse so cloud can verify */\n uploadToken: string;\n /** How many milliseconds of recent logs to include (e.g. 300_000 = last 5 min) */\n windowMs: number;\n}\n\n/**\n * Union type for all messages from cloud to Apps\n */\nexport type CloudToAppMessage =\n | AppConnectionAck\n | AppConnectionError\n | ReconnectAck\n | ReconnectRejected\n | ReconnectDeferred\n | StandardConnectionError\n | DataStream\n | AppStopped\n | SettingsUpdate\n | CapabilitiesUpdate\n | DeviceStateUpdate\n | TranscriptionData\n | TranslationData\n | AudioChunk\n | LocationUpdate\n | CalendarEvent\n | PhotoResponse\n | DashboardModeChanged\n | DashboardAlwaysOnChanged\n | CustomMessage\n | ManagedStreamStatus\n | StreamStatusCheckResponse\n | MentraosSettingsUpdate\n // New App-to-App communication response messages\n | AppMessageReceived\n | AppUserJoined\n | AppUserLeft\n | AppRoomUpdated\n | AppDirectMessageResponse\n | StreamStatus\n | PhotoResponse\n | RgbLedControlResponse\n | PermissionError\n | AudioPlayResponse\n | AudioStreamReady\n | RequestTelemetry;\n\n//===========================================================\n// Type guards\n//===========================================================\n\nexport function isAppConnectionAck(message: CloudToAppMessage): message is AppConnectionAck {\n return message.type === CloudToAppMessageType.CONNECTION_ACK;\n}\n\nexport function isAppConnectionError(message: CloudToAppMessage): message is AppConnectionError {\n return message.type === CloudToAppMessageType.CONNECTION_ERROR || (message as any).type === \"connection_error\";\n}\n\nexport function isReconnectAck(message: CloudToAppMessage): message is ReconnectAck {\n return message.type === CloudToAppMessageType.RECONNECT_ACK;\n}\n\nexport function isReconnectRejected(message: CloudToAppMessage): message is ReconnectRejected {\n return message.type === CloudToAppMessageType.RECONNECT_REJECTED;\n}\n\nexport function isReconnectDeferred(message: CloudToAppMessage): message is ReconnectDeferred {\n return message.type === CloudToAppMessageType.RECONNECT_DEFERRED;\n}\n\nexport function isAppStopped(message: CloudToAppMessage): message is AppStopped {\n return message.type === CloudToAppMessageType.APP_STOPPED;\n}\n\nexport function isSettingsUpdate(message: CloudToAppMessage): message is SettingsUpdate {\n return message.type === CloudToAppMessageType.SETTINGS_UPDATE;\n}\n\nexport function isCapabilitiesUpdate(message: CloudToAppMessage): message is CapabilitiesUpdate {\n return message.type === CloudToAppMessageType.CAPABILITIES_UPDATE;\n}\n\nexport function isDeviceStateUpdate(message: CloudToAppMessage): message is DeviceStateUpdate {\n return message.type === CloudToAppMessageType.DEVICE_STATE_UPDATE;\n}\n\nexport function isDataStream(message: CloudToAppMessage): message is DataStream {\n return message.type === CloudToAppMessageType.DATA_STREAM;\n}\n\nexport function isAudioChunk(message: CloudToAppMessage): message is AudioChunk {\n return message.type === StreamType.AUDIO_CHUNK;\n}\n\nexport function isDashboardModeChanged(message: CloudToAppMessage): message is DashboardModeChanged {\n return message.type === CloudToAppMessageType.DASHBOARD_MODE_CHANGED;\n}\n\nexport function isDashboardAlwaysOnChanged(message: CloudToAppMessage): message is DashboardAlwaysOnChanged {\n return message.type === CloudToAppMessageType.DASHBOARD_ALWAYS_ON_CHANGED;\n}\n\nexport function isManagedStreamStatus(message: CloudToAppMessage): message is ManagedStreamStatus {\n return message.type === CloudToAppMessageType.MANAGED_STREAM_STATUS;\n}\n\nexport function isStreamStatus(message: CloudToAppMessage): message is StreamStatus {\n return message.type === GlassesToCloudMessageType.STREAM_STATUS;\n}\n\nexport function isPhotoResponse(message: CloudToAppMessage): message is PhotoResponse {\n return message.type === GlassesToCloudMessageType.PHOTO_RESPONSE;\n}\n\nexport function isRgbLedControlResponse(message: CloudToAppMessage): message is RgbLedControlResponse {\n return message.type === GlassesToCloudMessageType.RGB_LED_CONTROL_RESPONSE;\n}\n\nexport function isStreamStatusCheckResponse(message: CloudToAppMessage): message is StreamStatusCheckResponse {\n return message.type === CloudToAppMessageType.STREAM_STATUS_CHECK_RESPONSE;\n}\n\nexport function isAudioPlayResponse(message: CloudToAppMessage): message is AudioPlayResponse {\n return message.type === CloudToAppMessageType.AUDIO_PLAY_RESPONSE;\n}\n\nexport function isRequestTelemetry(message: CloudToAppMessage): message is RequestTelemetry {\n return message.type === CloudToAppMessageType.REQUEST_TELEMETRY;\n}\n\n// New type guards for App-to-App communication\nexport function isAppMessageReceived(message: CloudToAppMessage): message is AppMessageReceived {\n return message.type === CloudToAppMessageType.APP_MESSAGE_RECEIVED;\n}\n\nexport function isAppUserJoined(message: CloudToAppMessage): message is AppUserJoined {\n return message.type === CloudToAppMessageType.APP_USER_JOINED;\n}\n\nexport function isAppUserLeft(message: CloudToAppMessage): message is AppUserLeft {\n return message.type === CloudToAppMessageType.APP_USER_LEFT;\n}\n\n//===========================================================\n// App-to-App Communication Response Messages\n//===========================================================\n\n/**\n * Message received from another App user\n */\nexport interface AppMessageReceived extends BaseMessage {\n type: CloudToAppMessageType.APP_MESSAGE_RECEIVED;\n payload: any;\n messageId: string;\n senderUserId: string;\n senderSessionId: string;\n roomId?: string;\n}\n\n/**\n * Notification that a user joined the App\n */\nexport interface AppUserJoined extends BaseMessage {\n type: CloudToAppMessageType.APP_USER_JOINED;\n userId: string;\n sessionId: string;\n joinedAt: Date;\n userProfile?: any;\n roomId?: string;\n}\n\n/**\n * Notification that a user left the App\n */\nexport interface AppUserLeft extends BaseMessage {\n type: CloudToAppMessageType.APP_USER_LEFT;\n userId: string;\n sessionId: string;\n leftAt: Date;\n roomId?: string;\n}\n\n/**\n * Room status update (members, config changes, etc.)\n */\nexport interface AppRoomUpdated extends BaseMessage {\n type: CloudToAppMessageType.APP_ROOM_UPDATED;\n roomId: string;\n updateType: \"user_joined\" | \"user_left\" | \"config_changed\" | \"room_closed\";\n roomData: {\n memberCount: number;\n maxUsers?: number;\n isPrivate?: boolean;\n metadata?: any;\n };\n}\n\n/**\n * Response to a direct message attempt\n */\nexport interface AppDirectMessageResponse extends BaseMessage {\n type: CloudToAppMessageType.APP_DIRECT_MESSAGE_RESPONSE;\n messageId: string;\n success: boolean;\n error?: string;\n targetUserId: string;\n}\n\n//===========================================================\n// Cloud-to-Sdk Communication Response Messages\n//===========================================================\n\n/**\n * Permission data structures for permission fetch responses\n */\nexport interface Permission {\n type: string; // or a union/enum if you want stricter typing\n description: string;\n _id: string;\n}\n\n/**\n * Package permissions response structure\n */\nexport interface PackagePermissions {\n packageName: string;\n permissions: Permission[];\n}\n",
7
13
  "// TODO:\n/**\n * Dashboard API Types\n *\n * Type definitions for the dashboard functionality in the SDK.\n */\nimport { Layout } from '../layouts';\nimport { AppToCloudMessageType } from '../message-types';\n\n/**\n * Dashboard modes supported by the system\n */\nexport enum DashboardMode {\n MAIN = 'main', // Full dashboard experience\n EXPANDED = 'expanded', // More space for App content\n // ALWAYS_ON = 'always_on' // Persistent minimal dashboard\n}\n\n/**\n * Dashboard API for the system dashboard App\n */\nexport interface DashboardSystemAPI {\n /**\n * Set content for the top left section of the dashboard\n * @param content Content to display\n */\n setTopLeft(content: string): void;\n\n /**\n * Set content for the top right section of the dashboard\n * @param content Content to display\n */\n setTopRight(content: string): void;\n\n /**\n * Set content for the bottom left section of the dashboard\n * @param content Content to display\n */\n setBottomLeft(content: string): void;\n\n /**\n * Set content for the bottom right section of the dashboard\n * @param content Content to display\n */\n setBottomRight(content: string): void;\n\n /**\n * Set the current dashboard mode\n * @param mode Dashboard mode to set\n */\n setViewMode(mode: DashboardMode): void;\n}\n\n/**\n * Dashboard API for all Apps\n */\nexport interface DashboardContentAPI {\n /**\n * Write content to dashboard\n * @param content Content to display\n * @param targets Optional list of dashboard modes to target\n */\n write(content: string, targets?: DashboardMode[]): void;\n\n /**\n * Write content to main dashboard mode\n * @param content Content to display\n */\n writeToMain(content: string): void;\n\n /**\n * Write content to expanded dashboard mode\n * @param content Text content to display\n */\n writeToExpanded(content: string): void;\n\n /**\n * Write content to always-on dashboard mode\n * @param content Content to display\n */\n // writeToAlwaysOn(content: string): void;\n\n /**\n * Get current active dashboard mode\n * @returns Promise resolving to current mode or 'none'\n */\n getCurrentMode(): Promise<DashboardMode | 'none'>;\n\n /**\n * Check if always-on dashboard is enabled\n * @returns Promise resolving to boolean\n */\n // isAlwaysOnEnabled(): Promise<boolean>;\n\n /**\n * Register for mode change notifications\n * @param callback Function to call when mode changes\n * @returns Cleanup function to unregister callback\n */\n onModeChange(callback: (mode: DashboardMode | 'none') => void): () => void;\n\n /**\n * Register for always-on mode change notifications\n * @param callback Function to call when always-on mode changes\n * @returns Cleanup function to unregister callback\n */\n // onAlwaysOnChange(callback: (enabled: boolean) => void): () => void;\n}\n\n/**\n * Dashboard API exposed on AppSession\n */\nexport interface DashboardAPI {\n /**\n * System dashboard API (only available for system dashboard App)\n */\n system?: DashboardSystemAPI;\n\n /**\n * Content API (available to all Apps)\n */\n content: DashboardContentAPI;\n}\n\n/**\n * Message to update dashboard content\n */\nexport interface DashboardContentUpdate {\n type: AppToCloudMessageType.DASHBOARD_CONTENT_UPDATE;\n packageName: string;\n sessionId: string;\n content: string;\n modes: DashboardMode[];\n timestamp: Date;\n}\n\n/**\n * Message for dashboard mode change\n */\nexport interface DashboardModeChange {\n type: AppToCloudMessageType.DASHBOARD_MODE_CHANGE;\n packageName: string;\n sessionId: string;\n mode: DashboardMode;\n timestamp: Date;\n}\n\n/**\n * Message to update system dashboard content\n */\nexport interface DashboardSystemUpdate {\n type: AppToCloudMessageType.DASHBOARD_SYSTEM_UPDATE;\n packageName: string;\n sessionId: string;\n section: 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight';\n content: string;\n timestamp: Date;\n}\n\n/**\n * Union type of all dashboard message types\n */\nexport type DashboardMessage =\n | DashboardContentUpdate\n | DashboardModeChange\n | DashboardSystemUpdate;",
14
+ "// src/enums.ts\n\n/**\n * Types of Third-Party Applications (Apps)\n */\nexport enum AppType {\n SYSTEM_DASHBOARD = \"system_dashboard\", // Special UI placement, system functionality\n BACKGROUND = \"background\", // Can temporarily take control of display\n STANDARD = \"standard\", // Regular App (default) only one standard app can run at a time. starting a standard App will close any other standard App that is running.\n}\n\n/**\n * Types of layouts for displaying content\n */\nexport enum LayoutType {\n TEXT_WALL = \"text_wall\",\n DOUBLE_TEXT_WALL = \"double_text_wall\",\n DASHBOARD_CARD = \"dashboard_card\",\n REFERENCE_CARD = \"reference_card\",\n BITMAP_VIEW = \"bitmap_view\",\n BITMAP_ANIMATION = \"bitmap_animation\",\n CLEAR_VIEW = \"clear_view\",\n}\n\n/**\n * Types of views for displaying content\n */\nexport enum ViewType {\n DASHBOARD = \"dashboard\", // Regular dashboard (main/expanded)\n // ALWAYS_ON = \"always_on\", // Persistent overlay dashboard\n MAIN = \"main\", // Regular app content\n}\n\n// Types for AppSettings\nexport enum AppSettingType {\n TOGGLE = \"toggle\",\n TEXT = \"text\",\n SELECT = \"select\",\n SLIDER = \"slider\",\n GROUP = \"group\",\n TEXT_NO_SAVE_BUTTON = \"text_no_save_button\",\n SELECT_WITH_SEARCH = \"select_with_search\",\n MULTISELECT = \"multiselect\",\n TITLE_VALUE = \"titleValue\",\n NUMERIC_INPUT = \"numeric_input\",\n TIME_PICKER = \"time_picker\",\n}\n// | { type: \"toggle\"; key: string; label: string; defaultValue: boolean }\n// | { type: \"text\"; key: string; label: string; defaultValue?: string }\n// | { type: \"select\"; key: string; label: string; options: { label: string; value: string }[]; defaultValue?: string };\n\n/**\n * Types of hardware components that apps can require\n */\nexport enum HardwareType {\n CAMERA = \"CAMERA\",\n DISPLAY = \"DISPLAY\",\n MICROPHONE = \"MICROPHONE\",\n SPEAKER = \"SPEAKER\",\n IMU = \"IMU\",\n BUTTON = \"BUTTON\",\n LIGHT = \"LIGHT\",\n WIFI = \"WIFI\",\n}\n\n/**\n * Levels of hardware requirements\n */\nexport enum HardwareRequirementLevel {\n REQUIRED = \"REQUIRED\", // App cannot function without this hardware\n OPTIONAL = \"OPTIONAL\", // App has enhanced features with this hardware\n}\n",
15
+ "// @mentra/sdk\n// packages/sdk/types/src/models.ts - Core models\n\nimport { AppSettingType, AppType, HardwareType, HardwareRequirementLevel } from \"./enums\";\n\n// Tool parameter type definition\nexport interface ToolParameterSchema {\n type: \"string\" | \"number\" | \"boolean\";\n description: string;\n enum?: string[];\n required?: boolean;\n}\n\n// Tool schema definition for Apps\nexport interface ToolSchema {\n id: string;\n description: string;\n activationPhrases?: string[];\n parameters?: Record<string, ToolParameterSchema>;\n}\n\n/**\n * Developer profile information\n */\nexport interface DeveloperProfile {\n company?: string;\n website?: string;\n contactEmail?: string;\n description?: string;\n logo?: string;\n}\n\n// Define PermissionType enum with legacy support\nexport enum PermissionType {\n MICROPHONE = \"MICROPHONE\",\n LOCATION = \"LOCATION\",\n BACKGROUND_LOCATION = \"BACKGROUND_LOCATION\",\n CALENDAR = \"CALENDAR\",\n CAMERA = \"CAMERA\",\n\n // Legacy notification permission (backward compatibility)\n NOTIFICATIONS = \"NOTIFICATIONS\",\n\n // New granular notification permissions\n READ_NOTIFICATIONS = \"READ_NOTIFICATIONS\",\n POST_NOTIFICATIONS = \"POST_NOTIFICATIONS\",\n\n ALL = \"ALL\",\n}\n\n// Legacy permission mapping for backward compatibility\nexport const LEGACY_PERMISSION_MAP = new Map<PermissionType, PermissionType[]>([\n [PermissionType.NOTIFICATIONS, [PermissionType.READ_NOTIFICATIONS]],\n]);\n\n// Permission interface\nexport interface Permission {\n type: PermissionType;\n description?: string;\n}\n\n/**\n * Hardware requirement for an app\n */\nexport interface HardwareRequirement {\n type: HardwareType;\n level: HardwareRequirementLevel;\n description?: string; // Why this hardware is needed\n}\n\n/**\n * Preview image orientation\n */\nexport type PhotoOrientation = \"landscape\" | \"portrait\";\n\n/**\n * Preview image for an app in the app store\n */\nexport interface PreviewImage {\n url: string;\n imageId: string;\n orientation: PhotoOrientation;\n order: number;\n}\n\n/**\n * Base interface for applications\n */\nexport interface AppI {\n packageName: string;\n name: string;\n publicUrl: string; // Base URL of the app server\n isSystemApp?: boolean; // Is this a system app?\n uninstallable?: boolean; // Can the app be uninstalled?\n\n webviewURL?: string; // URL for phone UI\n logoURL: string;\n appType: AppType; // Type of app\n appStoreId?: string; // Which app store registered this app\n\n /**\n * @deprecated Use organizationId instead. Will be removed after migration.\n */\n developerId?: string; // ID of the developer who created the app\n organizationId?: any; // ID of the organization that owns this app\n\n // Auth\n hashedEndpointSecret?: string;\n hashedApiKey?: string;\n\n // App details\n permissions?: Permission[];\n description?: string;\n version?: string;\n settings?: AppSettings;\n tools?: ToolSchema[];\n\n /**\n * Hardware requirements for the app\n * If not specified, app is assumed to work with any hardware\n */\n hardwareRequirements?: HardwareRequirement[];\n\n /**\n * Preview images for the app store\n */\n previewImages?: PreviewImage[];\n\n isPublic?: boolean;\n appStoreStatus?: \"DEVELOPMENT\" | \"SUBMITTED\" | \"REJECTED\" | \"PUBLISHED\";\n}\n\n/**\n * Base interface for all app settings\n */\nexport interface BaseAppSetting {\n key: string;\n label: string;\n value?: any; // User's selected value\n defaultValue?: any; // System default\n}\n\n/**\n * Setting types for applications\n */\nexport type AppSetting =\n | (BaseAppSetting & {\n type: AppSettingType.TOGGLE;\n defaultValue: boolean;\n value?: boolean;\n })\n | (BaseAppSetting & {\n type: AppSettingType.TEXT;\n defaultValue?: string;\n value?: string;\n })\n | (BaseAppSetting & {\n type: AppSettingType.TEXT_NO_SAVE_BUTTON;\n defaultValue?: string;\n value?: string;\n maxLines?: number;\n })\n | (BaseAppSetting & {\n type: AppSettingType.SELECT;\n options: { label: string; value: any }[];\n defaultValue?: any;\n value?: any;\n })\n | (BaseAppSetting & {\n type: AppSettingType.SELECT_WITH_SEARCH;\n options: { label: string; value: any }[];\n defaultValue?: any;\n value?: any;\n })\n | (BaseAppSetting & {\n type: AppSettingType.MULTISELECT;\n options: { label: string; value: any }[];\n defaultValue?: any[];\n value?: any[];\n })\n | (BaseAppSetting & {\n type: AppSettingType.SLIDER;\n min: number;\n max: number;\n defaultValue: number;\n value?: number;\n })\n | (BaseAppSetting & {\n type: AppSettingType.NUMERIC_INPUT;\n min?: number;\n max?: number;\n step?: number;\n placeholder?: string;\n defaultValue?: number;\n value?: number;\n })\n | (BaseAppSetting & {\n type: AppSettingType.TIME_PICKER;\n showSeconds?: boolean;\n defaultValue?: number; // Total seconds\n value?: number; // Total seconds\n })\n | (BaseAppSetting & {\n type: AppSettingType.GROUP;\n title: string;\n })\n | (BaseAppSetting & {\n type: AppSettingType.TITLE_VALUE;\n label: string;\n value: any;\n key?: never; // TITLE_VALUE settings don't need keys since they're display-only\n });\n\nexport type AppSettings = AppSetting[];\n\n/**\n * App configuration file structure\n * Represents the schema in app_config.json\n */\nexport interface AppConfig {\n name: string;\n description: string;\n version: string;\n settings: AppSetting[];\n tools: ToolSchema[];\n}\n\n/**\n * Validate a App configuration object\n * @param config Object to validate\n * @returns True if the config is valid\n */\nexport function validateAppConfig(config: any): config is AppConfig {\n if (!config || typeof config !== \"object\") return false;\n\n // Check required string properties\n if (typeof config.name !== \"string\" || typeof config.description !== \"string\" || typeof config.version !== \"string\") {\n return false;\n }\n\n // Check settings array\n if (!Array.isArray(config.settings)) return false;\n\n // Validate each setting\n return config.settings.every((setting: any) => {\n // Group settings just need a title\n if (setting.type === \"group\") {\n return typeof setting.title === \"string\";\n }\n\n // TITLE_VALUE settings just need label and value\n if (setting.type === \"titleValue\") {\n return typeof setting.label === \"string\" && \"value\" in setting;\n }\n\n // Regular settings need key and label\n if (typeof setting.key !== \"string\" || typeof setting.label !== \"string\") {\n return false;\n }\n\n // Type-specific validation\n switch (setting.type) {\n case AppSettingType.TOGGLE:\n return typeof setting.defaultValue === \"boolean\";\n\n case AppSettingType.TEXT:\n case AppSettingType.TEXT_NO_SAVE_BUTTON:\n return setting.defaultValue === undefined || typeof setting.defaultValue === \"string\";\n\n case AppSettingType.SELECT:\n case AppSettingType.SELECT_WITH_SEARCH:\n return (\n Array.isArray(setting.options) &&\n setting.options.every((opt: any) => typeof opt.label === \"string\" && \"value\" in opt)\n );\n\n case AppSettingType.MULTISELECT:\n return (\n Array.isArray(setting.options) &&\n setting.options.every((opt: any) => typeof opt.label === \"string\" && \"value\" in opt) &&\n (setting.defaultValue === undefined || Array.isArray(setting.defaultValue))\n );\n\n case AppSettingType.SLIDER:\n return (\n typeof setting.defaultValue === \"number\" &&\n typeof setting.min === \"number\" &&\n typeof setting.max === \"number\" &&\n setting.min <= setting.max\n );\n\n case AppSettingType.NUMERIC_INPUT:\n return (\n (setting.defaultValue === undefined || typeof setting.defaultValue === \"number\") &&\n (setting.min === undefined || typeof setting.min === \"number\") &&\n (setting.max === undefined || typeof setting.max === \"number\") &&\n (setting.step === undefined || typeof setting.step === \"number\") &&\n (setting.placeholder === undefined || typeof setting.placeholder === \"string\")\n );\n\n case AppSettingType.TIME_PICKER:\n return (\n (setting.defaultValue === undefined || typeof setting.defaultValue === \"number\") &&\n (setting.showSeconds === undefined || typeof setting.showSeconds === \"boolean\")\n );\n\n case AppSettingType.GROUP:\n return typeof setting.title === \"string\";\n\n case AppSettingType.TITLE_VALUE:\n return typeof setting.label === \"string\" && \"value\" in setting;\n\n default:\n return false;\n }\n });\n}\n\n/**\n * Transcript segment for speech processing\n */\nexport interface TranscriptSegment {\n speakerId?: string;\n resultId: string;\n text: string;\n timestamp: Date;\n isFinal: boolean;\n}\n\n/**\n * Complete transcript\n */\nexport interface TranscriptI {\n segments: TranscriptSegment[];\n languageSegments?: Map<string, TranscriptSegment[]>; // Language-indexed map for multi-language support\n}\n",
16
+ "// src/webhook.ts\n\n/**\n * Types of webhook requests that can be sent to Apps\n */\nexport enum WebhookRequestType {\n /** Request to start a App session */\n SESSION_REQUEST = \"session_request\",\n\n /** Request to stop a App session */\n STOP_REQUEST = \"stop_request\",\n}\n\n/**\n * Base interface for all webhook requests\n */\nexport interface BaseWebhookRequest {\n /** Type of webhook request */\n type: WebhookRequestType;\n\n /** Session ID for the request */\n sessionId: string;\n\n /** User ID associated with the session */\n userId: string;\n\n /** Timestamp of the request */\n timestamp: string;\n}\n\n/**\n * Session request webhook\n *\n * Sent to a App when a user starts the App\n */\nexport interface SessionWebhookRequest extends BaseWebhookRequest {\n type: WebhookRequestType.SESSION_REQUEST;\n /**\n * Canonical mini app websocket URL for this app session.\n * New SDK/server paths should prefer this field.\n */\n websocketUrl?: string;\n /**\n * @deprecated Prefer `websocketUrl`.\n */\n mentraOSWebsocketUrl?: string;\n /**\n * @deprecated Prefer `websocketUrl`.\n */\n augmentOSWebsocketUrl?: string;\n}\n\n/**\n * Stop request webhook\n *\n * Sent to a App when a user or the system stops the App\n */\nexport interface StopWebhookRequest extends BaseWebhookRequest {\n type: WebhookRequestType.STOP_REQUEST;\n reason: \"user_disabled\" | \"system_stop\" | \"error\";\n}\n\n/**\n * Union type for all webhook requests\n */\nexport type WebhookRequest = SessionWebhookRequest | StopWebhookRequest;\n\n/**\n * Response to a webhook request\n */\nexport interface WebhookResponse {\n status: \"success\" | \"error\";\n message?: string;\n}\n\n/**\n * Type guard to check if a webhook request is a session request\n */\nexport function isSessionWebhookRequest(request: WebhookRequest): request is SessionWebhookRequest {\n return request.type === WebhookRequestType.SESSION_REQUEST;\n}\n\n/**\n * Type guard to check if a webhook request is a stop request\n */\nexport function isStopWebhookRequest(request: WebhookRequest): request is StopWebhookRequest {\n return request.type === WebhookRequestType.STOP_REQUEST;\n}\n",
17
+ "// src/index.ts\n\nexport * from \"./token\";\n\n// Message type enums\nexport * from \"./message-types\";\n\n// Base message type\nexport * from \"./messages/base\";\n\n// Messages by direction - export everything except the conflicting type guards\nexport * from \"./messages/glasses-to-cloud\";\nexport * from \"./messages/cloud-to-glasses\";\n\n// Export from app-to-cloud excluding isPhotoRequest which conflicts with cloud-to-glasses\nexport {\n // Type guards - all except isPhotoRequest\n isAppConnectionInit,\n isAppReconnect,\n isAppSubscriptionUpdate,\n isDisplayRequest,\n isRgbLedControlRequest,\n isCameraFovSetRequest,\n isAudioPlayRequest,\n isAudioStopRequest,\n isDashboardContentUpdate,\n isDashboardModeChange,\n isDashboardSystemUpdate,\n isManagedStreamRequest,\n isManagedStreamStopRequest,\n isStreamRequest,\n isStreamStopRequest,\n isOwnershipRelease,\n isTelemetryResponse,\n // Export with alias to avoid conflict\n isPhotoRequest as isPhotoRequestFromApp,\n} from \"./messages/app-to-cloud\";\n\n// Type-only exports from app-to-cloud (all interfaces)\nexport type {\n SubscriptionRequest,\n AppConnectionInit,\n AppReconnect,\n AppSubscriptionUpdate,\n PhotoRequest,\n RgbLedControlRequest,\n CameraFovSetRequest,\n CameraRoiPosition,\n StreamRequest,\n StreamStopRequest,\n AppLocationPollRequest,\n RestreamDestination,\n ManagedStreamRequest,\n ManagedStreamStopRequest,\n StreamStatusCheckRequest,\n AudioPlayRequest,\n AudioStopRequest,\n AudioStreamStart,\n AudioStreamEnd,\n AppToCloudMessage,\n AppBroadcastMessage,\n AppDirectMessage,\n AppUserDiscovery,\n AppRoomJoin,\n AppRoomLeave,\n RequestWifiSetup,\n OwnershipReleaseMessage,\n TelemetryLogEntry,\n TelemetryResponse,\n} from \"./messages/app-to-cloud\";\n\n// Export cloud-to-app but exclude the conflicting type guards\nexport {\n // Type guards (excluding isPhotoResponse and isStreamStatus which conflict)\n isAppConnectionAck,\n isAppConnectionError,\n isAppStopped,\n isSettingsUpdate,\n isCapabilitiesUpdate,\n isDataStream,\n isAudioChunk,\n isAudioPlayResponse,\n isDashboardModeChanged,\n isDashboardAlwaysOnChanged,\n isManagedStreamStatus,\n isStreamStatusCheckResponse,\n // Re-export the cloud-to-app versions of these type guards since they're the ones\n // that should be used when dealing with CloudToAppMessage types\n isPhotoResponse as isPhotoResponseFromCloud,\n isStreamStatus as isStreamStatusFromCloud,\n isRgbLedControlResponse as isRgbLedControlResponseFromCloud,\n isRequestTelemetry,\n} from \"./messages/cloud-to-app\";\n\n// Type-only exports from cloud-to-app (all interfaces)\nexport type {\n AppConnectionAck,\n AppConnectionError,\n AppStopped,\n SettingsUpdate as AppSettingsUpdate, // Alias to avoid conflict with cloud-to-glasses SettingsUpdate\n CapabilitiesUpdate,\n DataStream,\n CloudToAppMessage,\n AudioPlayResponse,\n TranslationData,\n ToolCall,\n StandardConnectionError,\n CustomMessage,\n ManagedStreamStatus,\n StreamStatusCheckResponse,\n OutputStatus,\n MentraosSettingsUpdate,\n TranscriptionData,\n TranscriptionMetadata,\n SonioxToken,\n AudioChunk,\n PermissionError,\n PermissionErrorDetail,\n AudioStreamReady,\n RequestTelemetry,\n} from \"./messages/cloud-to-app\";\n\n// Stream types\nexport * from \"./streams\";\n\n// Layout types\nexport * from \"./layouts\";\n\n// Dashboard types\nexport * from \"./dashboard\";\n\n// RTMP streaming types\nexport * from \"./rtmp-stream\";\n\n// Other system enums\nexport * from \"./enums\";\n\n// Core model interfaces\nexport * from \"./models\";\n\n// Webhook interfaces\nexport * from \"./webhooks\";\n\n// Capability Discovery types\nexport * from \"./capabilities\";\n\n// Photo data types\nexport * from \"./photo-data\";\n\n/**\n * WebSocket error information\n */\nexport interface WebSocketError {\n code: string;\n message: string;\n details?: unknown;\n}\n\nimport type { Context } from \"hono\";\n\nimport type { AppSession } from \"../app/session\";\n\n/**\n * Hono Context variables for authenticated requests.\n * Apps should prefer the public auth helpers instead of reading these\n * variables directly from the context.\n */\nexport interface AuthVariables {\n authUserId?: string;\n activeSession: AppSession | null;\n}\n\nexport interface MentraAuthContext {\n userId: string | null;\n session: AppSession | null;\n}\n\nexport type MentraAuthHonoContext = Context<{ Variables: AuthVariables }>;\n\n/**\n * @deprecated Use AuthVariables with Hono context instead\n * This type is kept for backward compatibility during migration\n */\nexport interface AuthenticatedRequest {\n authUserId?: string;\n activeSession: AppSession | null;\n query: Record<string, string | undefined>;\n headers: Record<string, string | undefined>;\n cookies?: Record<string, string>;\n body?: unknown;\n}\n",
18
+ "/**\n * warning.ts\n *\n * Permission warning messages for the MentraOS SDK.\n *\n * Previously used boxen-bordered ASCII art banners with side-by-side layouts.\n * Now returns plain single-line strings that the clean transport formats with\n * color/prefix.\n *\n * The clean logger renders these as:\n * MentraOS ⚠ camera permission required for requestPhoto — enable at https://console.mentra.glass/apps/org.example.myapp/edit\n */\n\n/**\n * Generate a single-line permission warning message.\n *\n * @param permissionName - Human-readable permission name (e.g., \"microphone\", \"camera\")\n * @param funcName - Optional function name that triggered the warning\n * @param packageName - Optional package name for the developer portal link\n * @returns A plain string suitable for `logger.warn()`\n */\nconst createPermissionWarning = (permissionName: string, funcName?: string, packageName?: string): string => {\n const func = funcName ? `${funcName} requires` : \"This function requires\";\n const url = packageName ? ` — enable at https://console.mentra.glass/apps/${packageName}/edit` : \"\";\n return `${func} ${permissionName} permission${url}`;\n};\n\nexport const noMicrophoneWarn = (funcName?: string, packageName?: string): string =>\n createPermissionWarning(\"microphone\", funcName, packageName);\n\nexport const locationWarn = (funcName?: string, packageName?: string): string =>\n createPermissionWarning(\"location\", funcName, packageName);\n\nexport const baackgroundLocationWarn = (funcName?: string, packageName?: string): string =>\n createPermissionWarning(\"background location\", funcName, packageName);\n\nexport const calendarWarn = (funcName?: string, packageName?: string): string =>\n createPermissionWarning(\"calendar\", funcName, packageName);\n\nexport const readNotficationWarn = (funcName?: string, packageName?: string): string =>\n createPermissionWarning(\"read notification\", funcName, packageName);\n\nexport const postNotficationWarn = (funcName?: string, packageName?: string): string =>\n createPermissionWarning(\"post notification\", funcName, packageName);\n\nexport const cameraWarn = (funcName?: string, packageName?: string): string =>\n createPermissionWarning(\"camera\", funcName, packageName);\n",
19
+ "/**\n * permissions-utils.ts\n *\n * Runtime permission validation utilities for the MentraOS SDK.\n *\n * Queries the public permissions API endpoint to check if an app has declared\n * the required permission for a specific feature. If the permission is missing,\n * a warning is logged via the SDK logger (respecting log level settings).\n *\n * Key features:\n * - Single generic `checkPermission()` replaces 7 copy-pasted functions\n * - Accepts an optional pino Logger for SDK-integrated logging\n * - Gracefully handles offline/unreachable endpoints (silent failure)\n * - Non-blocking — allows app execution to continue even if checks fail\n */\nimport type { Logger } from \"pino\";\nimport {\n noMicrophoneWarn,\n locationWarn,\n baackgroundLocationWarn,\n calendarWarn,\n readNotficationWarn,\n postNotficationWarn,\n cameraWarn,\n} from \"../constants/log-messages/warning\";\nimport type { PackagePermissions, Permission } from \"../types/messages/cloud-to-app\";\n\n/**\n * Generic permission checker — replaces 7 copy-pasted functions.\n *\n * Fetches the app's declared permissions from the public API and logs a\n * warning if the required permission is missing. The warning flows through\n * the SDK logger so it respects `logLevel` settings.\n *\n * @param cloudServerUrl - Base URL of the MentraOS Cloud server\n * @param packageName - The app's package name\n * @param permissionType - Permission type to check (e.g., \"MICROPHONE\", \"CAMERA\")\n * @param warnMessageFn - Function that generates the warning message string\n * @param logger - Optional pino logger instance. If omitted, the check is a silent no-op.\n * @param funcName - Optional function name that triggered the check (for the warning message)\n */\nfunction checkPermission(\n cloudServerUrl: string,\n packageName: string,\n permissionType: string,\n warnMessageFn: (funcName?: string, packageName?: string) => string,\n logger?: Logger,\n funcName?: string,\n): void {\n if (!cloudServerUrl || !logger) return;\n\n const permissionsUrl = `${cloudServerUrl}/api/public/permissions/${encodeURIComponent(packageName)}`;\n\n fetch(permissionsUrl)\n .then(async (res) => {\n if (!res.ok) return null;\n const contentType = res.headers.get(\"content-type\");\n if (contentType && contentType.includes(\"application/json\")) {\n return (await res.json()) as PackagePermissions;\n }\n return null;\n })\n .then((data: PackagePermissions | null) => {\n if (data) {\n const hasPermission = data.permissions.some((p: Permission) => p.type === permissionType);\n if (!hasPermission) {\n logger.warn(warnMessageFn(funcName, packageName));\n }\n }\n })\n .catch(() => {\n // Silently fail if endpoint is unreachable — don't block execution\n });\n}\n\n// ─── Public API ──────────────────────────────────────────────────────────────\n//\n// Each function takes an optional logger parameter. If no logger is provided,\n// the permission check is a silent no-op. As call sites are updated to pass\n// the session logger, permission warnings start flowing through the SDK logger\n// and respect log level settings.\n\n/** Check if app has microphone permission, warn if missing */\nexport const microPhoneWarnLog = (\n cloudServerUrl: string,\n packageName: string,\n funcName?: string,\n logger?: Logger,\n): void => {\n checkPermission(cloudServerUrl, packageName, \"MICROPHONE\", noMicrophoneWarn, logger, funcName);\n};\n\n/** Check if app has location permission, warn if missing */\nexport const locationWarnLog = (\n cloudServerUrl: string,\n packageName: string,\n funcName?: string,\n logger?: Logger,\n): void => {\n checkPermission(cloudServerUrl, packageName, \"LOCATION\", locationWarn, logger, funcName);\n};\n\n/** Check if app has background location permission, warn if missing */\nexport const backgroundLocationWarnLog = (\n cloudServerUrl: string,\n packageName: string,\n funcName?: string,\n logger?: Logger,\n): void => {\n checkPermission(cloudServerUrl, packageName, \"BACKGROUND_LOCATION\", baackgroundLocationWarn, logger, funcName);\n};\n\n/** Check if app has calendar permission, warn if missing */\nexport const calendarWarnLog = (\n cloudServerUrl: string,\n packageName: string,\n funcName?: string,\n logger?: Logger,\n): void => {\n checkPermission(cloudServerUrl, packageName, \"CALENDAR\", calendarWarn, logger, funcName);\n};\n\n/** Check if app has read notifications permission, warn if missing */\nexport const readNotificationWarnLog = (\n cloudServerUrl: string,\n packageName: string,\n funcName?: string,\n logger?: Logger,\n): void => {\n checkPermission(cloudServerUrl, packageName, \"READ_NOTIFICATIONS\", readNotficationWarn, logger, funcName);\n};\n\n/** Check if app has post notifications permission, warn if missing */\nexport const postNotificationWarnLog = (\n cloudServerUrl: string,\n packageName: string,\n funcName?: string,\n logger?: Logger,\n): void => {\n checkPermission(cloudServerUrl, packageName, \"POST_NOTIFICATIONS\", postNotficationWarn, logger, funcName);\n};\n\n/** Check if app has camera permission, warn if missing */\nexport const cameraWarnLog = (\n cloudServerUrl: string,\n packageName: string,\n funcName?: string,\n logger?: Logger,\n): void => {\n checkPermission(cloudServerUrl, packageName, \"CAMERA\", cameraWarn, logger, funcName);\n};\n",
20
+ "/**\n * 🎮 Event Manager Module\n */\nimport EventEmitter from \"events\";\nimport type { Logger } from \"pino\";\nimport {\n StreamType,\n ExtendedStreamType,\n AppSettings,\n WebSocketError,\n // Event data types\n ButtonPress,\n HeadPosition,\n PhoneNotification,\n TranscriptionData,\n TranslationData,\n GlassesBatteryUpdate,\n PhoneBatteryUpdate,\n GlassesConnectionState,\n LocationUpdate,\n Vad,\n AudioChunk,\n CalendarEvent,\n VpsCoordinates,\n // Language stream helpers\n createTranscriptionStream,\n isValidLanguageCode,\n createTranslationStream,\n isLanguageStream,\n parseLanguageStream,\n CustomMessage,\n StreamStatus,\n PhotoTaken,\n ManagedStreamStatus,\n PhoneNotificationDismissed,\n Capabilities,\n TouchEvent,\n createTouchEventStream,\n} from \"../../types\";\nimport { DashboardMode } from \"../../types/dashboard\";\nimport { PermissionErrorDetail } from \"../../types/messages/cloud-to-app\";\nimport { calendarWarnLog, microPhoneWarnLog } from \"../../utils/permissions-utils\";\n\n/** 🎯 Type-safe event handler function */\ntype Handler<T> = (data: T) => void;\n\n/** 🔄 System events not tied to streams */\ninterface SystemEvents {\n connected: AppSettings | undefined;\n disconnected:\n | string\n | {\n message: string; // Human-readable close message\n code: number; // WebSocket close code (1000 = normal)\n reason: string; // Reason provided by server\n wasClean: boolean; // Whether this was a clean closure\n permanent?: boolean; // Whether this is a permanent disconnection (no more reconnection attempts)\n sessionEnded?: boolean; // Whether this disconnection is due to user session ending\n };\n error: WebSocketError | Error;\n settings_update: AppSettings;\n capabilities_update: {\n capabilities: Capabilities | null;\n modelName: string | null;\n timestamp?: Date;\n };\n dashboard_mode_change: { mode: DashboardMode | \"none\" };\n dashboard_always_on_change: { enabled: boolean };\n custom_message: CustomMessage;\n permission_error: {\n message: string;\n details: PermissionErrorDetail[];\n timestamp?: Date;\n };\n permission_denied: {\n stream: string;\n requiredPermission: string;\n message: string;\n };\n}\n\n/** 📡 All possible event types */\ntype EventType = ExtendedStreamType | keyof SystemEvents;\n\n/** 📦 Map of stream types to their data types */\nexport interface StreamDataTypes {\n [StreamType.BUTTON_PRESS]: ButtonPress;\n [StreamType.HEAD_POSITION]: HeadPosition;\n [StreamType.PHONE_NOTIFICATION]: PhoneNotification;\n [StreamType.TRANSCRIPTION]: TranscriptionData;\n [StreamType.TRANSLATION]: TranslationData;\n [StreamType.GLASSES_BATTERY_UPDATE]: GlassesBatteryUpdate;\n [StreamType.PHONE_BATTERY_UPDATE]: PhoneBatteryUpdate;\n [StreamType.GLASSES_CONNECTION_STATE]: GlassesConnectionState;\n [StreamType.LOCATION_UPDATE]: LocationUpdate;\n [StreamType.CALENDAR_EVENT]: CalendarEvent;\n [StreamType.VAD]: Vad;\n [StreamType.PHONE_NOTIFICATION_DISMISSED]: PhoneNotificationDismissed;\n [StreamType.AUDIO_CHUNK]: AudioChunk;\n [StreamType.VIDEO]: ArrayBuffer;\n [StreamType.STREAM_STATUS]: StreamStatus;\n [StreamType.MANAGED_STREAM_STATUS]: ManagedStreamStatus;\n [StreamType.VPS_COORDINATES]: VpsCoordinates;\n [StreamType.PHOTO_TAKEN]: PhotoTaken;\n [StreamType.OPEN_DASHBOARD]: never;\n [StreamType.START_APP]: never;\n [StreamType.STOP_APP]: never;\n [StreamType.ALL]: never;\n [StreamType.WILDCARD]: never;\n}\n\n/** 📦 Data type for an event */\nexport type EventData<T extends EventType> = T extends keyof StreamDataTypes\n ? StreamDataTypes[T]\n : T extends keyof SystemEvents\n ? SystemEvents[T]\n : T extends string\n ? T extends `${StreamType.TRANSCRIPTION}:${string}`\n ? TranscriptionData\n : T extends `${StreamType.TRANSLATION}:${string}`\n ? TranslationData\n : never\n : never;\n\nexport class EventManager {\n private emitter: EventEmitter;\n private handlers: Map<EventType, Set<Handler<unknown>>>;\n private lastLanguageTranscriptioCleanupHandler: () => void;\n private lastLanguageTranslationCleanupHandler: () => void;\n private logger: Logger;\n\n constructor(\n private subscribe: (type: ExtendedStreamType) => void,\n private unsubscribe: (type: ExtendedStreamType) => void,\n private packageName: string,\n private baseUrl: string,\n logger: Logger,\n ) {\n this.emitter = new EventEmitter();\n this.handlers = new Map();\n this.lastLanguageTranscriptioCleanupHandler = () => {};\n this.lastLanguageTranslationCleanupHandler = () => {};\n this.logger = logger;\n }\n\n // Convenience handlers for common event types\n\n onTranscription(handler: Handler<TranscriptionData>) {\n // Only make the API call if we have a base URL (server-side environment)\n microPhoneWarnLog(this.baseUrl, this.packageName, this.onTranscription.name, this.logger);\n\n return this.addHandler(createTranscriptionStream(\"en-US\"), handler);\n }\n\n /**\n * 🎤 Listen for transcription events in a specific language\n * @param language - Language code (e.g., \"en-US\") or \"auto\" for automatic detection\n * @param handler - Function to handle transcription data\n * @param optionsOrBoolean - Optional configuration object or boolean (backward compatible)\n * @param optionsOrBoolean.disableLanguageIdentification - Disable language identification (defaults to false/enabled)\n * @param optionsOrBoolean.hints - Array of language code hints to improve detection (e.g., [\"es\", \"fr\"])\n * @returns Cleanup function to remove the handler\n * @throws Error if language code is invalid\n */\n onTranscriptionForLanguage(\n language: string,\n handler: Handler<TranscriptionData>,\n optionsOrBoolean?:\n | boolean\n | {\n disableLanguageIdentification?: boolean;\n hints?: string[];\n },\n ): () => void {\n if (language !== \"auto\" && !isValidLanguageCode(language)) {\n throw new Error(`Invalid language code: ${language}`);\n }\n this.lastLanguageTranscriptioCleanupHandler();\n\n // Handle backward compatibility: boolean or options object\n const options =\n typeof optionsOrBoolean === \"boolean\" ? { disableLanguageIdentification: optionsOrBoolean } : optionsOrBoolean;\n\n const streamType = createTranscriptionStream(language, options);\n this.lastLanguageTranscriptioCleanupHandler = this.addHandler(streamType, handler);\n return this.lastLanguageTranscriptioCleanupHandler;\n }\n\n /**\n * 🌐 Listen for translation events for a specific language pair\n * @param sourceLanguage - Source language code (e.g., \"es-ES\")\n * @param targetLanguage - Target language code (e.g., \"en-US\")\n * @param handler - Function to handle translation data\n * @returns Cleanup function to remove the handler\n * @throws Error if language codes are invalid\n */\n ontranslationForLanguage(\n sourceLanguage: string,\n targetLanguage: string,\n handler: Handler<TranslationData>,\n ): () => void {\n microPhoneWarnLog(this.baseUrl || \"\", this.packageName, this.ontranslationForLanguage.name, this.logger);\n if (!isValidLanguageCode(sourceLanguage)) {\n throw new Error(`Invalid source language code: ${sourceLanguage}`);\n }\n if (!isValidLanguageCode(targetLanguage)) {\n throw new Error(`Invalid target language code: ${targetLanguage}`);\n }\n\n this.lastLanguageTranslationCleanupHandler();\n const streamType = createTranslationStream(sourceLanguage, targetLanguage);\n this.lastLanguageTranslationCleanupHandler = this.addHandler(streamType, handler);\n\n return this.lastLanguageTranslationCleanupHandler;\n }\n\n onHeadPosition(handler: Handler<HeadPosition>) {\n return this.addHandler(StreamType.HEAD_POSITION, handler);\n }\n\n onButtonPress(handler: Handler<ButtonPress>) {\n return this.addHandler(StreamType.BUTTON_PRESS, handler);\n }\n\n onTouchEvent(gestureOrHandler: string | Handler<TouchEvent>, handler?: Handler<TouchEvent>): () => void {\n // Handle both: onTouchEvent(handler) and onTouchEvent(\"forward_swipe\", handler)\n if (typeof gestureOrHandler === \"function\") {\n // Subscribe to all touch events\n return this.addHandler(StreamType.TOUCH_EVENT, gestureOrHandler);\n } else {\n // Subscribe to specific gesture\n const gestureStream = createTouchEventStream(gestureOrHandler);\n return this.addHandler(gestureStream, handler!);\n }\n }\n\n onPhoneNotifications(handler: Handler<PhoneNotification>) {\n return this.addHandler(StreamType.PHONE_NOTIFICATION, handler);\n }\n\n onPhoneNotificationDismissed(handler: Handler<PhoneNotificationDismissed>) {\n return this.addHandler(StreamType.PHONE_NOTIFICATION_DISMISSED, handler);\n }\n\n onGlassesBattery(handler: Handler<GlassesBatteryUpdate>) {\n return this.addHandler(StreamType.GLASSES_BATTERY_UPDATE, handler);\n }\n\n onPhoneBattery(handler: Handler<PhoneBatteryUpdate>) {\n return this.addHandler(StreamType.PHONE_BATTERY_UPDATE, handler);\n }\n\n onVoiceActivity(handler: Handler<Vad>) {\n microPhoneWarnLog(this.baseUrl || \"\", this.packageName, this.onVoiceActivity.name, this.logger);\n return this.addHandler(StreamType.VAD, handler);\n }\n\n onLocation(handler: Handler<LocationUpdate>) {\n return this.addHandler(StreamType.LOCATION_UPDATE, handler);\n }\n\n onCalendarEvent(handler: Handler<CalendarEvent>) {\n return this.addHandler(StreamType.CALENDAR_EVENT, handler);\n }\n\n /**\n * 🎤 Listen for audio chunk data\n * @param handler - Function to handle audio chunks\n * @returns Cleanup function to remove the handler\n */\n onAudioChunk(handler: Handler<AudioChunk>) {\n return this.addHandler(StreamType.AUDIO_CHUNK, handler);\n }\n\n // System event handlers\n\n onConnected(handler: Handler<SystemEvents[\"connected\"]>) {\n this.emitter.on(\"connected\", handler);\n return () => this.emitter.off(\"connected\", handler);\n }\n\n onDisconnected(handler: Handler<SystemEvents[\"disconnected\"]>) {\n this.emitter.on(\"disconnected\", handler);\n return () => this.emitter.off(\"disconnected\", handler);\n }\n\n onError(handler: Handler<SystemEvents[\"error\"]>) {\n this.emitter.on(\"error\", handler);\n return () => this.emitter.off(\"error\", handler);\n }\n\n onSettingsUpdate(handler: Handler<SystemEvents[\"settings_update\"]>) {\n this.emitter.on(\"settings_update\", handler);\n return () => this.emitter.off(\"settings_update\", handler);\n }\n\n /**\n * 🔧 Listen for device capabilities updates\n * @param handler - Function to handle capabilities updates\n * @returns Cleanup function to remove the handler\n */\n onCapabilitiesUpdate(handler: Handler<SystemEvents[\"capabilities_update\"]>) {\n this.emitter.on(\"capabilities_update\", handler);\n return () => this.emitter.off(\"capabilities_update\", handler);\n }\n\n /**\n * 🌐 Listen for dashboard mode changes\n * @param handler - Function to handle dashboard mode changes\n * @returns Cleanup function to remove the handler\n */\n onDashboardModeChange(handler: Handler<SystemEvents[\"dashboard_mode_change\"]>) {\n this.emitter.on(\"dashboard_mode_change\", handler);\n return () => this.emitter.off(\"dashboard_mode_change\", handler);\n }\n\n /**\n * 🌐 Listen for dashboard always-on mode changes\n * @param handler - Function to handle dashboard always-on mode changes\n * @returns Cleanup function to remove the handler\n */\n onDashboardAlwaysOnChange(handler: Handler<SystemEvents[\"dashboard_always_on_change\"]>) {\n this.emitter.on(\"dashboard_always_on_change\", handler);\n return () => this.emitter.off(\"dashboard_always_on_change\", handler);\n }\n\n /**\n * 🚫 Listen for permission errors when subscriptions are rejected\n * @param handler - Function to handle permission errors\n * @returns Cleanup function to remove the handler\n */\n onPermissionError(handler: Handler<SystemEvents[\"permission_error\"]>) {\n this.emitter.on(\"permission_error\", handler);\n return () => this.emitter.off(\"permission_error\", handler);\n }\n\n /**\n * 🚫 Listen for individual permission denied events for specific streams\n * @param handler - Function to handle permission denied events\n * @returns Cleanup function to remove the handler\n */\n onPermissionDenied(handler: Handler<SystemEvents[\"permission_denied\"]>) {\n this.emitter.on(\"permission_denied\", handler);\n return () => this.emitter.off(\"permission_denied\", handler);\n }\n\n /**\n * 🔄 Listen for changes to a specific setting\n * @param key - Setting key to monitor\n * @param handler - Function to handle setting value changes\n * @returns Cleanup function to remove the handler\n */\n onSettingChange<T>(key: string, handler: (value: T, previousValue: T | undefined) => void): () => void {\n let previousValue: T | undefined = undefined;\n\n const settingsHandler = (settings: AppSettings) => {\n try {\n const setting = settings.find((s) => s.key === key);\n if (setting) {\n // Only call handler if value has changed\n if (setting.value !== previousValue) {\n const newValue = setting.value as T;\n handler(newValue, previousValue);\n previousValue = newValue;\n }\n }\n } catch (error: unknown) {\n this.logger.debug({ key, error }, `Error in onSettingChange handler for key \"${key}\"`);\n }\n };\n\n this.emitter.on(\"settings_update\", settingsHandler);\n this.emitter.on(\"connected\", settingsHandler); // Also check when first connected\n\n return () => {\n this.emitter.off(\"settings_update\", settingsHandler);\n this.emitter.off(\"connected\", settingsHandler);\n };\n }\n\n /**\n * 🔄 Generic event handler\n *\n * Use this for stream types without specific handler methods\n */\n on<T extends ExtendedStreamType>(type: T, handler: Handler<EventData<T>>): () => void {\n // Check permissions for specific stream types\n if (type === StreamType.CALENDAR_EVENT) {\n calendarWarnLog(this.baseUrl, this.packageName, \"on\", this.logger);\n }\n return this.addHandler(type, handler);\n }\n\n /**\n * ➕ Add an event handler and subscribe if needed\n */\n private addHandler<T extends ExtendedStreamType>(type: T, handler: Handler<EventData<T>>): () => void {\n const handlers = this.handlers.get(type) ?? new Set();\n\n if (handlers.size === 0) {\n this.handlers.set(type, handlers);\n this.subscribe(type);\n }\n handlers.add(handler as Handler<unknown>);\n return () => this.removeHandler(type, handler);\n }\n\n /**\n * ➖ Remove an event handler\n */\n private removeHandler<T extends ExtendedStreamType>(type: T, handler: Handler<EventData<T>>): void {\n const handlers = this.handlers.get(type);\n if (!handlers) return;\n\n handlers.delete(handler as Handler<unknown>);\n if (handlers.size === 0) {\n this.handlers.delete(type);\n this.unsubscribe(type);\n }\n }\n\n /**\n * 🔍 Get all currently registered stream types\n * Returns the streams that have at least one handler registered.\n * Used to derive subscriptions from handlers (single source of truth).\n *\n * This is the fix for Bug 007: subscriptions are now derived from handlers\n * instead of being stored separately, preventing drift between the two.\n */\n getRegisteredStreams(): ExtendedStreamType[] {\n return Array.from(this.handlers.keys()) as ExtendedStreamType[];\n }\n\n /**\n * 🔍 Find a registered stream that matches the incoming stream type.\n *\n * For non-language streams: exact match (existing behavior).\n * For language streams: compare base type + transcribeLanguage\n * (+ translateLanguage for translations), ignoring query params like ?hints=.\n *\n * This allows the SDK to receive data from a cloud stream whose subscription\n * string doesn't include the same query params as the handler's subscription.\n * For example, incoming \"transcription:en-US\" matches handler \"transcription:en-US?hints=ja\".\n */\n findMatchingStream(incoming: ExtendedStreamType): ExtendedStreamType | null {\n // Fast path: exact match\n if (this.handlers.has(incoming)) {\n return incoming;\n }\n\n // For language streams, try base-language matching\n if (isLanguageStream(incoming as string)) {\n const incomingParsed = parseLanguageStream(incoming);\n if (!incomingParsed) return null;\n\n for (const key of this.handlers.keys()) {\n if (!isLanguageStream(key as string)) continue;\n\n const keyParsed = parseLanguageStream(key as ExtendedStreamType);\n if (!keyParsed) continue;\n\n // Compare base type\n if (keyParsed.type !== incomingParsed.type) continue;\n\n // Compare transcribe language\n if (keyParsed.transcribeLanguage !== incomingParsed.transcribeLanguage) continue;\n\n // For translations, also compare target language\n if (incomingParsed.translateLanguage || keyParsed.translateLanguage) {\n if (keyParsed.translateLanguage !== incomingParsed.translateLanguage) continue;\n }\n\n return key as ExtendedStreamType;\n }\n }\n\n return null;\n }\n\n /**\n * 📡 Emit an event to all registered handlers with error isolation\n */\n emit<T extends EventType>(event: T, data: EventData<T>): void {\n try {\n // Emit to EventEmitter handlers (system events)\n this.emitter.emit(event, data);\n\n // Emit to stream handlers if applicable\n const handlers = this.handlers.get(event);\n\n if (handlers) {\n // Create array of handlers to prevent modification during iteration\n const handlersArray = Array.from(handlers);\n\n // Execute each handler in isolated try/catch to prevent one handler\n // from crashing the entire App\n handlersArray.forEach((handler) => {\n try {\n (handler as Handler<EventData<T>>)(data);\n } catch (handlerError: unknown) {\n // Log at debug — the error event (emitted below) is the primary output path\n this.logger.debug(\n { event: String(event), error: handlerError },\n `Error in handler for event '${String(event)}'`,\n );\n\n // Emit an error event for tracking purposes\n if (event !== \"error\") {\n // Prevent infinite recursion\n const errorMessage = handlerError instanceof Error ? handlerError.message : String(handlerError);\n\n this.emitter.emit(\"error\", new Error(`Handler error for event '${String(event)}': ${errorMessage}`));\n }\n }\n });\n }\n\n // Fallback: if this is an error event and nobody is listening, log it.\n // This prevents errors from being silently swallowed when dev has no onError handler.\n if (event === \"error\" && this.emitter.listenerCount(\"error\") === 0 && (!handlers || handlers.size === 0)) {\n const error = data as unknown as Error;\n this.logger.error(error?.message ?? String(data));\n }\n } catch (emitError: unknown) {\n // Catch any errors in the emission process itself\n this.logger.debug({ event: String(event), error: emitError }, `Fatal error emitting event '${String(event)}'`);\n\n // Try to emit an error event if we're not already handling an error\n if (event !== \"error\") {\n try {\n const errorMessage = emitError instanceof Error ? emitError.message : String(emitError);\n\n this.emitter.emit(\"error\", new Error(`Event emission error for '${String(event)}': ${errorMessage}`));\n } catch {\n // If even this fails, log it — nothing more we can do\n this.logger.debug(\"Failed to emit error event after emission failure\");\n }\n }\n }\n }\n\n /**\n * 📨 Listen for custom messages with a specific action\n * @param action - The action identifier to filter by\n * @param handler - Function to handle the message\n * @returns Cleanup function to remove the handler\n * @deprecated Use settings.onMentraosChange() instead for system settings.\n * This method was used for datetime updates but is no longer needed.\n * Will be removed in a future version.\n */\n onCustomMessage(action: string, handler: (payload: any) => void): () => void {\n const messageHandler = (message: CustomMessage) => {\n if (message.action === action) {\n handler(message.payload);\n }\n };\n\n this.emitter.on(\"custom_message\", messageHandler);\n return () => this.emitter.off(\"custom_message\", messageHandler);\n }\n\n onVpsCoordinates(handler: Handler<VpsCoordinates>) {\n return this.addHandler(StreamType.VPS_COORDINATES, handler);\n }\n\n /**\n * 📸 Listen for photo responses\n * @param handler - Function to handle photo response data\n * @returns Cleanup function to remove the handler\n */\n onPhotoTaken(handler: Handler<PhotoTaken>) {\n return this.addHandler(StreamType.PHOTO_TAKEN, handler);\n }\n}\n",
8
21
  "/**\n * Dashboard API Implementation\n *\n * Provides dashboard functionality for Apps, allowing them to write content\n * to the dashboard and respond to dashboard mode changes.\n */\n// import { systemApps } from '../../constants';\nimport {\n DashboardAPI,\n DashboardContentAPI,\n DashboardMode,\n DashboardSystemAPI,\n DashboardContentUpdate,\n DashboardModeChange,\n DashboardSystemUpdate,\n} from \"../../types/dashboard\"\nimport {AppToCloudMessageType} from \"../../types/message-types\"\nimport {EventManager} from \"./events\"\n\n// Import AppSession interface for typing\nimport type {AppSession} from \"./index\"\nimport dotenv from \"dotenv\"\n// Load environment variables from .env file\ndotenv.config()\n\nconst SYSTEM_DASHBOARD_PACKAGE_NAME = process.env.SYSTEM_DASHBOARD_PACKAGE_NAME || \"system.augmentos.dashboard\"\n\n/**\n * Implementation of DashboardSystemAPI interface for system dashboard App\n */\nexport class DashboardSystemManager implements DashboardSystemAPI {\n private session: AppSession\n private packageName: string\n\n constructor(session: AppSession, packageName: string) {\n this.session = session\n this.packageName = packageName\n }\n\n setTopLeft(content: string): void {\n this.updateSystemSection(\"topLeft\", content)\n }\n\n setTopRight(content: string): void {\n this.updateSystemSection(\"topRight\", content)\n }\n\n setBottomLeft(content: string): void {\n this.updateSystemSection(\"bottomLeft\", content)\n }\n\n setBottomRight(content: string): void {\n this.updateSystemSection(\"bottomRight\", content)\n }\n\n setViewMode(mode: DashboardMode): void {\n const message: DashboardModeChange = {\n type: AppToCloudMessageType.DASHBOARD_MODE_CHANGE,\n packageName: this.packageName,\n sessionId: `${this.session.getSessionId()}-${this.packageName}`,\n mode,\n timestamp: new Date(),\n }\n this.session.sendMessage(message)\n }\n\n private updateSystemSection(section: \"topLeft\" | \"topRight\" | \"bottomLeft\" | \"bottomRight\", content: string): void {\n const message: DashboardSystemUpdate = {\n type: AppToCloudMessageType.DASHBOARD_SYSTEM_UPDATE,\n packageName: this.packageName,\n sessionId: `${this.session.getSessionId()}-${this.packageName}`,\n section,\n content,\n timestamp: new Date(),\n }\n this.session.sendMessage(message)\n }\n}\n\n/**\n * Implementation of DashboardContentAPI interface for all Apps\n */\nexport class DashboardContentManager implements DashboardContentAPI {\n private session: AppSession\n private packageName: string\n private events: EventManager\n private currentMode: DashboardMode | \"none\" = \"none\"\n // private alwaysOnEnabled: boolean = false;\n\n constructor(session: AppSession, packageName: string, events: EventManager) {\n this.session = session\n this.packageName = packageName\n this.events = events\n }\n\n write(content: string, targets: DashboardMode[] = [DashboardMode.MAIN]): void {\n const message: DashboardContentUpdate = {\n type: AppToCloudMessageType.DASHBOARD_CONTENT_UPDATE,\n packageName: this.packageName,\n sessionId: `${this.session.getSessionId()}-${this.packageName}`,\n content,\n modes: targets,\n timestamp: new Date(),\n }\n this.session.sendMessage(message)\n }\n\n writeToMain(content: string): void {\n this.write(content, [DashboardMode.MAIN])\n }\n\n writeToExpanded(content: string): void {\n const message: DashboardContentUpdate = {\n type: AppToCloudMessageType.DASHBOARD_CONTENT_UPDATE,\n packageName: this.packageName,\n sessionId: `${this.session.getSessionId()}-${this.packageName}`,\n content,\n modes: [DashboardMode.EXPANDED],\n timestamp: new Date(),\n }\n this.session.sendMessage(message)\n }\n\n // writeToAlwaysOn(content: string): void {\n // this.write(content, [DashboardMode.ALWAYS_ON]);\n // }\n\n async getCurrentMode(): Promise<DashboardMode | \"none\"> {\n return this.currentMode\n }\n\n // async isAlwaysOnEnabled(): Promise<boolean> {\n // return this.alwaysOnEnabled;\n // }\n\n onModeChange(callback: (mode: DashboardMode | \"none\") => void): () => void {\n return this.events.onDashboardModeChange((data) => {\n this.currentMode = data.mode\n callback(data.mode)\n })\n }\n\n // onAlwaysOnChange(callback: (enabled: boolean) => void): () => void {\n // return this.events.onDashboardAlwaysOnChange((data) => {\n // this.alwaysOnEnabled = data.enabled;\n // callback(data.enabled);\n // });\n // }\n\n // Internal methods to update state\n setCurrentMode(mode: DashboardMode | \"none\"): void {\n this.currentMode = mode\n this.events.emit(\"dashboard_mode_change\", {mode})\n }\n\n // setAlwaysOnEnabled(enabled: boolean): void {\n // this.alwaysOnEnabled = enabled;\n // this.events.emit('dashboard_always_on_change', { enabled });\n // }\n}\n\n/**\n * Dashboard Manager - Main class that manages dashboard functionality\n * Each AppSession instance gets its own DashboardManager instance\n */\nexport class DashboardManager implements DashboardAPI {\n public content: DashboardContentAPI\n public system?: DashboardSystemAPI\n\n constructor(session: AppSession) {\n const packageName = session.getPackageName()\n const events = session.events\n\n // Create content API (available to all Apps)\n this.content = new DashboardContentManager(session, packageName, events)\n\n // Add system API if this is the system dashboard App\n if (packageName === SYSTEM_DASHBOARD_PACKAGE_NAME) {\n session.logger.info({service: \"SDK:DashboardManager\"}, \"Initializing system dashboard manager\")\n this.system = new DashboardSystemManager(session, packageName)\n } else {\n session.logger.info({service: \"SDK:DashboardManager\"}, `Not the system dashboard: ${packageName}`)\n }\n }\n}\n",
9
22
  "/**\n * 🔐 App Token Utilities\n *\n * Provides utilities for working with App tokens.\n */\nimport * as jwt from 'jsonwebtoken';\nimport { AppTokenPayload, TokenValidationResult, TokenConfig } from '../../types/token';\n\n/**\n * Default token expiration (1 day)\n */\nconst DEFAULT_EXPIRATION = 60 * 60 * 24; // seconds * minutes * hours (1 day).\n\n/**\n * Create a App token for a user session\n *\n * @param payload - Token payload data\n * @param config - Token configuration\n * @returns JWT token string\n *\n * @example\n * ```typescript\n * const token = createToken(\n * {\n * userId: 'user123',\n * packageName: 'org.example.myapp',\n * sessionId: 'session789'\n * },\n * { secretKey: 'your_secret_key' }\n * );\n * ```\n */\nexport function createToken(\n payload: Omit<AppTokenPayload, 'iat' | 'exp'>,\n config: TokenConfig\n): string {\n return jwt.sign(\n payload,\n config.secretKey,\n { expiresIn: config.expiresIn || DEFAULT_EXPIRATION }\n );\n}\n\n/**\n * Validate and decode a App token\n *\n * @param token - JWT token string\n * @param secretKey - Secret key used for validation\n * @returns Token validation result\n *\n * @example\n * ```typescript\n * const result = validateToken('eyJhbGciOiJIUzI1...', 'your_secret_key');\n * if (result.valid) {\n * // Use result.payload\n * }\n * ```\n */\nexport function validateToken(\n token: string,\n secretKey: string\n): TokenValidationResult {\n try {\n const decoded = jwt.verify(token, secretKey) as AppTokenPayload;\n return {\n valid: true,\n payload: decoded\n };\n } catch (error) {\n return {\n valid: false,\n error: error instanceof Error ? error.message : 'Unknown error'\n };\n }\n}\n\n/**\n * Generate a webview URL with an embedded App token\n *\n * @param baseUrl - Base URL of the webview\n * @param token - JWT token string\n * @returns Full URL with token parameter\n *\n * @example\n * ```typescript\n * const url = generateWebviewUrl(\n * 'https://example.com/webview',\n * 'eyJhbGciOiJIUzI1...'\n * );\n * // Returns: https://example.com/webview?token=eyJhbGciOiJIUzI1...\n * ```\n */\nexport function generateWebviewUrl(baseUrl: string, token: string): string {\n const url = new URL(baseUrl);\n url.searchParams.append('token', token);\n return url.toString();\n}\n\n/**\n * Extract a App token from a URL\n *\n * @param url - URL string containing a token parameter\n * @returns Token string or null if not found\n *\n * @example\n * ```typescript\n * const token = extractTokenFromUrl(\n * 'https://example.com/webview?token=eyJhbGciOiJIUzI1...'\n * );\n * ```\n */\nexport function extractTokenFromUrl(url: string): string | null {\n try {\n const parsedUrl = new URL(url);\n return parsedUrl.searchParams.get('token');\n } catch (error) {\n return null;\n }\n}",
10
- "// src/index.ts\nexport * from \"./types/token\";\n\n// Message type enums\nexport * from \"./types/message-types\";\n\n// Base message type\nexport * from \"./types/messages/base\";\n\n// Messages by direction - export everything except the conflicting type guards\nexport * from \"./types/messages/glasses-to-cloud\";\nexport * from \"./types/messages/cloud-to-glasses\";\nexport * from \"./types/messages/app-to-cloud\";\n\n// Utility exports\nexport * from \"./utils/bitmap-utils\";\nexport * from \"./utils/animation-utils\";\n\n// Export cloud-to-app but exclude the conflicting type guards\nexport {\n // Types\n AppConnectionAck,\n AppConnectionError,\n AppStopped,\n SettingsUpdate as AppSettingsUpdate, // Alias to avoid conflict with cloud-to-glasses SettingsUpdate\n CapabilitiesUpdate,\n DataStream,\n CloudToAppMessage,\n TranslationData,\n ToolCall,\n StandardConnectionError,\n CustomMessage,\n ManagedStreamStatus,\n StreamStatusCheckResponse,\n OutputStatus,\n MentraosSettingsUpdate,\n TranscriptionData,\n TranscriptionMetadata,\n SonioxToken,\n AudioChunk,\n PermissionError,\n PermissionErrorDetail,\n AudioPlayResponse,\n // Type guards (excluding isPhotoResponse and isRtmpStreamStatus which conflict)\n isAppConnectionAck,\n isAppConnectionError,\n isAppStopped,\n isSettingsUpdate,\n isCapabilitiesUpdate,\n isDataStream,\n isAudioChunk,\n isStreamStatusCheckResponse,\n isDashboardModeChanged,\n isDashboardAlwaysOnChanged,\n isManagedStreamStatus,\n // Re-export the cloud-to-app versions of these type guards since they're the ones\n // that should be used when dealing with CloudToAppMessage types\n isPhotoResponse as isPhotoResponseFromCloud,\n isRgbLedControlResponse as isRgbLedControlResponseFromCloud,\n isRtmpStreamStatus as isRtmpStreamStatusFromCloud,\n} from \"./types/messages/cloud-to-app\";\n\n// Stream types\nexport * from \"./types/streams\";\n\n// Layout types\nexport * from \"./types/layouts\";\n\n// Dashboard types\nexport * from \"./types/dashboard\";\n\n// RTMP streaming types\nexport * from \"./types/rtmp-stream\";\n\n// Other system enums\nexport { AppType, LayoutType, ViewType, AppSettingType, HardwareType, HardwareRequirementLevel } from \"./types/enums\";\n\n// Core model interfaces\nexport * from \"./types/models\";\n\n// Webhook interfaces\nexport * from \"./types/webhooks\";\n\n// Capability Discovery types\nexport * from \"./types/capabilities\";\n\n// App session and server exports\nexport * from \"./app/index\";\n\n// Logging exports\nexport * from \"./logging/logger\";\n\n// Re-export common types for convenience\n// This allows developers to import commonly used types directly from the package root\n// without having to know exactly which file they come from\n\n// From messages/glasses-to-cloud.ts\nexport {\n ButtonPress,\n HeadPosition,\n TouchEvent,\n GlassesBatteryUpdate,\n PhoneBatteryUpdate,\n GlassesConnectionState,\n LocationUpdate,\n CalendarEvent,\n Vad,\n PhoneNotification,\n PhoneNotificationDismissed,\n StartApp,\n StopApp,\n ConnectionInit,\n DashboardState,\n OpenDashboard,\n GlassesToCloudMessage,\n PhotoResponse,\n RgbLedControlResponse,\n PhotoErrorCode,\n PhotoStage,\n ConnectionState,\n PhotoErrorDetails,\n RtmpStreamStatus,\n KeepAliveAck,\n} from \"./types/messages/glasses-to-cloud\";\n\n// From messages/cloud-to-glasses.ts\nexport {\n ConnectionAck,\n ConnectionError,\n AuthError,\n DisplayEvent,\n AppStateChange,\n MicrophoneStateChange,\n CloudToGlassesMessage,\n PhotoRequestToGlasses,\n RgbLedControlToGlasses,\n SettingsUpdate,\n StartRtmpStream,\n StopRtmpStream,\n KeepRtmpStreamAlive,\n LedColor,\n} from \"./types/messages/cloud-to-glasses\";\n\n// From messages/app-to-cloud.ts\nexport {\n AppConnectionInit,\n AppSubscriptionUpdate,\n RtmpStreamRequest,\n RtmpStreamStopRequest,\n AppToCloudMessage,\n PhotoRequest,\n RgbLedControlRequest,\n} from \"./types/messages/app-to-cloud\";\n\n// From layout.ts\nexport {\n TextWall,\n DoubleTextWall,\n DashboardCard,\n ReferenceCard,\n Layout,\n DisplayRequest,\n BitmapView,\n ClearView,\n} from \"./types/layouts\";\n\n// Type guards - re-export the most commonly used ones for convenience\nexport {\n isButtonPress,\n isHeadPosition,\n isConnectionInit,\n isStartApp,\n isStopApp,\n isPhotoResponse as isPhotoResponseFromGlasses,\n isRgbLedControlResponse as isRgbLedControlResponseFromGlasses,\n isRtmpStreamStatus as isRtmpStreamStatusFromGlasses,\n isKeepAliveAck,\n isPhoneNotificationDismissed,\n} from \"./types/messages/glasses-to-cloud\";\n\nexport {\n isConnectionAck,\n isDisplayEvent,\n isAppStateChange,\n isPhotoRequest,\n isSettingsUpdate as isSettingsUpdateToGlasses,\n isStartRtmpStream,\n isStopRtmpStream,\n isKeepRtmpStreamAlive,\n isRgbLedControl,\n} from \"./types/messages/cloud-to-glasses\";\n\nexport {\n isAppConnectionInit,\n isAppSubscriptionUpdate,\n isDisplayRequest,\n isRtmpStreamRequest,\n isRtmpStreamStopRequest,\n isPhotoRequest as isPhotoRequestFromApp,\n isRgbLedControlRequest,\n isOwnershipRelease,\n} from \"./types/messages/app-to-cloud\";\n\n// Export setting-related types\nexport {\n BaseAppSetting,\n AppSetting,\n AppSettings,\n AppConfig,\n validateAppConfig,\n ToolSchema,\n ToolParameterSchema,\n HardwareRequirement,\n} from \"./types/models\";\n\n// Export RTMP streaming types\nexport { VideoConfig, AudioConfig, StreamConfig, StreamStatusHandler } from \"./types/rtmp-stream\";\n\n// Export app session modules\nexport * from \"./app/session/modules\";\n\n// Export photo data types\nexport { PhotoData } from \"./types/photo-data\";\n\n// Export device state types (WebSocket-based observables)\nexport { DeviceState } from \"./app/session/device-state\";\nexport { Observable } from \"./utils/Observable\";\n\n// Re-export types from @mentra/types so SDK users don't need to install it separately\nexport type { GlassesInfo } from \"@mentra/types\";\n\n/**\n * WebSocket error information\n */\nexport interface WebSocketError {\n code: string;\n message: string;\n details?: unknown;\n}\n\nexport { AuthenticatedRequest } from \"./types/index\";\n",
11
- "// src/messages/glasses-to-cloud.ts\n\nimport { GlassesToCloudMessageType, ControlActionTypes, EventTypes } from \"../message-types\";\nimport { StreamType } from \"../streams\";\n\nimport { BaseMessage } from \"./base\";\n\n//===========================================================\n// Control actions\n//===========================================================\n\n/**\n * Connection initialization from glasses\n */\nexport interface ConnectionInit extends BaseMessage {\n type: GlassesToCloudMessageType.CONNECTION_INIT;\n userId?: string;\n coreToken?: string;\n}\n\n/**\n * Client requests LiveKit info (url, room, token)\n */\nexport interface LiveKitInit extends BaseMessage {\n type: GlassesToCloudMessageType.LIVEKIT_INIT;\n mode?: \"publish\" | \"subscribe\"; // Optional mode - defaults to 'publish' for backward compatibility\n}\n\nexport interface RequestSettings extends BaseMessage {\n type: GlassesToCloudMessageType.REQUEST_SETTINGS;\n sessionId: string;\n}\n\n/**\n * Start app request from glasses\n */\nexport interface StartApp extends BaseMessage {\n type: GlassesToCloudMessageType.START_APP;\n packageName: string;\n}\n\n/**\n * Stop app request from glasses\n */\nexport interface StopApp extends BaseMessage {\n type: GlassesToCloudMessageType.STOP_APP;\n packageName: string;\n}\n\n/**\n * Dashboard state update from glasses\n */\nexport interface DashboardState extends BaseMessage {\n type: GlassesToCloudMessageType.DASHBOARD_STATE;\n isOpen: boolean;\n}\n\n/**\n * Open dashboard request from glasses\n */\nexport interface OpenDashboard extends BaseMessage {\n type: GlassesToCloudMessageType.OPEN_DASHBOARD;\n}\n\n//===========================================================\n// Events and data\n//===========================================================\n\n/**\n * Button press event from glasses\n */\nexport interface ButtonPress extends BaseMessage {\n type: GlassesToCloudMessageType.BUTTON_PRESS;\n buttonId: string;\n pressType: \"short\" | \"long\";\n}\n\n/**\n * Head position event from glasses\n */\nexport interface HeadPosition extends BaseMessage {\n type: GlassesToCloudMessageType.HEAD_POSITION;\n position: \"up\" | \"down\";\n}\n\n/**\n * Touch gesture event from glasses\n */\nexport interface TouchEvent extends BaseMessage {\n type: GlassesToCloudMessageType.TOUCH_EVENT;\n device_model: string;\n gesture_name: string;\n timestamp: Date;\n}\n\n/**\n * Glasses battery update from glasses\n */\nexport interface GlassesBatteryUpdate extends BaseMessage {\n type: GlassesToCloudMessageType.GLASSES_BATTERY_UPDATE;\n level: number; // 0-100\n charging: boolean;\n timeRemaining?: number; // minutes\n}\n\n/**\n * Phone battery update from glasses\n */\nexport interface PhoneBatteryUpdate extends BaseMessage {\n type: GlassesToCloudMessageType.PHONE_BATTERY_UPDATE;\n level: number; // 0-100\n charging: boolean;\n timeRemaining?: number; // minutes\n}\n\n/**\n * Glasses connection state from glasses\n */\nexport interface GlassesConnectionState extends BaseMessage {\n type: GlassesToCloudMessageType.GLASSES_CONNECTION_STATE;\n modelName: string;\n status: string;\n\n // Optional WiFi details (only present for WiFi-capable glasses)\n wifi?: {\n connected: boolean;\n ssid?: string | null;\n };\n}\n\n/**\n * Location update from glasses\n */\nexport interface LocationUpdate extends BaseMessage {\n type: GlassesToCloudMessageType.LOCATION_UPDATE | StreamType.LOCATION_UPDATE;\n lat: number;\n lng: number;\n accuracy?: number; // Accuracy in meters\n correlationId?: string; // for poll responses\n}\n\n/**\n * VPS coordinates update from glasses\n */\nexport interface VpsCoordinates extends BaseMessage {\n type: GlassesToCloudMessageType.VPS_COORDINATES | StreamType.VPS_COORDINATES;\n deviceModel: string;\n requestId: string;\n x: number;\n y: number;\n z: number;\n qx: number;\n qy: number;\n qz: number;\n qw: number;\n confidence: number;\n}\n\nexport interface LocalTranscription extends BaseMessage {\n type: GlassesToCloudMessageType.LOCAL_TRANSCRIPTION;\n text: string;\n isFinal: boolean;\n startTime: number;\n endTime: number;\n speakerId: number;\n transcribeLanguage: string;\n provider: string;\n}\n\nexport interface CalendarEvent extends BaseMessage {\n type: GlassesToCloudMessageType.CALENDAR_EVENT | StreamType.CALENDAR_EVENT;\n eventId: string;\n title: string;\n dtStart: string;\n dtEnd: string;\n timezone: string;\n timeStamp: string;\n}\n\n/**\n * Voice activity detection from glasses\n */\nexport interface Vad extends BaseMessage {\n type: GlassesToCloudMessageType.VAD;\n status: boolean | \"true\" | \"false\";\n}\n\n/**\n * Phone notification from glasses\n */\nexport interface PhoneNotification extends BaseMessage {\n type: GlassesToCloudMessageType.PHONE_NOTIFICATION;\n notificationId: string;\n app: string;\n title: string;\n content: string;\n priority: \"low\" | \"normal\" | \"high\";\n}\n\n/**\n * Notification dismissed from glasses\n */\nexport interface PhoneNotificationDismissed extends BaseMessage {\n type: GlassesToCloudMessageType.PHONE_NOTIFICATION_DISMISSED;\n notificationId: string;\n app: string;\n title: string;\n content: string;\n notificationKey: string;\n}\n\n/**\n * MentraOS settings update from glasses\n */\nexport interface MentraosSettingsUpdateRequest extends BaseMessage {\n type: GlassesToCloudMessageType.MENTRAOS_SETTINGS_UPDATE_REQUEST;\n}\nexport interface MentraosSettingsUpdateRequest extends BaseMessage {\n type: GlassesToCloudMessageType.MENTRAOS_SETTINGS_UPDATE_REQUEST;\n}\n\n/**\n * Core status update from glasses\n */\nexport interface CoreStatusUpdate extends BaseMessage {\n type: GlassesToCloudMessageType.CORE_STATUS_UPDATE;\n status: string;\n details?: Record<string, any>;\n}\n\n// ===========================================================\n// Mentra Live\n// ===========================================================\n\n/**\n * Photo error codes for detailed error reporting\n */\nexport enum PhotoErrorCode {\n CAMERA_INIT_FAILED = \"CAMERA_INIT_FAILED\",\n CAMERA_CAPTURE_FAILED = \"CAMERA_CAPTURE_FAILED\",\n CAMERA_TIMEOUT = \"CAMERA_TIMEOUT\",\n CAMERA_BUSY = \"CAMERA_BUSY\",\n UPLOAD_FAILED = \"UPLOAD_FAILED\",\n UPLOAD_TIMEOUT = \"UPLOAD_TIMEOUT\",\n BLE_TRANSFER_FAILED = \"BLE_TRANSFER_FAILED\",\n BLE_TRANSFER_BUSY = \"BLE_TRANSFER_BUSY\",\n BLE_TRANSFER_FAILED_TO_START = \"BLE_TRANSFER_FAILED_TO_START\",\n BLE_TRANSFER_TIMEOUT = \"BLE_TRANSFER_TIMEOUT\",\n COMPRESSION_FAILED = \"COMPRESSION_FAILED\",\n PERMISSION_DENIED = \"PERMISSION_DENIED\",\n STORAGE_FULL = \"STORAGE_FULL\",\n NETWORK_ERROR = \"NETWORK_ERROR\",\n // Phone-side error codes\n PHONE_GLASSES_NOT_CONNECTED = \"PHONE_GLASSES_NOT_CONNECTED\",\n PHONE_BLE_TRANSFER_FAILED = \"PHONE_BLE_TRANSFER_FAILED\",\n PHONE_UPLOAD_FAILED = \"PHONE_UPLOAD_FAILED\",\n PHONE_TIMEOUT = \"PHONE_TIMEOUT\",\n UNKNOWN_ERROR = \"UNKNOWN_ERROR\",\n}\n\n/**\n * Photo processing stages for error context\n */\nexport enum PhotoStage {\n REQUEST_RECEIVED = \"REQUEST_RECEIVED\",\n CAMERA_INIT = \"CAMERA_INIT\",\n PHOTO_CAPTURE = \"PHOTO_CAPTURE\",\n COMPRESSION = \"COMPRESSION\",\n UPLOAD_START = \"UPLOAD_START\",\n UPLOAD_PROGRESS = \"UPLOAD_PROGRESS\",\n BLE_TRANSFER = \"BLE_TRANSFER\",\n RESPONSE_SENT = \"RESPONSE_SENT\",\n}\n\n/**\n * Connection state information for error diagnostics\n */\nexport interface ConnectionState {\n wifi: {\n connected: boolean;\n ssid?: string;\n hasInternet: boolean;\n };\n ble: {\n connected: boolean;\n transferInProgress: boolean;\n };\n camera: {\n available: boolean;\n initialized: boolean;\n };\n storage: {\n availableSpace: number;\n totalSpace: number;\n };\n}\n\n/**\n * Detailed error information for photo failures\n */\nexport interface PhotoErrorDetails {\n stage: PhotoStage;\n connectionState?: ConnectionState;\n retryable: boolean;\n suggestedAction?: string;\n diagnosticInfo?: {\n timestamp: number;\n duration: number;\n retryCount: number;\n lastSuccessfulStage?: PhotoStage;\n };\n}\n\n/**\n * Enhanced photo response with error support\n */\nexport interface PhotoResponse extends BaseMessage {\n type: GlassesToCloudMessageType.PHOTO_RESPONSE;\n requestId: string; // Unique ID for the photo request\n success: boolean; // Explicit success/failure flag\n\n // Success fields (only present when success = true)\n photoUrl?: string; // URL of the uploaded photo\n savedToGallery?: boolean; // Whether the photo was saved to gallery\n\n // Error fields (only present when success = false)\n error?: {\n code: PhotoErrorCode;\n message: string;\n details?: PhotoErrorDetails;\n };\n}\n\n/**\n * RGB LED control response from glasses\n */\nexport interface RgbLedControlResponse extends BaseMessage {\n type: GlassesToCloudMessageType.RGB_LED_CONTROL_RESPONSE;\n requestId: string;\n success: boolean;\n error?: string;\n}\n\n/**\n * RTMP stream status update from glasses\n */\nexport interface RtmpStreamStatus extends BaseMessage {\n type: GlassesToCloudMessageType.RTMP_STREAM_STATUS;\n streamId?: string; // Unique identifier for the stream\n status:\n | \"initializing\"\n | \"connecting\"\n | \"reconnecting\"\n | \"streaming\"\n | \"error\"\n | \"stopped\"\n | \"active\"\n | \"stopping\"\n | \"disconnected\"\n | \"timeout\"\n | \"reconnected\"\n | \"reconnect_failed\";\n errorDetails?: string;\n appId?: string; // ID of the app that requested the stream\n stats?: {\n bitrate: number;\n fps: number;\n droppedFrames: number;\n duration: number;\n };\n}\n\n/**\n * Keep-alive acknowledgment from glasses\n */\nexport interface KeepAliveAck extends BaseMessage {\n type: GlassesToCloudMessageType.KEEP_ALIVE_ACK;\n streamId: string; // ID of the stream being kept alive\n ackId: string; // Acknowledgment ID that was sent by cloud\n}\n\n/**\n * Photo taken event from glasses\n */\nexport interface PhotoTaken extends BaseMessage {\n type: GlassesToCloudMessageType.PHOTO_TAKEN;\n photoData: ArrayBuffer;\n mimeType: string;\n timestamp: Date;\n}\n\n/**\n * Audio play response from glasses/core\n */\nexport interface AudioPlayResponse extends BaseMessage {\n type: GlassesToCloudMessageType.AUDIO_PLAY_RESPONSE;\n requestId: string;\n success: boolean;\n error?: string;\n duration?: number;\n}\n\n/**\n * UDP audio registration request from glasses/phone\n * Mobile sends this to register its userIdHash for UDP audio routing\n */\nexport interface UdpRegister extends BaseMessage {\n type: GlassesToCloudMessageType.UDP_REGISTER;\n userIdHash: number; // FNV-1a 32-bit hash of userId\n}\n\n/**\n * UDP audio unregistration request from glasses/phone\n * Mobile sends this when stopping UDP audio\n */\nexport interface UdpUnregister extends BaseMessage {\n type: GlassesToCloudMessageType.UDP_UNREGISTER;\n userIdHash: number; // FNV-1a 32-bit hash of userId\n}\n\n/**\n * Union type for all messages from glasses to cloud\n */\nexport type GlassesToCloudMessage =\n | ConnectionInit\n | LiveKitInit\n | RequestSettings\n | StartApp\n | StopApp\n | DashboardState\n | OpenDashboard\n | ButtonPress\n | HeadPosition\n | TouchEvent\n | GlassesBatteryUpdate\n | PhoneBatteryUpdate\n | GlassesConnectionState\n | LocationUpdate\n | VpsCoordinates\n | CalendarEvent\n | Vad\n | PhoneNotification\n | PhoneNotificationDismissed\n | MentraosSettingsUpdateRequest\n | CoreStatusUpdate\n | RtmpStreamStatus\n | KeepAliveAck\n | PhotoResponse\n | RgbLedControlResponse\n | PhotoTaken\n | AudioPlayResponse\n | LocalTranscription\n | UdpRegister\n | UdpUnregister;\n\n//===========================================================\n// Type guards\n//===========================================================\n\nexport function isControlAction(message: GlassesToCloudMessage): boolean {\n return ControlActionTypes.includes(message.type as any);\n}\n\nexport function isEvent(message: GlassesToCloudMessage): boolean {\n return EventTypes.includes(message.type as any);\n}\n\n// Individual type guards\nexport function isConnectionInit(message: GlassesToCloudMessage): message is ConnectionInit {\n return message.type === GlassesToCloudMessageType.CONNECTION_INIT;\n}\n\nexport function isRequestSettings(message: GlassesToCloudMessage): message is RequestSettings {\n return message.type === GlassesToCloudMessageType.REQUEST_SETTINGS;\n}\n\nexport function isStartApp(message: GlassesToCloudMessage): message is StartApp {\n return message.type === GlassesToCloudMessageType.START_APP;\n}\n\nexport function isStopApp(message: GlassesToCloudMessage): message is StopApp {\n return message.type === GlassesToCloudMessageType.STOP_APP;\n}\n\nexport function isButtonPress(message: GlassesToCloudMessage): message is ButtonPress {\n return message.type === GlassesToCloudMessageType.BUTTON_PRESS;\n}\n\nexport function isHeadPosition(message: GlassesToCloudMessage): message is HeadPosition {\n return message.type === GlassesToCloudMessageType.HEAD_POSITION;\n}\n\nexport function isGlassesBatteryUpdate(message: GlassesToCloudMessage): message is GlassesBatteryUpdate {\n return message.type === GlassesToCloudMessageType.GLASSES_BATTERY_UPDATE;\n}\n\nexport function isPhoneBatteryUpdate(message: GlassesToCloudMessage): message is PhoneBatteryUpdate {\n return message.type === GlassesToCloudMessageType.PHONE_BATTERY_UPDATE;\n}\n\nexport function isGlassesConnectionState(message: GlassesToCloudMessage): message is GlassesConnectionState {\n return message.type === GlassesToCloudMessageType.GLASSES_CONNECTION_STATE;\n}\n\nexport function isLocationUpdate(message: GlassesToCloudMessage): message is LocationUpdate {\n return message.type === GlassesToCloudMessageType.LOCATION_UPDATE;\n}\n\nexport function isCalendarEvent(message: GlassesToCloudMessage): message is CalendarEvent {\n return message.type === GlassesToCloudMessageType.CALENDAR_EVENT;\n}\n\nexport function isVad(message: GlassesToCloudMessage): message is Vad {\n return message.type === GlassesToCloudMessageType.VAD;\n}\n\nexport function isPhoneNotification(message: GlassesToCloudMessage): message is PhoneNotification {\n return message.type === GlassesToCloudMessageType.PHONE_NOTIFICATION;\n}\n\nexport function isPhoneNotificationDismissed(message: GlassesToCloudMessage): message is PhoneNotificationDismissed {\n return message.type === GlassesToCloudMessageType.PHONE_NOTIFICATION_DISMISSED;\n}\n\nexport function isRtmpStreamStatus(message: GlassesToCloudMessage): message is RtmpStreamStatus {\n return message.type === GlassesToCloudMessageType.RTMP_STREAM_STATUS;\n}\n\nexport function isPhotoResponse(message: GlassesToCloudMessage): message is PhotoResponse {\n return message.type === GlassesToCloudMessageType.PHOTO_RESPONSE;\n}\n\nexport function isRgbLedControlResponse(message: GlassesToCloudMessage): message is RgbLedControlResponse {\n return message.type === GlassesToCloudMessageType.RGB_LED_CONTROL_RESPONSE;\n}\n\nexport function isKeepAliveAck(message: GlassesToCloudMessage): message is KeepAliveAck {\n return message.type === GlassesToCloudMessageType.KEEP_ALIVE_ACK;\n}\n\nexport function isPhotoTaken(message: GlassesToCloudMessage): message is PhotoTaken {\n return message.type === GlassesToCloudMessageType.PHOTO_TAKEN;\n}\n\nexport function isAudioPlayResponse(message: GlassesToCloudMessage): message is AudioPlayResponse {\n return message.type === GlassesToCloudMessageType.AUDIO_PLAY_RESPONSE;\n}\n\nexport function isLocalTranscription(message: GlassesToCloudMessage): message is LocalTranscription {\n return message.type === GlassesToCloudMessageType.LOCAL_TRANSCRIPTION;\n}\n\nexport function isUdpRegister(message: GlassesToCloudMessage): message is UdpRegister {\n return message.type === GlassesToCloudMessageType.UDP_REGISTER;\n}\n\nexport function isUdpUnregister(message: GlassesToCloudMessage): message is UdpUnregister {\n return message.type === GlassesToCloudMessageType.UDP_UNREGISTER;\n}\n",
12
- "// src/messages/cloud-to-glasses.ts\n\nimport { Layout } from \"../layouts\";\nimport { CloudToGlassesMessageType, ResponseTypes, UpdateTypes } from \"../message-types\";\n\nimport { BaseMessage } from \"./base\";\n// import { UserSession } from \"../user-session\";\n\n//===========================================================\n// Responses\n//===========================================================\n\n/**\n * Connection acknowledgment to glasses\n */\nexport interface ConnectionAck extends BaseMessage {\n type: CloudToGlassesMessageType.CONNECTION_ACK;\n // userSession: Partial<UserSession>;\n sessionId: string;\n livekit?: {\n url: string;\n roomName: string;\n token: string;\n };\n}\n\n/**\n * Connection error to glasses\n */\nexport interface ConnectionError extends BaseMessage {\n type: CloudToGlassesMessageType.CONNECTION_ERROR;\n code?: string;\n message: string;\n}\n\n/**\n * Authentication error to glasses\n */\nexport interface AuthError extends BaseMessage {\n type: CloudToGlassesMessageType.AUTH_ERROR;\n message: string;\n}\n\n//===========================================================\n// Updates\n//===========================================================\n\n/**\n * Display update to glasses\n */\nexport interface DisplayEvent extends BaseMessage {\n type: CloudToGlassesMessageType.DISPLAY_EVENT;\n layout: Layout;\n durationMs?: number;\n}\n\n/**\n * App state change to glasses\n */\nexport interface AppStateChange extends BaseMessage {\n type: CloudToGlassesMessageType.APP_STATE_CHANGE;\n // userSession: Partial<UserSession>;\n error?: string;\n}\n\n/**\n * Microphone state change to glasses\n */\nexport interface MicrophoneStateChange extends BaseMessage {\n type: CloudToGlassesMessageType.MICROPHONE_STATE_CHANGE;\n // userSession: Partial<UserSession>;\n isMicrophoneEnabled: boolean;\n requiredData: Array<\"pcm\" | \"transcription\" | \"pcm_or_transcription\">;\n bypassVad?: boolean; // NEW: PCM subscription bypass\n}\n\n/**\n * Photo request to glasses\n */\nexport interface PhotoRequestToGlasses extends BaseMessage {\n type: CloudToGlassesMessageType.PHOTO_REQUEST;\n // userSession: Partial<UserSession>;\n requestId: string;\n appId: string;\n saveToGallery?: boolean;\n webhookUrl?: string; // URL where ASG should send the photo directly\n authToken?: string; // Auth token for webhook authentication\n /** Desired capture size to guide device resolution selection */\n size?: \"small\" | \"medium\" | \"large\" | \"full\";\n /** Image compression level: none, medium, or heavy */\n compress?: \"none\" | \"medium\" | \"heavy\";\n}\n\n/**\n * LED color type for RGB LED control\n */\nexport type LedColor = \"red\" | \"green\" | \"blue\" | \"orange\" | \"white\";\n\n/**\n * RGB LED control request to glasses\n */\nexport interface RgbLedControlToGlasses extends BaseMessage {\n type: CloudToGlassesMessageType.RGB_LED_CONTROL;\n requestId: string;\n appId: string;\n action: \"on\" | \"off\"; // Only low-level on/off actions\n color?: LedColor; // LED color name\n ontime?: number;\n offtime?: number;\n count?: number;\n}\n\n// TODO(isaiah): Deprecated, remove this after new mobile client refactor complete, and we migrate to SettingsStateChange.\n/**\n * Settings update to glasses\n */\nexport interface SettingsUpdate extends BaseMessage {\n type: CloudToGlassesMessageType.SETTINGS_UPDATE;\n sessionId: string;\n settings: {\n useOnboardMic: boolean;\n contextualDashboard: boolean;\n metricSystemEnabled: boolean;\n headUpAngle: number;\n brightness: number;\n autoBrightness: boolean;\n sensingEnabled: boolean;\n alwaysOnStatusBar: boolean;\n bypassVad: boolean;\n bypassAudioEncoding: boolean;\n };\n}\n\n/**\n * LiveKit info for client to connect & publish\n */\nexport interface LiveKitInfo extends BaseMessage {\n type: CloudToGlassesMessageType.LIVEKIT_INFO;\n url: string;\n roomName: string;\n token: string;\n}\n\n//===========================================================\n// RTMP Streaming Commands\n//===========================================================\n\n/**\n * Start RTMP stream command to glasses\n */\nexport interface StartRtmpStream extends BaseMessage {\n type: CloudToGlassesMessageType.START_RTMP_STREAM;\n rtmpUrl: string;\n appId: string;\n streamId?: string;\n video?: any; // Video configuration\n audio?: any; // Audio configuration\n stream?: any; // Stream configuration\n}\n\n/**\n * Stop RTMP stream command to glasses\n */\nexport interface StopRtmpStream extends BaseMessage {\n type: CloudToGlassesMessageType.STOP_RTMP_STREAM;\n appId: string;\n streamId?: string;\n}\n\n/**\n * Keep RTMP stream alive command to glasses\n */\nexport interface KeepRtmpStreamAlive extends BaseMessage {\n type: CloudToGlassesMessageType.KEEP_RTMP_STREAM_ALIVE;\n streamId: string;\n ackId: string;\n}\n\n//===========================================================\n// Location Service Commands\n//===========================================================\n\n/**\n * Sets the continuous location update tier on the device.\n */\nexport interface SetLocationTier extends BaseMessage {\n type: CloudToGlassesMessageType.SET_LOCATION_TIER;\n tier: \"realtime\" | \"high\" | \"tenMeters\" | \"hundredMeters\" | \"kilometer\" | \"threeKilometers\" | \"reduced\" | \"standard\";\n}\n\n/**\n * Requests a single, on-demand location fix from the device.\n */\nexport interface RequestSingleLocation extends BaseMessage {\n type: CloudToGlassesMessageType.REQUEST_SINGLE_LOCATION;\n accuracy: string; // The accuracy tier requested by the app\n correlationId: string; // To match the response with the poll request\n}\n\n/**\n * Audio play request to glasses\n */\nexport interface AudioPlayRequestToGlasses extends BaseMessage {\n type: CloudToGlassesMessageType.AUDIO_PLAY_REQUEST;\n // userSession: Partial<UserSession>;\n requestId: string;\n appId: string;\n audioUrl: string; // URL to audio file for download and play\n volume?: number; // Volume level 0.0-1.0, defaults to 1.0\n stopOtherAudio?: boolean; // Whether to stop other audio playback, defaults to true\n}\n\n/**\n * Audio stop request to glasses\n */\nexport interface AudioStopRequestToGlasses extends BaseMessage {\n type: CloudToGlassesMessageType.AUDIO_STOP_REQUEST;\n // userSession: Partial<UserSession>;\n appId: string;\n}\n\n/**\n * WiFi setup request to glasses/mobile\n */\nexport interface ShowWifiSetup extends BaseMessage {\n type: CloudToGlassesMessageType.SHOW_WIFI_SETUP;\n reason?: string;\n appPackageName?: string;\n}\n\n/**\n * UDP ping acknowledgment from cloud to mobile\n * Sent when the Go UDP listener receives a ping from the mobile client\n */\nexport interface UdpPingAck extends BaseMessage {\n type: CloudToGlassesMessageType.UDP_PING_ACK;\n}\n\n/**\n * Union type for all messages from cloud to glasses\n */\nexport type CloudToGlassesMessage =\n | ConnectionAck\n | ConnectionError\n | AuthError\n | DisplayEvent\n | AppStateChange\n | MicrophoneStateChange\n | PhotoRequestToGlasses\n | RgbLedControlToGlasses\n | AudioPlayRequestToGlasses\n | AudioStopRequestToGlasses\n | SettingsUpdate\n | StartRtmpStream\n | StopRtmpStream\n | KeepRtmpStreamAlive\n | SetLocationTier\n | RequestSingleLocation\n | LiveKitInfo\n | ShowWifiSetup\n | UdpPingAck;\n\n//===========================================================\n// Type guards\n//===========================================================\n\nexport function isResponse(message: CloudToGlassesMessage): boolean {\n return ResponseTypes.includes(message.type as any);\n}\n\nexport function isUpdate(message: CloudToGlassesMessage): boolean {\n return UpdateTypes.includes(message.type as any);\n}\n\n// Individual type guards\nexport function isConnectionAck(message: CloudToGlassesMessage): message is ConnectionAck {\n return message.type === CloudToGlassesMessageType.CONNECTION_ACK;\n}\n\nexport function isConnectionError(message: CloudToGlassesMessage): message is ConnectionError {\n return message.type === CloudToGlassesMessageType.CONNECTION_ERROR;\n}\n\nexport function isAuthError(message: CloudToGlassesMessage): message is AuthError {\n return message.type === CloudToGlassesMessageType.AUTH_ERROR;\n}\n\nexport function isDisplayEvent(message: CloudToGlassesMessage): message is DisplayEvent {\n return message.type === CloudToGlassesMessageType.DISPLAY_EVENT;\n}\n\nexport function isAppStateChange(message: CloudToGlassesMessage): message is AppStateChange {\n return message.type === CloudToGlassesMessageType.APP_STATE_CHANGE;\n}\n\nexport function isMicrophoneStateChange(message: CloudToGlassesMessage): message is MicrophoneStateChange {\n return message.type === CloudToGlassesMessageType.MICROPHONE_STATE_CHANGE;\n}\n\nexport function isPhotoRequest(message: CloudToGlassesMessage): message is PhotoRequestToGlasses {\n return message.type === CloudToGlassesMessageType.PHOTO_REQUEST;\n}\n\nexport function isRgbLedControl(message: CloudToGlassesMessage): message is RgbLedControlToGlasses {\n return message.type === CloudToGlassesMessageType.RGB_LED_CONTROL;\n}\n\nexport function isSettingsUpdate(message: CloudToGlassesMessage): message is SettingsUpdate {\n return message.type === CloudToGlassesMessageType.SETTINGS_UPDATE;\n}\n\nexport function isStartRtmpStream(message: CloudToGlassesMessage): message is StartRtmpStream {\n return message.type === CloudToGlassesMessageType.START_RTMP_STREAM;\n}\n\nexport function isStopRtmpStream(message: CloudToGlassesMessage): message is StopRtmpStream {\n return message.type === CloudToGlassesMessageType.STOP_RTMP_STREAM;\n}\n\nexport function isKeepRtmpStreamAlive(message: CloudToGlassesMessage): message is KeepRtmpStreamAlive {\n return message.type === CloudToGlassesMessageType.KEEP_RTMP_STREAM_ALIVE;\n}\n\nexport function isAudioPlayRequestToGlasses(message: CloudToGlassesMessage): message is AudioPlayRequestToGlasses {\n return message.type === CloudToGlassesMessageType.AUDIO_PLAY_REQUEST;\n}\n\nexport function isAudioStopRequestToGlasses(message: CloudToGlassesMessage): message is AudioStopRequestToGlasses {\n return message.type === CloudToGlassesMessageType.AUDIO_STOP_REQUEST;\n}\n",
13
- "// src/messages/app-to-cloud.ts\n\nimport {BaseMessage} from \"./base\"\nimport {AppToCloudMessageType} from \"../message-types\"\nimport {ExtendedStreamType, LocationStreamRequest} from \"../streams\"\nimport {DisplayRequest} from \"../layouts\"\nimport {DashboardContentUpdate, DashboardModeChange, DashboardSystemUpdate} from \"../dashboard\"\nimport type {VideoConfig, AudioConfig, StreamConfig} from \"../rtmp-stream\"\nimport {LedColor} from \"./cloud-to-glasses\"\n\n// a subscription can now be either a simple string or our new rich object\nexport type SubscriptionRequest = ExtendedStreamType | LocationStreamRequest\n\n/**\n * Connection initialization from App\n */\nexport interface AppConnectionInit extends BaseMessage {\n type: AppToCloudMessageType.CONNECTION_INIT\n packageName: string\n sessionId: string\n apiKey: string\n}\n\n/**\n * Subscription update from App\n */\nexport interface AppSubscriptionUpdate extends BaseMessage {\n type: AppToCloudMessageType.SUBSCRIPTION_UPDATE\n packageName: string\n subscriptions: SubscriptionRequest[]\n}\n\n/**\n * Photo request from App\n */\nexport interface PhotoRequest extends BaseMessage {\n type: AppToCloudMessageType.PHOTO_REQUEST\n packageName: string\n requestId: string // SDK-generated request ID to track the request\n saveToGallery?: boolean\n customWebhookUrl?: string // Custom webhook URL to override TPA's default\n authToken?: string // Auth token for custom webhook authentication\n /** Desired photo size sent by App. Defaults to 'medium' if omitted. */\n size?: \"small\" | \"medium\" | \"large\" | \"full\"\n /** Image compression level: none, medium, or heavy. Defaults to none. */\n compress?: \"none\" | \"medium\" | \"heavy\"\n}\n\n/**\n * RGB LED control request from App\n */\nexport interface RgbLedControlRequest extends BaseMessage {\n type: AppToCloudMessageType.RGB_LED_CONTROL\n packageName: string\n requestId: string // SDK-generated request ID to track the request\n action: \"on\" | \"off\" // Only low-level on/off actions\n color?: LedColor // LED color name\n ontime?: number // LED on duration in ms\n offtime?: number // LED off duration in ms\n count?: number // Number of on/off cycles\n}\n\n// Video, Audio and Stream configuration interfaces are imported from '../rtmp-stream'\n\n/**\n * RTMP stream request from App\n */\nexport interface RtmpStreamRequest extends BaseMessage {\n type: AppToCloudMessageType.RTMP_STREAM_REQUEST\n packageName: string\n rtmpUrl: string\n video?: VideoConfig\n audio?: AudioConfig\n stream?: StreamConfig\n}\n\n/**\n * RTMP stream stop request from App\n */\nexport interface RtmpStreamStopRequest extends BaseMessage {\n type: AppToCloudMessageType.RTMP_STREAM_STOP\n packageName: string\n streamId?: string // Optional stream ID to specify which stream to stop\n}\n\n// defines the structure for our new on-demand location poll command\nexport interface AppLocationPollRequest extends BaseMessage {\n type: AppToCloudMessageType.LOCATION_POLL_REQUEST\n packageName: string\n sessionId: string\n accuracy: string\n correlationId: string\n}\n\n/**\n * Re-stream destination for managed streams\n */\nexport interface RestreamDestination {\n /** RTMP URL like rtmp://youtube.com/live/STREAM-KEY */\n url: string\n /** Optional friendly name like \"YouTube\" or \"Twitch\" */\n name?: string\n}\n\n/**\n * Managed RTMP stream request from App\n * The cloud handles the RTMP endpoint and returns HLS/DASH URLs\n */\nexport interface ManagedStreamRequest extends BaseMessage {\n type: AppToCloudMessageType.MANAGED_STREAM_REQUEST\n packageName: string\n quality?: \"720p\" | \"1080p\"\n enableWebRTC?: boolean\n video?: VideoConfig\n audio?: AudioConfig\n stream?: StreamConfig\n /** Optional RTMP destinations to re-stream to (YouTube, Twitch, etc) */\n restreamDestinations?: RestreamDestination[]\n}\n\n/**\n * Managed RTMP stream stop request from App\n */\nexport interface ManagedStreamStopRequest extends BaseMessage {\n type: AppToCloudMessageType.MANAGED_STREAM_STOP\n packageName: string\n}\n\n/**\n * Stream status check request from App\n * Checks if there are any existing streams (managed or unmanaged) for the current user\n */\nexport interface StreamStatusCheckRequest extends BaseMessage {\n type: AppToCloudMessageType.STREAM_STATUS_CHECK\n packageName: string\n sessionId: string\n}\n\n/**\n * Audio play request from App\n */\nexport interface AudioPlayRequest extends BaseMessage {\n type: AppToCloudMessageType.AUDIO_PLAY_REQUEST\n packageName: string\n requestId: string // SDK-generated request ID to track the request\n audioUrl: string // URL to audio file for download and play\n volume?: number // Volume level 0.0-1.0, defaults to 1.0\n stopOtherAudio?: boolean // Whether to stop other audio playback, defaults to true\n /**\n * Track ID for audio playback (defaults to 0)\n * - 0: speaker (default audio playback)\n * - 1: app_audio (app-specific audio)\n * - 2: tts (text-to-speech audio)\n * Use different track IDs to play multiple audio streams simultaneously (mixing)\n */\n trackId?: number\n}\n\n/**\n * Audio stop request from App\n */\nexport interface AudioStopRequest extends BaseMessage {\n type: AppToCloudMessageType.AUDIO_STOP_REQUEST\n packageName: string\n /**\n * Track ID to stop (optional)\n * 0: speaker (default audio playback)\n * 1: app_audio (app-specific audio)\n * 2: tts (text-to-speech audio)\n * If omitted, stops all tracks\n */\n trackId?: number\n sessionId?: string // Session ID for routing\n}\n\n/**\n * WiFi setup request from App\n */\nexport interface RequestWifiSetup extends BaseMessage {\n type: AppToCloudMessageType.REQUEST_WIFI_SETUP\n packageName: string\n sessionId: string\n reason?: string\n}\n\n/**\n * Ownership release message from App\n * Sent before intentional disconnect to signal clean handoff (no resurrection needed)\n */\nexport interface OwnershipReleaseMessage extends BaseMessage {\n type: AppToCloudMessageType.OWNERSHIP_RELEASE\n packageName: string\n sessionId: string\n reason: \"switching_clouds\" | \"clean_shutdown\" | \"user_logout\"\n}\n\n/**\n * Union type for all messages from Apps to cloud\n */\nexport type AppToCloudMessage =\n | AppConnectionInit\n | AppSubscriptionUpdate\n | AppLocationPollRequest\n | DisplayRequest\n | PhotoRequest\n | RgbLedControlRequest\n | AudioPlayRequest\n | AudioStopRequest\n | RtmpStreamRequest\n | RtmpStreamStopRequest\n | ManagedStreamRequest\n | ManagedStreamStopRequest\n | StreamStatusCheckRequest\n | DashboardContentUpdate\n | DashboardModeChange\n | DashboardSystemUpdate\n | RequestWifiSetup\n // Session lifecycle\n | OwnershipReleaseMessage\n // New App-to-App communication messages\n | AppBroadcastMessage\n | AppDirectMessage\n | AppUserDiscovery\n | AppRoomJoin\n | AppRoomLeave\n\n/**\n * Type guard to check if a message is a App connection init\n */\nexport function isAppConnectionInit(message: AppToCloudMessage): message is AppConnectionInit {\n return message.type === AppToCloudMessageType.CONNECTION_INIT\n}\n\n/**\n * Type guard to check if a message is a App subscription update\n */\nexport function isAppSubscriptionUpdate(message: AppToCloudMessage): message is AppSubscriptionUpdate {\n return message.type === AppToCloudMessageType.SUBSCRIPTION_UPDATE\n}\n\n/**\n * Type guard to check if a message is a App display request\n */\nexport function isDisplayRequest(message: AppToCloudMessage): message is DisplayRequest {\n return message.type === AppToCloudMessageType.DISPLAY_REQUEST\n}\n\n/**\n * Type guard to check if a message is a App photo request\n */\nexport function isPhotoRequest(message: AppToCloudMessage): message is PhotoRequest {\n return message.type === AppToCloudMessageType.PHOTO_REQUEST\n}\n\n/**\n * Type guard to check if a message is a RGB LED control request\n */\nexport function isRgbLedControlRequest(message: AppToCloudMessage): message is RgbLedControlRequest {\n return message.type === AppToCloudMessageType.RGB_LED_CONTROL\n}\n\n/**\n * Type guard to check if a message is a App audio play request\n */\nexport function isAudioPlayRequest(message: AppToCloudMessage): message is AudioPlayRequest {\n return message.type === AppToCloudMessageType.AUDIO_PLAY_REQUEST\n}\n\n/**\n * Type guard to check if a message is a App audio stop request\n */\nexport function isAudioStopRequest(message: AppToCloudMessage): message is AudioStopRequest {\n return message.type === AppToCloudMessageType.AUDIO_STOP_REQUEST\n}\n\n/**\n * Type guard to check if a message is a dashboard content update\n */\nexport function isDashboardContentUpdate(message: AppToCloudMessage): message is DashboardContentUpdate {\n return message.type === AppToCloudMessageType.DASHBOARD_CONTENT_UPDATE\n}\n\n/**\n * Type guard to check if a message is a dashboard mode change\n */\nexport function isDashboardModeChange(message: AppToCloudMessage): message is DashboardModeChange {\n return message.type === AppToCloudMessageType.DASHBOARD_MODE_CHANGE\n}\n\n/**\n * Type guard to check if a message is a dashboard system update\n */\nexport function isDashboardSystemUpdate(message: AppToCloudMessage): message is DashboardSystemUpdate {\n return message.type === AppToCloudMessageType.DASHBOARD_SYSTEM_UPDATE\n}\n\n/**\n * Type guard to check if a message is a managed stream request\n */\nexport function isManagedStreamRequest(message: AppToCloudMessage): message is ManagedStreamRequest {\n return message.type === AppToCloudMessageType.MANAGED_STREAM_REQUEST\n}\n\n/**\n * Type guard to check if a message is a managed stream stop request\n */\nexport function isManagedStreamStopRequest(message: AppToCloudMessage): message is ManagedStreamStopRequest {\n return message.type === AppToCloudMessageType.MANAGED_STREAM_STOP\n}\n\n//===========================================================\n// App-to-App Communication Messages\n//===========================================================\n\n/**\n * Broadcast message to all users with the same App active\n */\nexport interface AppBroadcastMessage extends BaseMessage {\n type: AppToCloudMessageType.APP_BROADCAST_MESSAGE\n packageName: string\n sessionId: string\n payload: any\n messageId: string\n senderUserId: string\n}\n\n/**\n * Direct message to a specific user with the same App active\n */\nexport interface AppDirectMessage extends BaseMessage {\n type: AppToCloudMessageType.APP_DIRECT_MESSAGE\n packageName: string\n sessionId: string\n targetUserId: string\n payload: any\n messageId: string\n senderUserId: string\n}\n\n/**\n * Request to discover other users with the same App active\n */\nexport interface AppUserDiscovery extends BaseMessage {\n type: AppToCloudMessageType.APP_USER_DISCOVERY\n packageName: string\n sessionId: string\n includeUserProfiles?: boolean\n}\n\n/**\n * Join a communication room for group messaging\n */\nexport interface AppRoomJoin extends BaseMessage {\n type: AppToCloudMessageType.APP_ROOM_JOIN\n packageName: string\n sessionId: string\n roomId: string\n roomConfig?: {\n maxUsers?: number\n isPrivate?: boolean\n metadata?: any\n }\n}\n\n/**\n * Leave a communication room\n */\nexport interface AppRoomLeave extends BaseMessage {\n type: AppToCloudMessageType.APP_ROOM_LEAVE\n packageName: string\n sessionId: string\n roomId: string\n}\n\n/**\n * Type guard to check if a message is an RTMP stream request\n */\nexport function isRtmpStreamRequest(message: AppToCloudMessage): message is RtmpStreamRequest {\n return message.type === AppToCloudMessageType.RTMP_STREAM_REQUEST\n}\n\n/**\n * Type guard to check if a message is an RTMP stream stop request\n */\nexport function isRtmpStreamStopRequest(message: AppToCloudMessage): message is RtmpStreamStopRequest {\n return message.type === AppToCloudMessageType.RTMP_STREAM_STOP\n}\n\n/**\n * Type guard to check if a message is an ownership release message\n */\nexport function isOwnershipRelease(message: AppToCloudMessage): message is OwnershipReleaseMessage {\n return message.type === AppToCloudMessageType.OWNERSHIP_RELEASE\n}\n",
23
+ "import EventEmitter from \"events\";\nimport type { Logger } from \"pino\";\nimport { createLogger, type LoggerConfig } from \"../logging/logger\";\nimport type { AppConfig, AppSettings, Capabilities } from \"../types\";\nimport { AppToCloudMessageType, CloudToAppMessageType } from \"../types/message-types\";\nimport type { Transport } from \"../transport/Transport\";\nimport { CameraManager } from \"./managers/CameraManager\";\nimport { DashboardManager } from \"./managers/DashboardManager\";\nimport { DeviceManager } from \"./managers/DeviceManager\";\nimport { DisplayManager } from \"./managers/DisplayManager\";\nimport { LedManager } from \"./managers/LedManager\";\nimport { LocationManager } from \"./managers/LocationManager\";\nimport { MicManager } from \"./managers/MicManager\";\nimport { PermissionsManager } from \"./managers/PermissionsManager\";\nimport { PhoneManager } from \"./managers/PhoneManager\";\nimport { SpeakerManager } from \"./managers/SpeakerManager\";\nimport { StorageManager } from \"./managers/StorageManager\";\nimport { TimeUtils } from \"./managers/TimeUtils\";\nimport { TranscriptionManager } from \"./managers/TranscriptionManager\";\nimport { TranslationManager } from \"./managers/TranslationManager\";\nimport { _MessageRouter } from \"./internal/_MessageRouter\";\nimport { _ConnectionManager } from \"./internal/_ConnectionManager\";\nimport { _SubscriptionManager } from \"./internal/_SubscriptionManager\";\n\nexport interface MentraSessionConfig extends LoggerConfig {\n packageName: string;\n apiKey: string;\n sessionId: string;\n transport: Transport;\n userId?: string;\n serverUrl?: string;\n autoReconnect?: boolean;\n maxReconnectAttempts?: number;\n reconnectDelay?: number;\n logger?: Logger;\n}\n\ntype SessionEventMap = {\n connected: [AppSettings];\n disconnected: [{ code: number; reason: string; permanent: boolean }];\n error: [Error];\n stopped: [string];\n settings: [AppSettings];\n reconnected: [];\n};\n\nconst DEFAULT_RECONNECT_ATTEMPTS = 3;\nconst DEFAULT_RECONNECT_DELAY_MS = 1_000;\nconst DEFAULT_PARKED_TIMEOUT_MS = 30_000;\nconst SDK_VERSION = \"3.0.0-hono.8\";\n\nexport class MentraSession {\n readonly transport: Transport;\n readonly logger: Logger;\n\n readonly permissions: PermissionsManager;\n readonly transcription: TranscriptionManager;\n readonly translation: TranslationManager;\n readonly display: DisplayManager;\n readonly speaker: SpeakerManager;\n readonly mic: MicManager;\n readonly device: DeviceManager;\n readonly phone: PhoneManager;\n readonly camera: CameraManager;\n readonly led: LedManager;\n readonly location: LocationManager;\n readonly dashboard: DashboardManager;\n readonly storage: StorageManager;\n readonly time: TimeUtils;\n\n settingsData: AppSettings = [];\n mentraosSettings: Record<string, any> = {};\n appConfig: AppConfig | null = null;\n capabilities: Capabilities | null = null;\n private runtimeSessionId: string;\n private hasCompletedInitialConnect = false;\n\n private readonly config: Required<\n Pick<\n MentraSessionConfig,\n \"packageName\" | \"apiKey\" | \"sessionId\" | \"autoReconnect\" | \"maxReconnectAttempts\" | \"reconnectDelay\"\n >\n > &\n Pick<MentraSessionConfig, \"userId\" | \"serverUrl\">;\n private readonly lifecycle = new EventEmitter();\n private readonly cleanupTasks: Array<() => void | Promise<void>> = [];\n private readonly _router: _MessageRouter;\n private readonly _subscriptions: _SubscriptionManager;\n private readonly _lifecycleManager: _ConnectionManager;\n\n constructor(config: MentraSessionConfig) {\n this.transport = config.transport;\n this.runtimeSessionId = config.sessionId;\n this.config = {\n packageName: config.packageName,\n apiKey: config.apiKey,\n sessionId: config.sessionId,\n userId: config.userId,\n serverUrl: config.serverUrl,\n autoReconnect: config.autoReconnect ?? true,\n maxReconnectAttempts: config.maxReconnectAttempts ?? DEFAULT_RECONNECT_ATTEMPTS,\n reconnectDelay: config.reconnectDelay ?? DEFAULT_RECONNECT_DELAY_MS,\n };\n\n this.logger =\n config.logger ??\n createLogger({\n logLevel: config.logLevel,\n verbose: config.verbose,\n }).child({\n packageName: this.config.packageName,\n sessionId: this.config.sessionId,\n service: \"mentra-session\",\n });\n\n // SDK internal logger — tagged with _sdk: true so the clean transport\n // filters it to warn+ in the terminal. BetterStack still gets everything.\n // Developer's session.logger (above) has no _sdk tag → always visible.\n const sdkLogger = this.logger.child({ _sdk: true });\n\n this._router = new _MessageRouter(sdkLogger);\n this._subscriptions = new _SubscriptionManager({\n logger: sdkLogger,\n isConnected: () => this.isConnected,\n sendMessage: this.sendMessage.bind(this),\n getPackageName: () => this.config.packageName,\n getSessionId: () => this.runtimeSessionId,\n });\n this._lifecycleManager = new _ConnectionManager({\n transport: this.transport,\n logger: sdkLogger,\n autoReconnect: this.config.autoReconnect,\n maxReconnectAttempts: this.config.maxReconnectAttempts,\n reconnectDelay: this.config.reconnectDelay,\n onTransportReady: () => this.sendHandshake(),\n onTextMessage: (raw) => this.handleTextMessage(raw),\n onBinaryMessage: (data) => this.mic.handleBinaryAudio(data),\n onClose: (info) => this.emit(\"disconnected\", info),\n onError: (error) => {\n this.emit(\"error\", error);\n this.logger.error(error, \"MentraSession transport error\");\n },\n });\n\n this.permissions = new PermissionsManager({ logger: sdkLogger });\n\n const deps = {\n router: this._router.dataStreamRouter,\n messageHandlers: this._router.messageHandlers,\n addSubscription: (stream: string) => this._subscriptions.add(stream),\n removeSubscription: (stream: string) => this._subscriptions.remove(stream),\n sendMessage: this.sendMessage.bind(this),\n sendBinary: this.sendBinary.bind(this),\n logger: sdkLogger,\n getPackageName: () => this.config.packageName,\n getSessionId: () => this.runtimeSessionId,\n getServerUrl: () => this.getServerUrl(),\n permissions: this.permissions,\n };\n\n this.transcription = new TranscriptionManager(deps);\n this.translation = new TranslationManager(deps);\n this.display = new DisplayManager(deps);\n this.speaker = new SpeakerManager(deps);\n this.mic = new MicManager(deps);\n this.device = new DeviceManager(deps);\n this.phone = new PhoneManager(deps);\n this.camera = new CameraManager(deps);\n this.led = new LedManager(deps);\n this.location = new LocationManager(deps);\n this.dashboard = new DashboardManager(deps);\n this.storage = new StorageManager(deps, {\n userId: this.config.userId ?? \"unknown-user\",\n apiKey: this.config.apiKey,\n });\n this.time = new TimeUtils(\"UTC\");\n\n this.registerCoreHandlers();\n }\n\n get packageName(): string {\n return this.config.packageName;\n }\n\n get sessionId(): string {\n return this.runtimeSessionId;\n }\n\n get userId(): string | undefined {\n return this.config.userId;\n }\n\n get isConnected(): boolean {\n return this._lifecycleManager.isConnected;\n }\n\n get isParked(): boolean {\n return this._lifecycleManager.isParked;\n }\n\n async connect(): Promise<void> {\n await this._lifecycleManager.connect();\n }\n\n async disconnect(): Promise<void> {\n this._lifecycleManager.disconnect();\n await this.destroyManagers();\n this._router.destroy();\n this._subscriptions.clear();\n }\n\n async releaseOwnership(reason: \"switching_clouds\" | \"clean_shutdown\" | \"user_logout\"): Promise<void> {\n this.sendMessage({\n type: AppToCloudMessageType.OWNERSHIP_RELEASE,\n packageName: this.config.packageName,\n sessionId: this.runtimeSessionId,\n reason,\n timestamp: new Date(),\n });\n\n await new Promise((resolve) => setTimeout(resolve, 100));\n }\n\n updateSettingsForTesting(newSettings: AppSettings): void {\n this.settingsData = newSettings;\n this.permissions.updateFromSettings(newSettings);\n this.emit(\"settings\", this.settingsData);\n }\n\n onConnected(handler: (...args: SessionEventMap[\"connected\"]) => void): () => void {\n this.lifecycle.on(\"connected\", handler);\n return () => this.lifecycle.off(\"connected\", handler);\n }\n\n onDisconnected(handler: (...args: SessionEventMap[\"disconnected\"]) => void): () => void {\n this.lifecycle.on(\"disconnected\", handler);\n return () => this.lifecycle.off(\"disconnected\", handler);\n }\n\n onError(handler: (...args: SessionEventMap[\"error\"]) => void): () => void {\n this.lifecycle.on(\"error\", handler);\n return () => this.lifecycle.off(\"error\", handler);\n }\n\n onStopped(handler: (...args: SessionEventMap[\"stopped\"]) => void): () => void {\n this.lifecycle.on(\"stopped\", handler);\n return () => this.lifecycle.off(\"stopped\", handler);\n }\n\n onSettings(handler: (...args: SessionEventMap[\"settings\"]) => void): () => void {\n this.lifecycle.on(\"settings\", handler);\n return () => this.lifecycle.off(\"settings\", handler);\n }\n\n onReconnected(handler: (...args: SessionEventMap[\"reconnected\"]) => void): () => void {\n this.lifecycle.on(\"reconnected\", handler);\n return () => this.lifecycle.off(\"reconnected\", handler);\n }\n\n sendMessage(message: unknown): void {\n this.transport.send(JSON.stringify(message));\n }\n\n sendBinary(data: ArrayBuffer | Uint8Array): void {\n this.transport.sendBinary(data);\n }\n\n getServerUrl(): string | null {\n return this.config.serverUrl ?? null;\n }\n\n private registerCoreHandlers(): void {\n this.cleanupTasks.push(\n this._router.messageHandlers.register(CloudToAppMessageType.DATA_STREAM, (message) => {\n this._router.dataStreamRouter.handle(message);\n }),\n );\n\n this.cleanupTasks.push(\n this._router.messageHandlers.register(CloudToAppMessageType.CONNECTION_ACK, (message) => {\n this.handleConnectionAck(message, false);\n }),\n );\n\n this.cleanupTasks.push(\n this._router.messageHandlers.register(CloudToAppMessageType.RECONNECT_ACK, (message) => {\n this.handleConnectionAck(message, true);\n }),\n );\n\n this.cleanupTasks.push(\n this._router.messageHandlers.register(CloudToAppMessageType.SETTINGS_UPDATE, (message) => {\n this.handleSettingsUpdate(message);\n }),\n );\n\n this.cleanupTasks.push(\n this._router.messageHandlers.register(CloudToAppMessageType.CAPABILITIES_UPDATE, (message) => {\n this.capabilities = message.capabilities ?? null;\n this.device.handleCapabilitiesUpdate(message);\n }),\n );\n\n this.cleanupTasks.push(\n this._router.messageHandlers.register(CloudToAppMessageType.DEVICE_STATE_UPDATE, (message) => {\n this.device.handleDeviceStateUpdate(message);\n }),\n );\n\n this.cleanupTasks.push(\n this._router.messageHandlers.register(CloudToAppMessageType.APP_STOPPED, (message) => {\n const reason = message.reason ?? \"unknown\";\n this.logger.info({ reason }, \"MentraSession received app_stopped\");\n\n // Tell _ConnectionManager NOT to reconnect. The cloud explicitly stopped\n // this session (user closed the app from the phone). Without this, the\n // subsequent WebSocket close triggers scheduleReconnect() because\n // explicitDisconnect is false (the SDK didn't initiate the close).\n // See: cloud/issues/088 — \"app keeps restarting after user stops it\"\n this._lifecycleManager.disconnect();\n\n this.emit(\"stopped\", reason);\n }),\n );\n\n this.cleanupTasks.push(\n this._router.messageHandlers.register(CloudToAppMessageType.CONNECTION_ERROR, (message) => {\n this.emit(\"error\", new Error(message.message ?? \"MentraSession connection error\"));\n }),\n );\n\n this.cleanupTasks.push(\n this._router.messageHandlers.register(CloudToAppMessageType.RECONNECT_REJECTED, (message) => {\n if (message.code === \"NOT_RUNNING\" || message.code === \"BOOT_TIMEOUT\") {\n this.transport.close(1000, message.message ?? \"Reconnect rejected\");\n this.emit(\"disconnected\", {\n code: 4002,\n reason: message.message ?? \"Reconnect rejected\",\n permanent: true,\n });\n return;\n }\n\n this.sendConnectionInit();\n }),\n );\n\n this.cleanupTasks.push(\n this._router.messageHandlers.register(CloudToAppMessageType.RECONNECT_DEFERRED, (message) => {\n const timeoutMs = typeof message.timeoutMs === \"number\" ? message.timeoutMs : DEFAULT_PARKED_TIMEOUT_MS;\n this._lifecycleManager.park(timeoutMs, () => {\n this.transport.close(1000, \"Parked timeout\");\n this.emit(\"disconnected\", {\n code: 4001,\n reason: \"Parked reconnect timeout exceeded\",\n permanent: true,\n });\n });\n }),\n );\n\n this.cleanupTasks.push(\n this._router.messageHandlers.register(\"augmentos_settings_update\", (message) => {\n this.applyMentraosSettings(message.settings ?? {});\n }),\n );\n }\n\n private handleTextMessage(raw: string): void {\n try {\n this._router.handleRawText(raw);\n } catch (error) {\n this.emit(\"error\", error instanceof Error ? error : new Error(String(error)));\n }\n }\n\n private handleConnectionAck(message: any, isReconnect: boolean): void {\n this.settingsData = message.settings ?? [];\n this.appConfig = message.config ?? null;\n this.capabilities = message.capabilities ?? null;\n this.runtimeSessionId = message.sessionId ?? this.runtimeSessionId;\n\n this.permissions.updateFromSettings(message.mentraosSettings ?? message.settings ?? {});\n this.applyMentraosSettings(message.mentraosSettings ?? {});\n\n if (message.capabilities) {\n this.device.handleCapabilitiesUpdate({\n type: CloudToAppMessageType.CAPABILITIES_UPDATE,\n capabilities: message.capabilities,\n modelName: message.capabilities.modelName ?? null,\n });\n }\n\n this._lifecycleManager.markConnected();\n this._subscriptions.sync();\n const wasReconnect = isReconnect || this.hasCompletedInitialConnect;\n this.hasCompletedInitialConnect = true;\n\n const transcriptionConfig = this.transcription.config;\n if (transcriptionConfig) {\n this.transcription.configure(transcriptionConfig);\n }\n\n this.emit(\"connected\", this.settingsData);\n this.emit(\"settings\", this.settingsData);\n if (wasReconnect) {\n this.emit(\"reconnected\");\n }\n }\n\n private handleSettingsUpdate(message: any): void {\n this.settingsData = message.settings ?? [];\n this.permissions.updateFromSettings(message.settings ?? {});\n this.emit(\"settings\", this.settingsData);\n }\n\n private applyMentraosSettings(settings: Record<string, any>): void {\n this.mentraosSettings = settings;\n const timezone = settings?.timezone;\n\n if (typeof timezone === \"string\" && timezone.length > 0) {\n try {\n this.time.setTimezone(timezone);\n } catch (error) {\n this.logger.warn({ timezone, error }, \"MentraSession received invalid timezone\");\n }\n }\n }\n\n private sendHandshake(): void {\n if (this.hasCompletedInitialConnect) {\n this.sendMessage({\n type: AppToCloudMessageType.RECONNECT,\n sessionId: this.runtimeSessionId,\n sdkVersion: SDK_VERSION,\n timestamp: new Date(),\n });\n return;\n }\n\n this.sendConnectionInit();\n }\n\n private sendConnectionInit(): void {\n this.sendMessage({\n type: AppToCloudMessageType.CONNECTION_INIT,\n packageName: this.config.packageName,\n apiKey: this.config.apiKey,\n sdkVersion: SDK_VERSION,\n timestamp: new Date(),\n });\n }\n\n private emit<K extends keyof SessionEventMap>(event: K, ...args: SessionEventMap[K]): void {\n this.lifecycle.emit(event, ...args);\n }\n\n private async destroyManagers(): Promise<void> {\n this._lifecycleManager.destroy();\n\n await this.storage.destroy();\n this.camera.destroy();\n this.dashboard.destroy();\n this.device.destroy();\n this.led.destroy();\n this.location.destroy();\n this.mic.stop();\n this.phone.destroy();\n this.speaker.destroy();\n this.transcription.stop();\n this.translation.stop();\n\n for (const cleanup of this.cleanupTasks.splice(0)) {\n await cleanup();\n }\n }\n}\n",
24
+ "/**\n * CameraManager — v3 SDK Camera API\n *\n * Covers:\n * - photo capture\n * - externally-triggered photo events\n * - unmanaged RTMP streaming\n * - managed stream orchestration/status\n */\n\nimport { EventEmitter } from \"events\";\nimport {\n AppToCloudMessageType,\n CloudToAppMessageType,\n type ManagedStreamStatus,\n type RestreamDestination,\n type StreamStatus,\n StreamType,\n type StreamStatusCheckResponse,\n type AudioConfig,\n type StreamConfig,\n type VideoConfig,\n} from \"../../types\";\n\nexport interface PhotoOptions {\n size?: \"small\" | \"medium\" | \"large\" | \"full\";\n compression?: \"none\" | \"medium\" | \"heavy\";\n saveToGallery?: boolean;\n sound?: boolean;\n timeout?: number;\n}\n\nexport interface PhotoData {\n url: string;\n width: number;\n height: number;\n timestamp: number;\n savedToGallery: boolean;\n}\n\n/**\n * Options for session.camera.startStream().\n *\n * Three modes:\n * - No options → managed relay (default, best for most apps)\n * - `destinations` → managed relay + fan out to external services\n * - `direct` → glasses connect straight to this URL, no relay\n */\nexport interface StreamOptions {\n /** Direct stream URL. Glasses connect to this URL directly, bypassing the cloud relay.\n * Supports srt://, rtmp://, rtmps://, and https:// (WHIP) protocols.\n * When set, the cloud relay is not used. No viewer URLs are returned.\n * Most apps should NOT use this — use the default managed relay instead. */\n direct?: string;\n\n /** Restream destinations. The cloud relay fans out to these URLs.\n * Only works with managed streaming (when `direct` is not set).\n * Each URL is an RTMP or SRT ingest endpoint (YouTube, Twitch, etc.) */\n destinations?: string[];\n\n /** Stream quality. Only applies to managed streaming. */\n quality?: \"720p\" | \"1080p\";\n\n /** Enable WebRTC playback URL. Only applies to managed streaming. Default: true. */\n enableWebRTC?: boolean;\n\n /** Video configuration (resolution, bitrate, fps) */\n video?: VideoConfig;\n\n /** Audio configuration (bitrate, sample rate) */\n audio?: AudioConfig;\n\n /** Stream transport configuration */\n stream?: StreamConfig;\n\n /** Controls stream start/stop sounds on the glasses. Default: true. */\n sound?: boolean;\n}\n\nexport interface StreamResult {\n hlsUrl: string;\n dashUrl: string;\n webrtcUrl?: string;\n previewUrl?: string;\n thumbnailUrl?: string;\n streamId: string;\n}\n\n/** @deprecated Use StreamOptions instead */\nexport interface RtmpStreamOptions {\n rtmpUrl: string;\n video?: VideoConfig;\n audio?: AudioConfig;\n stream?: StreamConfig;\n sound?: boolean;\n}\n\n/** @deprecated Use StreamOptions with destinations instead */\nexport interface ManagedStreamOptions {\n quality?: \"720p\" | \"1080p\";\n enableWebRTC?: boolean;\n video?: VideoConfig;\n audio?: AudioConfig;\n stream?: StreamConfig;\n restreamDestinations?: RestreamDestination[];\n sound?: boolean;\n}\n\n/** @deprecated Use StreamResult instead */\nexport type ManagedStreamResult = StreamResult;\n\nexport interface ExistingStreamInfo {\n hasActiveStream: boolean;\n streamInfo?: {\n type: \"managed\" | \"unmanaged\";\n streamId: string;\n status: string;\n createdAt: Date;\n hlsUrl?: string;\n dashUrl?: string;\n webrtcUrl?: string;\n previewUrl?: string;\n thumbnailUrl?: string;\n activeViewers?: number;\n rtmpUrl?: string;\n requestingAppId?: string;\n };\n}\n\nexport type StreamStatusHandler = (status: StreamStatus) => void;\n\nexport interface CameraManagerDeps {\n router: {\n on(key: string, handler: (streamType: string, data: any, message: any) => void): () => void;\n };\n messageHandlers: {\n register(type: string, handler: (msg: any) => void): () => void;\n };\n addSubscription: (stream: string) => void;\n removeSubscription: (stream: string) => void;\n sendMessage: (message: any) => void;\n logger: {\n debug(...args: any[]): void;\n info(...args: any[]): void;\n warn(...args: any[]): void;\n error(...args: any[]): void;\n };\n getPackageName: () => string;\n getSessionId: () => string;\n}\n\ninterface PendingPhotoRequest {\n requestId: string;\n resolve: (data: PhotoData) => void;\n reject: (error: Error) => void;\n timer: ReturnType<typeof setTimeout>;\n}\n\ninterface PendingStreamCheck {\n resolve: (response: ExistingStreamInfo) => void;\n timeoutId: ReturnType<typeof setTimeout>;\n}\n\nconst DEFAULT_TIMEOUT_MS = 30_000;\nconst STREAM_CHECK_TIMEOUT_MS = 5_000;\nconst MANAGED_STREAM_TIMEOUT_MS = 30_000;\n\nfunction generateRequestId(prefix: string): string {\n if (typeof crypto !== \"undefined\" && typeof crypto.randomUUID === \"function\") {\n return `${prefix}_${crypto.randomUUID()}`;\n }\n\n return `${prefix}_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;\n}\n\nexport class CameraManager {\n private readonly deps: CameraManagerDeps;\n private readonly events = new EventEmitter();\n private readonly handlerCleanups: Array<() => void> = [];\n\n private pendingRequests = new Map<string, PendingPhotoRequest>();\n private pendingStreamChecks = new Map<string, PendingStreamCheck>();\n private pendingManagedStreamRequest:\n | {\n resolve: (value: ManagedStreamResult) => void;\n reject: (reason?: any) => void;\n timeoutId: ReturnType<typeof setTimeout>;\n }\n | undefined;\n\n private _hasPermission = true;\n private isStreaming = false;\n private currentStreamUrl?: string;\n private currentStreamState?: StreamStatus;\n private isManagedStreaming = false;\n private currentManagedStreamId?: string;\n private currentManagedStreamUrls?: ManagedStreamResult;\n private managedStreamStatus?: ManagedStreamStatus;\n\n constructor(deps: CameraManagerDeps) {\n this.deps = deps;\n\n this.handlerCleanups.push(\n this.deps.messageHandlers.register(CloudToAppMessageType.PHOTO_RESPONSE, (msg: any) =>\n this.handlePhotoResponse(msg),\n ),\n // Register for both old (\"rtmp_stream_status\") and new (\"stream_status\") message types.\n // The cloud currently sends \"rtmp_stream_status\" but the enum maps to \"stream_status\".\n this.deps.messageHandlers.register(CloudToAppMessageType.STREAM_STATUS, (msg: any) =>\n this.handleStreamStatus(msg),\n ),\n this.deps.messageHandlers.register(\"rtmp_stream_status\" as any, (msg: any) =>\n this.handleStreamStatus(msg),\n ),\n this.deps.messageHandlers.register(CloudToAppMessageType.MANAGED_STREAM_STATUS, (msg: any) =>\n this.handleManagedStreamStatus(msg),\n ),\n this.deps.messageHandlers.register(CloudToAppMessageType.STREAM_STATUS_CHECK_RESPONSE, (msg: any) =>\n this.handleStreamCheckResponse(msg),\n ),\n );\n }\n\n takePhoto(opts?: PhotoOptions): Promise<PhotoData> {\n return new Promise<PhotoData>((resolve, reject) => {\n const requestId = generateRequestId(\"photo_req\");\n const timeoutMs = opts?.timeout ?? DEFAULT_TIMEOUT_MS;\n\n const timer = setTimeout(() => {\n this.pendingRequests.delete(requestId);\n reject(new Error(`Photo request timed out after ${timeoutMs}ms (requestId: ${requestId})`));\n }, timeoutMs);\n\n this.pendingRequests.set(requestId, { requestId, resolve, reject, timer });\n\n const message = {\n type: AppToCloudMessageType.PHOTO_REQUEST,\n packageName: this.deps.getPackageName(),\n sessionId: this.deps.getSessionId(),\n requestId,\n timestamp: new Date(),\n saveToGallery: opts?.saveToGallery ?? false,\n size: opts?.size ?? \"medium\",\n compress: opts?.compression ?? \"none\",\n sound: opts?.sound,\n };\n\n try {\n this.deps.sendMessage(message);\n this.deps.logger.info(\n { requestId, size: message.size, compress: message.compress, saveToGallery: message.saveToGallery },\n \"📸 Photo request sent\",\n );\n } catch (err) {\n clearTimeout(timer);\n this.pendingRequests.delete(requestId);\n reject(err instanceof Error ? err : new Error(String(err)));\n }\n });\n }\n\n onPhotoTaken(handler: (photo: PhotoData) => void): () => void {\n const streamKey = StreamType.PHOTO_TAKEN;\n this.deps.addSubscription(streamKey);\n\n const routerCleanup = this.deps.router.on(streamKey, (_streamType, data) => {\n try {\n handler(normalisePhotoData(data));\n } catch (err) {\n this.deps.logger.error(\"[CameraManager] Error in onPhotoTaken handler:\", err);\n }\n });\n\n return () => {\n routerCleanup();\n this.deps.removeSubscription(streamKey);\n };\n }\n\n // ── Unified streaming API ────────────────────────────────────────────────\n\n /**\n * Start a video stream from the glasses.\n *\n * Three modes:\n * - `startStream()` — managed relay (default). Cloud handles quality, reconnection, viewer URLs.\n * - `startStream({ destinations: [...] })` — managed relay + fan out to YouTube/Twitch/etc.\n * - `startStream({ direct: \"srt://...\" })` — glasses connect straight to your URL, no relay.\n *\n * @example\n * ```ts\n * // Default — managed relay\n * const stream = await session.camera.startStream();\n * console.log(stream.hlsUrl, stream.webrtcUrl);\n *\n * // Managed relay + restream to YouTube\n * const stream = await session.camera.startStream({\n * destinations: [\"rtmp://youtube.com/live/your-key\"],\n * });\n *\n * // Direct — glasses → your server, no relay\n * await session.camera.startStream({ direct: \"srt://192.168.1.100:4201\" });\n * ```\n */\n async startStream(options?: StreamOptions): Promise<StreamResult | void> {\n const opts = options ?? {};\n\n if (opts.direct) {\n return this._startDirectStream(opts);\n }\n return this._startManagedStream(opts);\n }\n\n /**\n * Stop any active stream (managed or direct).\n */\n async stopStream(): Promise<void> {\n if (this.isStreaming) {\n // Stop direct stream\n this.deps.sendMessage({\n type: AppToCloudMessageType.STREAM_STOP,\n packageName: this.deps.getPackageName(),\n sessionId: this.deps.getSessionId(),\n streamId: this.currentStreamState?.streamId,\n timestamp: new Date(),\n });\n }\n\n if (this.isManagedStreaming) {\n // Stop managed stream\n this.deps.sendMessage({\n type: AppToCloudMessageType.MANAGED_STREAM_STOP,\n packageName: this.deps.getPackageName(),\n sessionId: this.deps.getSessionId(),\n timestamp: new Date(),\n });\n\n // Issue 091: Clear local state immediately. Don't wait for the cloud\n // to respond with managed_stream_status: \"stopped\" — the cloud may\n // not respond if the stream was already cleaned up (keep-alive timeout,\n // glasses battery death, etc.). Without this, isManagedStreaming stays\n // true and the next startStream() throws \"Already streaming.\"\n this.isManagedStreaming = false;\n this.currentManagedStreamId = undefined;\n this.currentManagedStreamUrls = undefined;\n }\n }\n\n /**\n * Subscribe to stream status updates (works for both managed and direct).\n */\n onStreamStatus(handler: StreamStatusHandler): () => void {\n // Subscribe to BOTH direct and managed stream status events.\n // Issue 091: the \"unified\" onStreamStatus was only wired to direct\n // stream events. Managed stream events (stopped, error, active from\n // Cloudflare keep-alive timeout, battery death, etc.) went to a\n // separate \"managed_stream_status\" event that this handler never heard.\n this.deps.addSubscription(StreamType.STREAM_STATUS);\n this.deps.addSubscription(StreamType.MANAGED_STREAM_STATUS);\n this.events.on(\"stream_status\", handler);\n this.events.on(\"managed_stream_status\", handler);\n\n return () => {\n this.events.off(\"stream_status\", handler);\n this.events.off(\"managed_stream_status\", handler);\n this.deps.removeSubscription(StreamType.STREAM_STATUS);\n this.deps.removeSubscription(StreamType.MANAGED_STREAM_STATUS);\n };\n }\n\n isCurrentlyStreaming(): boolean {\n return this.isStreaming || this.isManagedStreaming;\n }\n\n getCurrentStreamUrl(): string | undefined {\n return this.currentStreamUrl;\n }\n\n getStreamStatus(): StreamStatus | undefined {\n return this.currentStreamState;\n }\n\n getStreamUrls(): StreamResult | undefined {\n return this.currentManagedStreamUrls;\n }\n\n // ── Direct streaming (glasses → URL, no relay) ───────────────────────────\n\n private async _startDirectStream(opts: StreamOptions): Promise<void> {\n const url = opts.direct!;\n\n if (!url.startsWith(\"rtmp://\") && !url.startsWith(\"rtmps://\") && !url.startsWith(\"srt://\") && !url.startsWith(\"https://\") && !url.startsWith(\"http://\")) {\n throw new Error(\"Invalid stream URL: must start with rtmp://, rtmps://, srt://, https://, or http://\");\n }\n\n // Only check streams WE started, not orphaned streams from a previous session.\n // isStreaming is only set when _startDirectStream sends a STREAM_REQUEST.\n // It's NOT set by incoming status events.\n if (this.isStreaming || this.isManagedStreaming) {\n throw new Error(\"Already streaming. Stop the current stream before starting a new one.\");\n }\n\n this.currentStreamUrl = url;\n this.deps.sendMessage({\n type: AppToCloudMessageType.STREAM_REQUEST,\n packageName: this.deps.getPackageName(),\n sessionId: this.deps.getSessionId(),\n streamUrl: url,\n video: opts.video,\n audio: opts.audio,\n stream: opts.stream,\n sound: opts.sound,\n timestamp: new Date(),\n });\n this.isStreaming = true;\n }\n\n // ── Managed streaming (glasses → cloud relay → viewers/destinations) ─────\n\n private async _startManagedStream(opts: StreamOptions): Promise<StreamResult> {\n // Only check streams WE started, not orphaned streams from a previous session.\n if (this.isStreaming || this.isManagedStreaming) {\n throw new Error(\"Already streaming. Stop the current stream before starting a new one.\");\n }\n\n // Convert destinations to restreamDestinations format\n const restreamDestinations: RestreamDestination[] | undefined = opts.destinations?.map((url) => ({ url }));\n\n this.deps.sendMessage({\n type: AppToCloudMessageType.MANAGED_STREAM_REQUEST,\n packageName: this.deps.getPackageName(),\n sessionId: this.deps.getSessionId(),\n quality: opts.quality,\n enableWebRTC: opts.enableWebRTC ?? true,\n video: opts.video,\n audio: opts.audio,\n stream: opts.stream,\n restreamDestinations,\n sound: opts.sound,\n timestamp: new Date(),\n });\n this.isManagedStreaming = true;\n\n return new Promise<StreamResult>((resolve, reject) => {\n const timeoutId = setTimeout(() => {\n if (this.pendingManagedStreamRequest?.timeoutId === timeoutId) {\n this.pendingManagedStreamRequest = undefined;\n this.isManagedStreaming = false;\n reject(new Error(\"Managed stream request timeout\"));\n }\n }, MANAGED_STREAM_TIMEOUT_MS);\n\n this.pendingManagedStreamRequest = { resolve, reject, timeoutId };\n });\n }\n\n // ── Deprecated methods (backward compat) ─────────────────────────────────\n\n /** @deprecated Use startStream({ direct: url }) instead */\n async startDirectStream(options: RtmpStreamOptions): Promise<void> {\n return this._startDirectStream({ direct: options.rtmpUrl, video: options.video, audio: options.audio, stream: options.stream, sound: options.sound });\n }\n\n /** @deprecated Use startStream() or startStream({ destinations: [...] }) instead */\n async startManagedStream(options: ManagedStreamOptions = {}): Promise<StreamResult> {\n return this._startManagedStream({\n quality: options.quality,\n enableWebRTC: options.enableWebRTC,\n video: options.video,\n audio: options.audio,\n stream: options.stream,\n destinations: options.restreamDestinations?.map((d) => d.url),\n sound: options.sound,\n });\n }\n\n /** @deprecated Use stopStream() instead */\n async stopManagedStream(): Promise<void> {\n return this.stopStream();\n }\n\n /** @deprecated Use onStreamStatus() instead */\n onManagedStreamStatus(handler: (status: ManagedStreamStatus) => void): () => void {\n this.deps.addSubscription(StreamType.MANAGED_STREAM_STATUS);\n this.events.on(\"managed_stream_status\", handler);\n\n return () => {\n this.events.off(\"managed_stream_status\", handler);\n this.deps.removeSubscription(StreamType.MANAGED_STREAM_STATUS);\n };\n }\n\n /** @deprecated Use isCurrentlyStreaming() instead */\n isManagedStreamActive(): boolean {\n return this.isManagedStreaming;\n }\n\n /** @deprecated Use getStreamUrls() instead */\n getManagedStreamUrls(): StreamResult | undefined {\n return this.currentManagedStreamUrls;\n }\n\n getManagedStreamStatus(): ManagedStreamStatus | undefined {\n return this.managedStreamStatus;\n }\n\n async checkExistingStream(): Promise<ExistingStreamInfo> {\n return new Promise<ExistingStreamInfo>((resolve) => {\n const requestId = generateRequestId(\"stream_check\");\n const timeoutId = setTimeout(() => {\n this.pendingStreamChecks.delete(requestId);\n resolve({ hasActiveStream: false });\n }, STREAM_CHECK_TIMEOUT_MS);\n\n this.pendingStreamChecks.set(requestId, { resolve, timeoutId });\n\n this.deps.sendMessage({\n type: AppToCloudMessageType.STREAM_STATUS_CHECK,\n packageName: this.deps.getPackageName(),\n sessionId: this.deps.getSessionId(),\n requestId,\n timestamp: new Date(),\n });\n });\n }\n\n get hasPermission(): boolean {\n return this._hasPermission;\n }\n\n handlePhotoResponse(message: any): void {\n const requestId: string | undefined = message?.requestId;\n if (!requestId) {\n this.deps.logger.warn(\"[CameraManager] Received PHOTO_RESPONSE without requestId:\", message);\n return;\n }\n\n const pending = this.pendingRequests.get(requestId);\n if (!pending) {\n this.deps.logger.debug(`[CameraManager] No pending request for requestId=\"${requestId}\" — ignoring.`);\n return;\n }\n\n clearTimeout(pending.timer);\n this.pendingRequests.delete(requestId);\n\n if (message.error?.code === \"permission_denied\") {\n this._hasPermission = false;\n }\n\n if (message.success === false) {\n const errorMsg = message.error?.message ?? message.error?.code ?? \"Photo capture failed\";\n pending.reject(new Error(errorMsg));\n return;\n }\n\n pending.resolve({\n url: message.photoUrl ?? \"\",\n width: message.width ?? 0,\n height: message.height ?? 0,\n timestamp: message.timestamp ? new Date(message.timestamp).getTime() : Date.now(),\n savedToGallery: message.savedToGallery ?? false,\n });\n }\n\n private handleStreamStatus(message: StreamStatus): void {\n this.currentStreamState = {\n ...message,\n timestamp: message.timestamp ? new Date(message.timestamp) : new Date(),\n };\n\n // Only update isStreaming for streams WE initiated (isStreaming is set to true\n // in _startDirectStream). Don't let orphaned stream status events from a\n // previous session set isStreaming — that blocks new streams from starting.\n if (this.isStreaming) {\n if (message.status === \"stopped\" || message.status === \"error\" || message.status === \"timeout\") {\n this.isStreaming = false;\n this.currentStreamUrl = undefined;\n }\n }\n\n this.events.emit(\"stream_status\", this.currentStreamState);\n }\n\n private handleManagedStreamStatus(status: ManagedStreamStatus): void {\n this.managedStreamStatus = status;\n\n if (status.status === \"initializing\" && status.streamId) {\n this.isManagedStreaming = true;\n this.currentManagedStreamId = status.streamId;\n }\n\n if (status.status === \"active\") {\n this.isManagedStreaming = true;\n this.currentManagedStreamId = status.streamId;\n\n if (status.hlsUrl && status.dashUrl) {\n const result: ManagedStreamResult = {\n hlsUrl: status.hlsUrl,\n dashUrl: status.dashUrl,\n webrtcUrl: status.webrtcUrl,\n previewUrl: status.previewUrl,\n thumbnailUrl: status.thumbnailUrl,\n streamId: status.streamId ?? \"\",\n };\n\n this.currentManagedStreamUrls = result;\n\n if (this.pendingManagedStreamRequest) {\n clearTimeout(this.pendingManagedStreamRequest.timeoutId);\n this.pendingManagedStreamRequest.resolve(result);\n this.pendingManagedStreamRequest = undefined;\n }\n }\n }\n\n if (status.status === \"error\" || status.status === \"stopped\") {\n if (this.pendingManagedStreamRequest) {\n clearTimeout(this.pendingManagedStreamRequest.timeoutId);\n this.pendingManagedStreamRequest.reject(new Error(status.message || \"Managed stream failed\"));\n this.pendingManagedStreamRequest = undefined;\n }\n\n this.isManagedStreaming = false;\n this.currentManagedStreamId = undefined;\n this.currentManagedStreamUrls = undefined;\n }\n\n this.events.emit(\"managed_stream_status\", status);\n }\n\n private handleStreamCheckResponse(response: StreamStatusCheckResponse): void {\n // Match by requestId from the response — don't blindly pop the first entry.\n // This prevents concurrent checkExistingStream() calls from resolving the wrong promise.\n const requestId = (response as any).requestId;\n const pending = requestId ? this.pendingStreamChecks.get(requestId) : undefined;\n\n // Fallback: if the cloud doesn't include requestId, pop the first entry (v2 behavior)\n if (!pending) {\n const firstEntry = this.pendingStreamChecks.entries().next();\n if (firstEntry.done || !firstEntry.value) {\n return;\n }\n const [fallbackId, fallbackPending] = firstEntry.value;\n clearTimeout(fallbackPending.timeoutId);\n this.pendingStreamChecks.delete(fallbackId);\n fallbackPending.resolve({\n hasActiveStream: response.hasActiveStream,\n streamInfo: response.streamInfo\n ? {\n ...response.streamInfo,\n createdAt: new Date(response.streamInfo.createdAt),\n }\n : undefined,\n });\n return;\n }\n\n clearTimeout(pending.timeoutId);\n this.pendingStreamChecks.delete(requestId!);\n pending.resolve({\n hasActiveStream: response.hasActiveStream,\n streamInfo: response.streamInfo\n ? {\n ...response.streamInfo,\n createdAt: new Date(response.streamInfo.createdAt),\n }\n : undefined,\n });\n }\n\n destroy(): void {\n for (const [requestId, pending] of this.pendingRequests) {\n clearTimeout(pending.timer);\n pending.reject(new Error(`CameraManager destroyed — session disconnected (${requestId})`));\n }\n this.pendingRequests.clear();\n\n for (const [requestId, pending] of this.pendingStreamChecks) {\n clearTimeout(pending.timeoutId);\n pending.resolve({ hasActiveStream: false });\n this.pendingStreamChecks.delete(requestId);\n }\n\n if (this.pendingManagedStreamRequest) {\n clearTimeout(this.pendingManagedStreamRequest.timeoutId);\n this.pendingManagedStreamRequest.reject(new Error(\"CameraManager destroyed — session disconnected\"));\n this.pendingManagedStreamRequest = undefined;\n }\n\n for (const cleanup of this.handlerCleanups) {\n cleanup();\n }\n this.handlerCleanups.length = 0;\n\n this.events.removeAllListeners();\n this.currentStreamState = undefined;\n this.currentStreamUrl = undefined;\n this.isStreaming = false;\n this.managedStreamStatus = undefined;\n this.currentManagedStreamId = undefined;\n this.currentManagedStreamUrls = undefined;\n this.isManagedStreaming = false;\n }\n}\n\nfunction normalisePhotoData(raw: any): PhotoData {\n return {\n url: raw.photoUrl ?? raw.url ?? \"\",\n width: raw.width ?? 0,\n height: raw.height ?? 0,\n timestamp: raw.timestamp ? new Date(raw.timestamp).getTime() : Date.now(),\n savedToGallery: raw.savedToGallery ?? false,\n };\n}\n",
25
+ "/**\n * 📊 DashboardManager — v3 SDK Dashboard Content API\n *\n * Thin wrapper that sends dashboard content updates to the cloud.\n * Provides a simplified two-method API: {@link showText} and {@link clear}.\n *\n * Wire format is identical to v2 DashboardContentManager:\n * ```json\n * {\n * \"type\": \"dashboard_content_update\",\n * \"packageName\": \"<packageName>\",\n * \"sessionId\": \"<sessionId>-<packageName>\",\n * \"content\": \"<text>\",\n * \"modes\": [\"main\"],\n * \"timestamp\": \"<ISO date>\"\n * }\n * ```\n *\n * The `sessionId` field in dashboard messages uses the composite format\n * `\"<sessionId>-<packageName>\"` to match the v2 wire format exactly.\n *\n * @module\n */\n\nimport { AppToCloudMessageType } from \"../../types/message-types\";\nimport { DashboardMode, DashboardContentUpdate } from \"../../types/dashboard\";\n\n// ─── Internal Types ─────────────────────────────────────────────────────────\n\n/**\n * Dependencies injected by MentraSession.\n *\n * Structural type — no concrete imports so the manager stays unit-testable\n * with plain stubs.\n */\nexport interface DashboardManagerDeps {\n /** DataStreamRouter — register for DATA_STREAM messages by streamType key. */\n router: {\n on(key: string, handler: (streamType: string, data: any, message: any) => void): () => void;\n };\n /** MessageHandlerRegistry — register for top-level message types. */\n messageHandlers: {\n register(type: string, handler: (msg: any) => void): () => void;\n };\n /** Add a subscription string (triggers SUBSCRIPTION_UPDATE to cloud). */\n addSubscription: (stream: string) => void;\n /** Remove a subscription string. */\n removeSubscription: (stream: string) => void;\n /** Send an arbitrary JSON message over the WebSocket. */\n sendMessage: (message: any) => void;\n /** Structured logger. */\n logger: {\n debug(...args: any[]): void;\n info(...args: any[]): void;\n warn(...args: any[]): void;\n error(...args: any[]): void;\n };\n /** Package name for outgoing messages. */\n getPackageName: () => string;\n /** Current session ID. */\n getSessionId: () => string;\n}\n\n// ─── Manager ────────────────────────────────────────────────────────────────\n\n/**\n * Controls the dashboard content displayed for this app on the user's glasses.\n *\n * Dashboard content is a text overlay shown in the main dashboard view.\n * The {@link showText} method sends content targeting the `MAIN` dashboard\n * mode by default. Use {@link clear} to remove any displayed content.\n *\n * All methods are fire-and-forget — the messages are sent immediately and\n * no response is awaited.\n *\n * @example\n * ```ts\n * const session = await mentra.connect();\n *\n * // Show a single line of text\n * session.dashboard.showText(\"Meeting in 5 minutes\");\n *\n * // Show multiple lines\n * session.dashboard.showText([\"Line 1\", \"Line 2\", \"Line 3\"]);\n *\n * // Clear the dashboard\n * session.dashboard.clear();\n * ```\n */\nexport class DashboardManager {\n private readonly deps: DashboardManagerDeps;\n\n constructor(deps: DashboardManagerDeps) {\n this.deps = deps;\n }\n\n // ─── Public API ─────────────────────────────────────────────────────────\n\n /**\n * Show text content on the dashboard.\n *\n * Sends a `dashboard_content_update` message targeting the `MAIN`\n * dashboard mode. Accepts either a single string or an array of\n * pre-wrapped lines — when an array is provided the lines are joined\n * with newlines before sending.\n *\n * @param text - A string or array of strings to display on the dashboard.\n *\n * @example\n * ```ts\n * // Single string\n * session.dashboard.showText(\"Status: Connected\");\n *\n * // Pre-wrapped lines\n * session.dashboard.showText([\n * \"Temperature: 72°F\",\n * \"Humidity: 45%\",\n * \"Wind: 5 mph\",\n * ]);\n * ```\n */\n showText(text: string | string[]): void {\n const content = Array.isArray(text) ? text.join(\"\\n\") : text;\n\n const message: DashboardContentUpdate = {\n type: AppToCloudMessageType.DASHBOARD_CONTENT_UPDATE,\n packageName: this.deps.getPackageName(),\n sessionId: `${this.deps.getSessionId()}-${this.deps.getPackageName()}`,\n content,\n modes: [DashboardMode.MAIN],\n timestamp: new Date(),\n };\n\n this.deps.sendMessage(message);\n\n this.deps.logger.debug({ contentLength: content.length }, \"📊 Dashboard content update sent\");\n }\n\n /**\n * Clear the dashboard content.\n *\n * Sends an empty `dashboard_content_update` message targeting the\n * `MAIN` dashboard mode, which removes any currently displayed content\n * for this app.\n *\n * @example\n * ```ts\n * session.dashboard.clear();\n * ```\n */\n clear(): void {\n const message: DashboardContentUpdate = {\n type: AppToCloudMessageType.DASHBOARD_CONTENT_UPDATE,\n packageName: this.deps.getPackageName(),\n sessionId: `${this.deps.getSessionId()}-${this.deps.getPackageName()}`,\n content: \"\",\n modes: [DashboardMode.MAIN],\n timestamp: new Date(),\n };\n\n this.deps.sendMessage(message);\n\n this.deps.logger.debug(\"📊 Dashboard content cleared\");\n }\n\n // ─── Cleanup ────────────────────────────────────────────────────────────\n\n /**\n * Clean up resources.\n *\n * Called by MentraSession during disconnect/cleanup. Dashboard commands\n * are fire-and-forget so there is no pending state to drain.\n *\n * @internal\n */\n destroy(): void {\n this.deps.logger.debug(\"[DashboardManager] Destroyed.\");\n }\n}\n",
26
+ "/**\n * Observable<T> - A reactive value wrapper that notifies listeners of changes\n *\n * Provides synchronous value access and reactive subscriptions via onChange().\n * Supports implicit coercion for use in conditionals and comparisons.\n *\n * @example\n * ```typescript\n * const wifiStatus = new Observable(false);\n *\n * // Synchronous read\n * console.log(wifiStatus.value); // false\n *\n * // Implicit coercion\n * if (wifiStatus) { ... } // Works via valueOf()\n *\n * // Reactive subscription\n * const cleanup = wifiStatus.onChange((connected) => {\n * console.log(\"WiFi:\", connected);\n * });\n *\n * // Update (triggers callbacks)\n * wifiStatus.setValue(true);\n *\n * // Cleanup\n * cleanup();\n * ```\n */\nexport class Observable<T> {\n private _value: T;\n private _listeners: Set<(value: T) => void> = new Set();\n private _initialized: boolean = false; // Track if value has been set from WebSocket\n\n constructor(initialValue: T) {\n this._value = initialValue;\n }\n\n /**\n * Get the current value synchronously\n */\n get value(): T {\n return this._value;\n }\n\n /**\n * Implicit coercion to primitive value (for conditionals/comparisons)\n */\n valueOf(): T {\n return this._value;\n }\n\n /**\n * String representation\n */\n toString(): string {\n return String(this._value);\n }\n\n /**\n * Symbol.toPrimitive for implicit type coercion\n * Allows usage in conditionals: if (observable) { ... }\n */\n [Symbol.toPrimitive](hint: string): T | string {\n if (hint === 'string') {\n return String(this._value);\n }\n return this._value;\n }\n\n /**\n * Subscribe to value changes\n *\n * The callback is called immediately with the current value ONLY if\n * the Observable has been initialized (setValue() called at least once).\n * This prevents callbacks from firing with default/uninitialized values.\n *\n * @param callback - Function to call when value changes\n * @returns Cleanup function to unsubscribe\n *\n * @example\n * ```typescript\n * const cleanup = observable.onChange((value) => {\n * console.log(\"New value:\", value);\n * });\n *\n * // Later: unsubscribe\n * cleanup();\n * ```\n */\n onChange(callback: (value: T) => void): () => void {\n this._listeners.add(callback);\n // Call immediately with current value ONLY if initialized\n if (this._initialized) {\n callback(this._value);\n }\n // Return cleanup function\n return () => this._listeners.delete(callback);\n }\n\n /**\n * Update the value and notify listeners\n *\n * Triggers callbacks if:\n * 1. This is the first setValue() call (initialization from WebSocket), OR\n * 2. The new value is different from current value\n *\n * Uses strict equality (===) for comparison.\n *\n * @param value - New value to set\n *\n * @internal This method is called by DeviceState when receiving WebSocket updates\n */\n setValue(value: T): void {\n const isFirstInit = !this._initialized;\n\n // Mark as initialized (first setValue call from WebSocket)\n if (isFirstInit) {\n this._initialized = true;\n }\n\n // Notify listeners if this is initialization OR value changed\n if (isFirstInit || this._value !== value) {\n this._value = value;\n // Notify all listeners\n this._listeners.forEach((cb) => {\n try {\n cb(value);\n } catch (error) {\n console.error('Error in Observable onChange callback:', error);\n }\n });\n }\n }\n\n /**\n * Get the number of active listeners\n * @internal Used for debugging/testing\n */\n get listenerCount(): number {\n return this._listeners.size;\n }\n}\n",
27
+ "/**\n * DeviceManager — Consolidated Device State, Hardware Events & Capabilities\n *\n * Owns all device-related concerns for a MentraSession:\n *\n * - **Reactive state** — Observable properties for connection, battery, WiFi,\n * hotspot, and case status (mirrors the legacy DeviceState pattern).\n * - **Hardware events** — Button presses, head position, touch gestures,\n * battery updates, and VPS coordinates, all routed from the DataStreamRouter.\n * - **Capabilities** — Device capability profile received at connection time\n * and updated mid-session when the glasses model changes.\n * - **Actions** — Outbound commands like `requestWifiSetup`.\n *\n * All handler registrations return a cleanup function. Subscriptions are\n * managed automatically — `addSubscription` is called when the first handler\n * for a stream is registered, and `removeSubscription` when the last is removed.\n *\n * @example\n * ```ts\n * // Reactive state\n * device.state.batteryLevel.onChange((level) => {\n * console.log(\"Battery:\", level, \"%\");\n * });\n *\n * // Hardware events\n * const stop = device.onButtonPress((e) => {\n * console.log(e.buttonId, e.pressType);\n * });\n *\n * // Filtered touch events\n * device.onTouchEvent(\"double_tap\", (e) => {\n * console.log(\"Double tap!\", e);\n * });\n *\n * // Capabilities\n * device.onCapabilitiesChange((caps) => {\n * console.log(\"Device supports camera:\", !!caps?.camera);\n * });\n *\n * // Actions\n * device.requestWifiSetup(\"App needs internet for sync\");\n *\n * // Cleanup\n * stop();\n * ```\n *\n * @module\n */\n\nimport { Observable } from \"../../utils/Observable\";\nimport { StreamType } from \"../../types/streams\";\nimport type { PermissionsManager } from \"./PermissionsManager\";\n\n// ─── Event Types ────────────────────────────────────────────────────────────\n\n/**\n * Button press event from the glasses hardware.\n */\nexport interface ButtonPressEvent {\n /** Identifier of the button that was pressed. */\n buttonId: string;\n /** Whether the press was short or long. */\n pressType: \"short\" | \"long\";\n}\n\n/**\n * Head position event from the IMU.\n */\nexport interface HeadPositionEvent {\n /** Current head position. */\n position: \"up\" | \"down\";\n}\n\n/**\n * Normalised touch/gesture event from the glasses touchpad.\n *\n * Raw wire fields (`gesture_name`, `device_model`) are normalised to\n * `gesture` and `model` for a cleaner developer experience.\n */\nexport interface TouchEventData {\n /** Normalised gesture name (e.g. \"double_tap\", \"forward_swipe\"). */\n gesture: string;\n /** Normalised device model name. */\n model: string;\n /** Timestamp of the gesture. */\n timestamp: Date | string;\n /** The original raw data, preserved for advanced use cases. */\n [key: string]: any;\n}\n\n/**\n * Glasses battery update event.\n */\nexport interface BatteryUpdateEvent {\n /** Battery level 0–100. */\n level: number;\n /** Whether the glasses are currently charging. */\n charging: boolean;\n /** Estimated minutes remaining (if available). */\n timeRemaining?: number;\n}\n\n// ─── Dependency Types ───────────────────────────────────────────────────────\n\n/**\n * Dependencies injected by MentraSession into the DeviceManager.\n */\nexport interface DeviceManagerDeps {\n /** DataStreamRouter — register for stream-type events. */\n router: {\n on(key: string, handler: (streamType: string, data: any, message: any) => void): () => void;\n };\n /** MessageHandlerRegistry — register for top-level message types. */\n messageHandlers: {\n register(type: string, handler: (msg: any) => void): () => void;\n };\n /** Subscribe to a data stream (sent to cloud). */\n addSubscription: (stream: string) => void;\n /** Unsubscribe from a data stream. */\n removeSubscription: (stream: string) => void;\n /** Send an arbitrary message to the cloud. */\n sendMessage: (message: any) => void;\n /** Session-scoped logger. */\n logger: {\n debug(...args: any[]): void;\n info(...args: any[]): void;\n warn(...args: any[]): void;\n error(...args: any[]): void;\n };\n /** Returns the current app's package name. */\n getPackageName: () => string;\n /** Returns the active session ID. */\n getSessionId: () => string;\n /** PermissionsManager for gating protected streams. */\n permissions: PermissionsManager;\n}\n\n// ─── Reactive Device State ──────────────────────────────────────────────────\n\n/**\n * Read-only reactive device state container.\n *\n * Every property is an {@link Observable} — call `.value` for synchronous\n * reads or `.onChange(cb)` for reactive subscriptions.\n */\nexport interface DeviceStateShape {\n readonly connected: Observable<boolean>;\n readonly modelName: Observable<string | null>;\n readonly batteryLevel: Observable<number | null>;\n readonly charging: Observable<boolean | null>;\n readonly caseBatteryLevel: Observable<number | null>;\n readonly caseCharging: Observable<boolean | null>;\n readonly caseOpen: Observable<boolean | null>;\n readonly caseRemoved: Observable<boolean | null>;\n readonly wifiConnected: Observable<boolean>;\n readonly wifiSsid: Observable<string | null>;\n readonly wifiLocalIp: Observable<string | null>;\n readonly hotspotEnabled: Observable<boolean | null>;\n readonly hotspotSsid: Observable<string | null>;\n}\n\n// ─── Internal Helpers ───────────────────────────────────────────────────────\n\n/**\n * Normalise raw touch event data from the wire format.\n *\n * - `gesture_name` → `gesture`\n * - `device_model` → `model`\n */\nfunction normaliseTouchEvent(raw: any): TouchEventData {\n return {\n ...raw,\n gesture: raw.gesture_name ?? raw.gesture ?? \"unknown\",\n model: raw.device_model ?? raw.model ?? \"unknown\",\n timestamp: raw.timestamp ?? new Date().toISOString(),\n };\n}\n\n// ─── DeviceManager ──────────────────────────────────────────────────────────\n\n/**\n * Manages all device-related state, hardware events, capabilities, and actions.\n *\n * Created by MentraSession and exposed as `session.device`.\n */\nexport class DeviceManager {\n // ─── Reactive State ───────────────────────────────────────────────────\n\n /** Reactive device state observables. */\n readonly state: DeviceStateShape;\n\n // ─── Capabilities ─────────────────────────────────────────────────────\n\n /** Current device capabilities (set from CONNECTION_ACK). */\n capabilities: any = null;\n\n // ─── Private ──────────────────────────────────────────────────────────\n\n private deps: DeviceManagerDeps;\n private permissions: PermissionsManager;\n\n /** Internal mutable references to observables (state exposes them read-only). */\n private readonly _connected: Observable<boolean>;\n private readonly _modelName: Observable<string | null>;\n private readonly _batteryLevel: Observable<number | null>;\n private readonly _charging: Observable<boolean | null>;\n private readonly _caseBatteryLevel: Observable<number | null>;\n private readonly _caseCharging: Observable<boolean | null>;\n private readonly _caseOpen: Observable<boolean | null>;\n private readonly _caseRemoved: Observable<boolean | null>;\n private readonly _wifiConnected: Observable<boolean>;\n private readonly _wifiSsid: Observable<string | null>;\n private readonly _wifiLocalIp: Observable<string | null>;\n private readonly _hotspotEnabled: Observable<boolean | null>;\n private readonly _hotspotSsid: Observable<string | null>;\n\n /** Capabilities-change listeners. */\n private capabilitiesListeners: Set<(caps: any) => void> = new Set();\n\n /**\n * Ref-counted handler bookkeeping per stream key.\n *\n * Tracks the number of active handlers for each stream so that\n * `addSubscription` / `removeSubscription` are called exactly once\n * when the first handler is added / last handler is removed.\n */\n private handlerCounts: Map<string, number> = new Map();\n\n /** Cleanup functions returned by router/messageHandlers registrations. */\n private cleanups: Array<() => void> = [];\n\n constructor(deps: DeviceManagerDeps) {\n this.deps = deps;\n this.permissions = deps.permissions;\n\n // ── Initialise Observables ────────────────────────────────────────\n this._connected = new Observable<boolean>(false);\n this._modelName = new Observable<string | null>(null);\n this._batteryLevel = new Observable<number | null>(null);\n this._charging = new Observable<boolean | null>(null);\n this._caseBatteryLevel = new Observable<number | null>(null);\n this._caseCharging = new Observable<boolean | null>(null);\n this._caseOpen = new Observable<boolean | null>(null);\n this._caseRemoved = new Observable<boolean | null>(null);\n this._wifiConnected = new Observable<boolean>(false);\n this._wifiSsid = new Observable<string | null>(null);\n this._wifiLocalIp = new Observable<string | null>(null);\n this._hotspotEnabled = new Observable<boolean | null>(null);\n this._hotspotSsid = new Observable<string | null>(null);\n\n // Expose as read-only shape\n this.state = {\n connected: this._connected,\n modelName: this._modelName,\n batteryLevel: this._batteryLevel,\n charging: this._charging,\n caseBatteryLevel: this._caseBatteryLevel,\n caseCharging: this._caseCharging,\n caseOpen: this._caseOpen,\n caseRemoved: this._caseRemoved,\n wifiConnected: this._wifiConnected,\n wifiSsid: this._wifiSsid,\n wifiLocalIp: this._wifiLocalIp,\n hotspotEnabled: this._hotspotEnabled,\n hotspotSsid: this._hotspotSsid,\n };\n\n // ── Register message handlers ─────────────────────────────────────\n this.cleanups.push(\n deps.messageHandlers.register(\"device_state_update\", (msg: any) => {\n this.handleDeviceStateUpdate(msg);\n }),\n );\n this.cleanups.push(\n deps.messageHandlers.register(\"capabilities_update\", (msg: any) => {\n this.handleCapabilitiesUpdate(msg);\n }),\n );\n }\n\n // ═══════════════════════════════════════════════════════════════════════\n // Hardware Events\n // ═══════════════════════════════════════════════════════════════════════\n\n /**\n * Listen for physical button press events on the glasses.\n *\n * @param handler - Called with {@link ButtonPressEvent} for every press\n * @returns Cleanup function to remove the handler\n *\n * @example\n * ```ts\n * const stop = device.onButtonPress((e) => {\n * if (e.pressType === \"long\") {\n * console.log(\"Long press on\", e.buttonId);\n * }\n * });\n * ```\n */\n onButtonPress(handler: (event: ButtonPressEvent) => void): () => void {\n return this.addStreamHandler(StreamType.BUTTON_PRESS, (_st, data) => {\n handler({\n buttonId: data.buttonId ?? data.button_id ?? \"unknown\",\n pressType: data.pressType ?? data.press_type ?? \"short\",\n });\n });\n }\n\n /**\n * Listen for head position (up/down) events from the IMU.\n *\n * @param handler - Called with {@link HeadPositionEvent} on position change\n * @returns Cleanup function to remove the handler\n */\n onHeadPosition(handler: (event: HeadPositionEvent) => void): () => void {\n return this.addStreamHandler(StreamType.HEAD_POSITION, (_st, data) => {\n handler({\n position: data.position ?? \"down\",\n });\n });\n }\n\n /**\n * Listen for touch/gesture events from the glasses touchpad.\n *\n * Overloaded:\n * - `onTouchEvent(handler)` — all touch events\n * - `onTouchEvent(gesture, handler)` — only events matching the given gesture\n *\n * @param gestureOrHandler - A gesture name string, or a handler for all events\n * @param handler - Handler when the first argument is a gesture name\n * @returns Cleanup function to remove the handler\n *\n * @example\n * ```ts\n * // All gestures\n * device.onTouchEvent((e) => console.log(e.gesture));\n *\n * // Specific gesture\n * device.onTouchEvent(\"double_tap\", (e) => console.log(\"Double tap!\"));\n * ```\n */\n onTouchEvent(handler: (event: TouchEventData) => void): () => void;\n onTouchEvent(gesture: string, handler: (event: TouchEventData) => void): () => void;\n onTouchEvent(\n gestureOrHandler: string | ((event: TouchEventData) => void),\n handler?: (event: TouchEventData) => void,\n ): () => void {\n if (typeof gestureOrHandler === \"function\") {\n // Subscribe to all touch events\n return this.addStreamHandler(StreamType.TOUCH_EVENT, (_st, data) => {\n gestureOrHandler(normaliseTouchEvent(data));\n });\n }\n\n // Subscribe to a specific gesture via \"touch_event:{gesture}\" stream key\n const gesture = gestureOrHandler;\n const gestureStream = `${StreamType.TOUCH_EVENT}:${gesture}`;\n return this.addStreamHandler(gestureStream, (_st, data) => {\n handler!(normaliseTouchEvent(data));\n });\n }\n\n /**\n * Subscribe to multiple touch gestures at once.\n *\n * Registers a handler for each gesture and returns a single cleanup\n * function that removes all of them.\n *\n * @param gestures - Array of gesture names (e.g. `[\"double_tap\", \"forward_swipe\"]`)\n * @returns Cleanup function that removes all gesture subscriptions\n *\n * @example\n * ```ts\n * const stop = device.subscribeToGestures([\"single_tap\", \"double_tap\", \"forward_swipe\"]);\n * // Later:\n * stop();\n * ```\n */\n subscribeToGestures(gestures: string[]): () => void {\n const cleanupFns: Array<() => void> = [];\n\n for (const gesture of gestures) {\n const gestureStream = `${StreamType.TOUCH_EVENT}:${gesture}`;\n\n // Register a no-op handler to establish the subscription.\n // The actual events will be delivered via onTouchEvent handlers.\n const cleanup = this.addStreamHandler(gestureStream, () => {\n // Subscription placeholder — events routed via prefix match\n });\n cleanupFns.push(cleanup);\n }\n\n return () => {\n for (const fn of cleanupFns) {\n fn();\n }\n };\n }\n\n /**\n * Listen for glasses battery update events.\n *\n * Also updates the reactive `state.batteryLevel` and `state.charging` observables.\n *\n * @param handler - Called with {@link BatteryUpdateEvent} on each update\n * @returns Cleanup function to remove the handler\n */\n onBatteryUpdate(handler: (event: BatteryUpdateEvent) => void): () => void {\n return this.addStreamHandler(StreamType.GLASSES_BATTERY_UPDATE, (_st, data) => {\n // Update reactive state from the battery event\n if (data.level !== undefined) {\n this._batteryLevel.setValue(data.level);\n }\n if (data.charging !== undefined) {\n this._charging.setValue(data.charging);\n }\n\n handler({\n level: data.level ?? 0,\n charging: data.charging ?? false,\n timeRemaining: data.timeRemaining ?? data.time_remaining,\n });\n });\n }\n\n /**\n * Listen for VPS (Visual Positioning System) coordinate updates.\n *\n * @param handler - Called with raw VPS coordinate data\n * @returns Cleanup function to remove the handler\n */\n onVpsCoordinates(handler: (event: any) => void): () => void {\n return this.addStreamHandler(StreamType.VPS_COORDINATES, (_st, data) => {\n handler(data);\n });\n }\n\n // ═══════════════════════════════════════════════════════════════════════\n // Actions\n // ═══════════════════════════════════════════════════════════════════════\n\n /**\n * Request the user to set up WiFi on their glasses.\n *\n * Sends a `request_wifi_setup` message to the cloud, which prompts\n * the companion app to display a WiFi configuration flow.\n *\n * @param reason - Optional human-readable reason shown to the user\n *\n * @example\n * ```ts\n * device.requestWifiSetup(\"This app needs WiFi for real-time sync\");\n * ```\n */\n requestWifiSetup(reason?: string): void {\n this.deps.logger.info(`DeviceManager: Requesting WiFi setup${reason ? ` — ${reason}` : \"\"}`);\n this.deps.sendMessage({\n type: \"request_wifi_setup\",\n packageName: this.deps.getPackageName(),\n sessionId: this.deps.getSessionId(),\n timestamp: new Date().toISOString(),\n ...(reason ? { reason } : {}),\n });\n }\n\n // ═══════════════════════════════════════════════════════════════════════\n // Capabilities\n // ═══════════════════════════════════════════════════════════════════════\n\n /**\n * Subscribe to device capability changes.\n *\n * Called when capabilities are first received (CONNECTION_ACK) and\n * whenever the device model or capabilities change mid-session.\n *\n * @param handler - Called with the new capabilities object\n * @returns Cleanup function to remove the handler\n *\n * @example\n * ```ts\n * device.onCapabilitiesChange((caps) => {\n * if (caps?.camera?.photo) {\n * console.log(\"Camera supports photos\");\n * }\n * });\n * ```\n */\n onCapabilitiesChange(handler: (caps: any) => void): () => void {\n this.capabilitiesListeners.add(handler);\n return () => {\n this.capabilitiesListeners.delete(handler);\n };\n }\n\n // ═══════════════════════════════════════════════════════════════════════\n // Internal — Called by MentraSession\n // ═══════════════════════════════════════════════════════════════════════\n\n /**\n * Handle a `device_state_update` message from the cloud.\n *\n * Updates all matching Observable properties. Only fields present in\n * the message are touched — Observables for absent fields keep their\n * current value.\n *\n * @param message - The raw device_state_update message\n * @internal\n */\n handleDeviceStateUpdate(message: any): void {\n const state = message?.state ?? message?.data ?? message;\n if (!state) {\n this.deps.logger.debug(\"DeviceManager: Received empty device_state_update\");\n return;\n }\n\n this.deps.logger.debug(\"DeviceManager: Processing device state update\");\n\n // Connection\n if (state.connected !== undefined) this._connected.setValue(state.connected);\n if (state.modelName !== undefined) this._modelName.setValue(state.modelName);\n\n // WiFi\n if (state.wifiConnected !== undefined) this._wifiConnected.setValue(state.wifiConnected);\n if (state.wifiSsid !== undefined) this._wifiSsid.setValue(state.wifiSsid ?? null);\n if (state.wifiLocalIp !== undefined) this._wifiLocalIp.setValue(state.wifiLocalIp ?? null);\n\n // Battery\n if (state.batteryLevel !== undefined) this._batteryLevel.setValue(state.batteryLevel ?? null);\n if (state.charging !== undefined) this._charging.setValue(state.charging ?? null);\n if (state.caseBatteryLevel !== undefined) this._caseBatteryLevel.setValue(state.caseBatteryLevel ?? null);\n if (state.caseCharging !== undefined) this._caseCharging.setValue(state.caseCharging ?? null);\n if (state.caseOpen !== undefined) this._caseOpen.setValue(state.caseOpen ?? null);\n if (state.caseRemoved !== undefined) this._caseRemoved.setValue(state.caseRemoved ?? null);\n\n // Hotspot\n if (state.hotspotEnabled !== undefined) this._hotspotEnabled.setValue(state.hotspotEnabled ?? null);\n if (state.hotspotSsid !== undefined) this._hotspotSsid.setValue(state.hotspotSsid ?? null);\n }\n\n /**\n * Handle a `capabilities_update` message from the cloud.\n *\n * Extracts the capabilities payload and delegates to {@link setCapabilities}.\n *\n * @param message - The raw capabilities_update message\n * @internal\n */\n handleCapabilitiesUpdate(message: any): void {\n const caps = message?.capabilities ?? message?.data?.capabilities ?? null;\n const modelName = message?.modelName ?? message?.data?.modelName ?? null;\n\n if (modelName) {\n this._modelName.setValue(modelName);\n }\n\n this.setCapabilities(caps);\n }\n\n /**\n * Directly set the device capabilities.\n *\n * Called by MentraSession from the CONNECTION_ACK payload, or by\n * {@link handleCapabilitiesUpdate} for mid-session updates.\n *\n * @param caps - The capabilities object (or null)\n * @internal\n */\n setCapabilities(caps: any): void {\n this.capabilities = caps;\n this.deps.logger.info(`DeviceManager: Capabilities ${caps ? \"updated\" : \"cleared\"}`);\n\n // Notify listeners\n for (const listener of this.capabilitiesListeners) {\n try {\n listener(caps);\n } catch (err) {\n this.deps.logger.error(\n `DeviceManager: Error in capabilities listener: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n }\n\n /**\n * Remove all tracked handlers and clear listeners.\n *\n * Called by MentraSession during disconnect/cleanup.\n *\n * @internal\n */\n destroy(): void {\n for (const cleanup of this.cleanups) {\n cleanup();\n }\n\n this.cleanups.length = 0;\n this.handlerCounts.clear();\n this.capabilitiesListeners.clear();\n }\n\n // ═══════════════════════════════════════════════════════════════════════\n // Private — Stream Handler Management\n // ═══════════════════════════════════════════════════════════════════════\n\n /**\n * Register a handler on the DataStreamRouter for a given stream key,\n * managing subscription lifecycle automatically.\n *\n * - Calls `deps.addSubscription` when the first handler for a key is added.\n * - Calls `deps.removeSubscription` when the last handler for a key is removed.\n *\n * @param streamKey - The stream type or prefixed stream key\n * @param handler - The stream handler function\n * @returns Cleanup function that unregisters the handler and manages subscription\n */\n private addStreamHandler(\n streamKey: string,\n handler: (streamType: string, data: any, message: any) => void,\n ): () => void {\n const currentCount = this.handlerCounts.get(streamKey) ?? 0;\n\n // First handler for this stream — subscribe\n if (currentCount === 0) {\n this.deps.addSubscription(streamKey);\n }\n this.handlerCounts.set(streamKey, currentCount + 1);\n\n // Register on the router\n const routerCleanup = this.deps.router.on(streamKey, handler);\n\n // Track for bulk cleanup\n let cleaned = false;\n const cleanup = () => {\n if (cleaned) return;\n cleaned = true;\n\n // Remove from router\n routerCleanup();\n\n // Decrement handler count\n const count = this.handlerCounts.get(streamKey) ?? 0;\n const newCount = count - 1;\n if (newCount <= 0) {\n this.handlerCounts.delete(streamKey);\n this.deps.removeSubscription(streamKey);\n } else {\n this.handlerCounts.set(streamKey, newCount);\n }\n };\n\n this.cleanups.push(cleanup);\n return cleanup;\n }\n}\n",
28
+ "/**\n * 🖥️ DisplayManager — AR Display Control\n *\n * v3 manager that wraps the existing LayoutManager display functionality.\n * Sends DisplayRequest messages to the cloud with identical wire format\n * to ensure backward compatibility.\n *\n * @example\n * ```ts\n * const display = new DisplayManager(deps);\n *\n * // Simple text\n * display.showText(\"Hello AR World!\");\n *\n * // Pre-wrapped lines\n * display.showText([\"Line 1\", \"Line 2\"]);\n *\n * // Structured layouts\n * display.showReferenceCard(\"Weather\", \"Sunny and 75°F\");\n * display.showDashboardCard(\"BPM\", \"72\");\n *\n * // Clear the display\n * display.clear();\n * ```\n */\n\nimport {\n DisplayRequest,\n Layout,\n TextWall,\n DoubleTextWall,\n ReferenceCard,\n DashboardCard,\n BitmapView,\n ClearView,\n} from \"../../types/layouts\";\nimport { LayoutType, ViewType } from \"../../types/enums\";\nimport { AppToCloudMessageType } from \"../../types/message-types\";\n\n// ─── Dependencies ────────────────────────────────────────────────────────────\n\n/**\n * Shared dependency bag injected by MentraSession.\n * Keeps managers decoupled from the session implementation.\n */\nexport interface ManagerDeps {\n /** DataStreamRouter — register for DATA_STREAM messages by streamType key. */\n router: {\n on(key: string, handler: (streamType: string, data: any, message: any) => void): () => void;\n };\n /** MessageHandlerRegistry — register for top-level message types. */\n messageHandlers: {\n register(type: string, handler: (msg: any) => void): () => void;\n };\n addSubscription: (stream: string) => void;\n removeSubscription: (stream: string) => void;\n sendMessage: (message: any) => void;\n sendBinary: (data: ArrayBuffer | Uint8Array) => void;\n logger: {\n debug(...args: any[]): void;\n info(...args: any[]): void;\n warn(...args: any[]): void;\n error(...args: any[]): void;\n };\n getPackageName: () => string;\n getSessionId: () => string;\n}\n\n// ─── DisplayManager ─────────────────────────────────────────────────────────\n\n/**\n * Controls the AR display on the user's glasses.\n *\n * Provides high-level methods for showing text, cards, bitmaps, and\n * clearing the display. All methods produce a `DisplayRequest` message\n * with `type: \"display_event\"` — the exact same wire format the cloud\n * and glasses firmware already understand from v2.\n */\nexport class DisplayManager {\n private readonly deps: ManagerDeps;\n\n constructor(deps: ManagerDeps) {\n this.deps = deps;\n }\n\n // ─── High-Level API ──────────────────────────────────────────────────────\n\n /**\n * Show text on the AR display.\n *\n * Accepts a single string or an array of pre-wrapped lines.\n * When an array is provided the lines are joined with newlines\n * and sent as a single TextWall layout.\n *\n * @param text - A string or array of strings to display\n *\n * @example\n * ```ts\n * display.showText(\"Connected to server\");\n * display.showText([\"Line 1\", \"Line 2\", \"Line 3\"]);\n * ```\n */\n showText(text: string | string[]): void {\n const resolved = Array.isArray(text) ? text.join(\"\\n\") : text;\n this.showTextWall(resolved);\n }\n\n /**\n * 📝 Show a single block of text on the main display.\n *\n * Best for simple messages, status updates, and notifications.\n *\n * @param text - Text content to display\n *\n * @example\n * ```ts\n * display.showTextWall(\"Listening…\");\n * ```\n */\n showTextWall(text: string): void {\n if (text === undefined || text === null) {\n text = \"\";\n this.deps.logger.warn(\"showTextWall called with null/undefined text\");\n }\n\n if (typeof text !== \"string\") {\n text = String(text);\n this.deps.logger.warn(\"showTextWall: non-string input converted to string\");\n }\n\n const layout: TextWall = {\n layoutType: LayoutType.TEXT_WALL,\n text,\n };\n\n try {\n this.sendDisplayEvent(layout);\n } catch (err) {\n this.deps.logger.error(\"Failed to display text wall:\", err);\n }\n }\n\n /**\n * ↕️ Show two sections of text, one above the other.\n *\n * Best for before/after content, question/answer, translations,\n * or any two-part message.\n *\n * @param leftText - Text for the top section\n * @param rightText - Text for the bottom section\n *\n * @example\n * ```ts\n * display.showDoubleTextWall(\"Original: Hello\", \"Translated: Bonjour\");\n * ```\n */\n showDoubleTextWall(leftText: string, rightText: string): void {\n const layout: DoubleTextWall = {\n layoutType: LayoutType.DOUBLE_TEXT_WALL,\n topText: leftText,\n bottomText: rightText,\n };\n this.sendDisplayEvent(layout);\n }\n\n /**\n * 📇 Show a card with a title and body text.\n *\n * Best for titled content, important information, and notifications\n * with context.\n *\n * @param title - Card title\n * @param body - Main content text\n *\n * @example\n * ```ts\n * display.showReferenceCard(\"Meeting Reminder\", \"Team standup in 5 minutes\");\n * ```\n */\n showReferenceCard(title: string, body: string): void {\n const layout: ReferenceCard = {\n layoutType: LayoutType.REFERENCE_CARD,\n title,\n text: body,\n };\n this.sendDisplayEvent(layout);\n }\n\n /**\n * 📊 Show a dashboard card with left and right text.\n *\n * Best for key-value pairs, metrics, and dashboard-style displays.\n * Automatically uses the DASHBOARD view type.\n *\n * @param leftText - Left side text (typically label/key)\n * @param rightText - Right side text (typically value)\n *\n * @example\n * ```ts\n * display.showDashboardCard(\"Weather\", \"72°F\");\n * ```\n */\n showDashboardCard(leftText: string, rightText: string): void {\n const layout: DashboardCard = {\n layoutType: LayoutType.DASHBOARD_CARD,\n leftText,\n rightText,\n };\n this.sendDisplayEvent(layout, ViewType.DASHBOARD);\n }\n\n /**\n * 🖼️ Show a bitmap image on the display.\n *\n * @param data - Hex or base64 encoded bitmap data string\n *\n * @example\n * ```ts\n * display.showBitmap(base64EncodedBitmapString);\n * ```\n */\n showBitmap(data: any): void {\n if (typeof data !== \"string\") {\n this.deps.logger.error(\"showBitmap: data must be a string\");\n return;\n }\n\n if (data.length > 1_000_000) {\n this.deps.logger.error(\"showBitmap: data exceeds 1 MB limit\");\n return;\n }\n\n const layout: BitmapView = {\n layoutType: LayoutType.BITMAP_VIEW,\n data,\n };\n this.sendDisplayEvent(layout);\n }\n\n /**\n * 🧹 Clear the AR display.\n *\n * Removes any currently shown content from the main view.\n *\n * @example\n * ```ts\n * display.clear();\n * ```\n */\n clear(): void {\n const layout: ClearView = {\n layoutType: LayoutType.CLEAR_VIEW,\n };\n this.sendDisplayEvent(layout);\n }\n\n // ─── Internal ────────────────────────────────────────────────────────────\n\n /**\n * Build and send a DisplayRequest message.\n *\n * Wire format is identical to v2 LayoutManager — the cloud receives\n * the same `{ type: \"display_event\", packageName, view, layout, … }`.\n *\n * @param layout - The layout configuration to display\n * @param view - View type (main or dashboard), defaults to MAIN\n * @param durationMs - Optional display duration in milliseconds\n */\n private sendDisplayEvent(layout: Layout, view: ViewType = ViewType.MAIN, durationMs?: number): void {\n if (!layout || !layout.layoutType) {\n this.deps.logger.error(\"sendDisplayEvent: layout must have a layoutType property\");\n return;\n }\n\n // Validate view type\n if (view !== ViewType.MAIN && view !== ViewType.DASHBOARD) {\n this.deps.logger.warn(`Invalid view type: ${view}, defaulting to MAIN`);\n view = ViewType.MAIN;\n }\n\n // Validate duration\n if (durationMs !== undefined) {\n if (typeof durationMs !== \"number\" || durationMs < 0) {\n this.deps.logger.warn(`Invalid duration: ${durationMs}, ignoring`);\n durationMs = undefined;\n }\n }\n\n const message: DisplayRequest = {\n timestamp: new Date(),\n sessionId: this.deps.getSessionId(),\n type: AppToCloudMessageType.DISPLAY_REQUEST,\n packageName: this.deps.getPackageName(),\n view,\n layout,\n durationMs,\n };\n\n this.deps.sendMessage(message);\n }\n}\n",
29
+ "/**\n * LedManager — v3 SDK LED Control API\n *\n * Thin wrapper around the existing LedModule patterns. Provides a simplified\n * API for controlling RGB LEDs on connected smart glasses.\n *\n * Wire format is identical to v2:\n * ```json\n * {\n * \"type\": \"rgb_led_control\",\n * \"packageName\": \"<packageName>\",\n * \"sessionId\": \"<sessionId>\",\n * \"requestId\": \"<requestId>\",\n * \"action\": \"on\" | \"off\",\n * \"color\": \"<LedColor>\",\n * \"ontime\": <ms>\n * }\n * ```\n *\n * @module\n */\n\nimport { AppToCloudMessageType } from \"../../types\";\nimport type { LedColor } from \"../../types\";\n\n// ─── Internal Types ─────────────────────────────────────────────────────────\n\n/**\n * Dependencies injected by MentraSession.\n *\n * Structural type — no concrete imports so the manager stays unit-testable\n * with plain stubs.\n */\nexport interface LedManagerDeps {\n /** DataStreamRouter — register for DATA_STREAM messages by streamType key. */\n router: {\n on(key: string, handler: (streamType: string, data: any, message: any) => void): () => void;\n };\n /** MessageHandlerRegistry — register for top-level message types. */\n messageHandlers: {\n register(type: string, handler: (msg: any) => void): () => void;\n };\n /** Add a subscription string (triggers SUBSCRIPTION_UPDATE to cloud). */\n addSubscription: (stream: string) => void;\n /** Remove a subscription string. */\n removeSubscription: (stream: string) => void;\n /** Send an arbitrary JSON message over the WebSocket. */\n sendMessage: (message: any) => void;\n /** Structured logger. */\n logger: {\n debug(...args: any[]): void;\n info(...args: any[]): void;\n warn(...args: any[]): void;\n error(...args: any[]): void;\n };\n /** Package name for outgoing messages. */\n getPackageName: () => string;\n /** Current session ID. */\n getSessionId: () => string;\n}\n\n// ─── Helpers ────────────────────────────────────────────────────────────────\n\n/**\n * Generate a unique request ID for LED control requests.\n * Uses `crypto.randomUUID()` when available, falls back to timestamp + random.\n */\nfunction generateRequestId(): string {\n if (typeof crypto !== \"undefined\" && typeof crypto.randomUUID === \"function\") {\n return `led_req_${crypto.randomUUID()}`;\n }\n return `led_req_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;\n}\n\n// ─── Manager ────────────────────────────────────────────────────────────────\n\n/**\n * Controls RGB LEDs on connected smart glasses.\n *\n * LED commands are fire-and-forget — the methods return immediately after\n * sending the control message to the cloud. No response is awaited.\n *\n * @example\n * ```ts\n * const session = await mentra.connect();\n *\n * // Solid green LED for 2 seconds\n * session.led.setColor(\"green\", 2000);\n *\n * // Turn LED off\n * session.led.off();\n * ```\n */\nexport class LedManager {\n private readonly deps: LedManagerDeps;\n\n constructor(deps: LedManagerDeps) {\n this.deps = deps;\n }\n\n // ─── Public API ─────────────────────────────────────────────────────────\n\n /**\n * Set the LED to a specific colour.\n *\n * Sends an `rgb_led_control` message with `action: \"on\"` to the cloud.\n * The LED will remain on for `onTimeMs` milliseconds (defaults to 1000ms)\n * then turn off automatically on the device.\n *\n * @param color - LED colour name. One of `\"red\"`, `\"green\"`, `\"blue\"`, `\"orange\"`, `\"white\"`.\n * @param onTimeMs - Duration in milliseconds the LED stays on. Defaults to `1000`.\n *\n * @example\n * ```ts\n * // Red LED for 500ms\n * session.led.setColor(\"red\", 500);\n *\n * // White LED for the default 1s\n * session.led.setColor(\"white\");\n * ```\n */\n setColor(color: string, onTimeMs?: number): void {\n const requestId = generateRequestId();\n\n const message = {\n type: AppToCloudMessageType.RGB_LED_CONTROL,\n packageName: this.deps.getPackageName(),\n sessionId: this.deps.getSessionId(),\n requestId,\n timestamp: new Date(),\n action: \"on\" as const,\n color: color as LedColor,\n ontime: onTimeMs ?? 1000,\n offtime: 0,\n count: 1,\n };\n\n this.deps.sendMessage(message);\n\n this.deps.logger.debug({ requestId, color, ontime: message.ontime }, \"💡 LED setColor request sent\");\n }\n\n /**\n * Turn the LED off.\n *\n * Sends an `rgb_led_control` message with `action: \"off\"` to the cloud.\n *\n * @example\n * ```ts\n * session.led.off();\n * ```\n */\n off(): void {\n const requestId = generateRequestId();\n\n const message = {\n type: AppToCloudMessageType.RGB_LED_CONTROL,\n packageName: this.deps.getPackageName(),\n sessionId: this.deps.getSessionId(),\n requestId,\n timestamp: new Date(),\n action: \"off\" as const,\n };\n\n this.deps.sendMessage(message);\n\n this.deps.logger.debug({ requestId }, \"💡 LED off request sent\");\n }\n\n // ─── Cleanup ────────────────────────────────────────────────────────────\n\n /**\n * Clean up resources.\n *\n * Called by MentraSession during disconnect/cleanup. LED commands are\n * fire-and-forget so there is no pending state to drain.\n *\n * @internal\n */\n destroy(): void {\n this.deps.logger.debug(\"[LedManager] Destroyed.\");\n }\n}\n",
30
+ "/**\n * LocationManager — v3 SDK Location API\n *\n * Wraps the existing LocationManager patterns from v2 with a cleaner,\n * composable API. Subscribes to location data streams, caches the latest\n * known position, and supports one-shot location polls.\n *\n * Wire format is identical to v2:\n * - Location stream subscription: `\"location_stream\"` added to subscriptions\n * - Location poll: `{ type: \"location_poll_request\", packageName, sessionId, accuracy, correlationId }`\n * - Location updates arrive as DATA_STREAM messages with streamType `\"location_update\"` or `\"location_stream\"`\n *\n * The LocationUpdate payload shape (from glasses-to-cloud):\n * ```json\n * {\n * \"type\": \"location_update\",\n * \"lat\": number,\n * \"lng\": number,\n * \"accuracy\": number,\n * \"correlationId\": string | undefined\n * }\n * ```\n *\n * @module\n */\n\nimport { AppToCloudMessageType, StreamType } from \"../../types\";\n\n// ─── Public Types ───────────────────────────────────────────────────────────\n\n/**\n * Normalised location data delivered to subscriber callbacks.\n */\nexport interface LocationData {\n /** Latitude in decimal degrees. */\n lat: number;\n /** Longitude in decimal degrees. */\n lng: number;\n /** Horizontal accuracy in metres (`undefined` if not available). */\n accuracy?: number;\n /** Unix timestamp (ms) when the location was recorded. */\n timestamp: number;\n /** Correlation ID returned from a one-shot poll (if applicable). */\n correlationId?: string;\n}\n\n/**\n * Accuracy tier for location stream subscriptions.\n * Maps directly to the v2 `LocationStreamRequest.rate` values.\n */\nexport type LocationAccuracy =\n | \"standard\"\n | \"high\"\n | \"realtime\"\n | \"tenMeters\"\n | \"hundredMeters\"\n | \"kilometer\"\n | \"threeKilometers\"\n | \"reduced\";\n\n/** Callback signature for location subscribers. */\nexport type LocationHandler = (location: LocationData) => void;\n\n// ─── Internal Types ─────────────────────────────────────────────────────────\n\n/**\n * Dependencies injected by MentraSession.\n *\n * Structural type — no concrete imports so the manager stays unit-testable\n * with plain stubs.\n */\nexport interface LocationManagerDeps {\n /** DataStreamRouter — register for DATA_STREAM messages by streamType key. */\n router: {\n on(key: string, handler: (streamType: string, data: any, message: any) => void): () => void;\n };\n /** MessageHandlerRegistry — register for top-level message types. */\n messageHandlers: {\n register(type: string, handler: (msg: any) => void): () => void;\n };\n /** Add a subscription string (triggers SUBSCRIPTION_UPDATE to cloud). */\n addSubscription: (stream: string) => void;\n /** Remove a subscription string. */\n removeSubscription: (stream: string) => void;\n /** Send an arbitrary JSON message over the WebSocket. */\n sendMessage: (message: any) => void;\n /** Structured logger. */\n logger: {\n debug(...args: any[]): void;\n info(...args: any[]): void;\n warn(...args: any[]): void;\n error(...args: any[]): void;\n };\n /** Package name for outgoing messages. */\n getPackageName: () => string;\n /** Current session ID. */\n getSessionId: () => string;\n}\n\n/**\n * Internal bookkeeping for a single `onUpdate()` registration.\n */\ninterface Registration {\n /** Cleanup function returned by `router.on()` for the primary stream. */\n routerCleanup: () => void;\n /** Cleanup function returned by `router.on()` for the secondary location_update stream. */\n updateCleanup: () => void;\n /** The stream key this registration subscribed to. */\n streamKey: string;\n}\n\n// ─── Constants ──────────────────────────────────────────────────────────────\n\n/** Stream type for continuous location updates. */\nconst LOCATION_STREAM = StreamType.LOCATION_STREAM; // \"location_stream\"\n\n/** Stream type for individual location update events. */\nconst LOCATION_UPDATE = StreamType.LOCATION_UPDATE; // \"location_update\"\n\n/** Default timeout for one-shot location polls. */\nconst POLL_TIMEOUT_MS = 15_000;\n\n// ─── Helpers ────────────────────────────────────────────────────────────────\n\n/**\n * Generate a unique correlation ID for location poll requests.\n */\nfunction generateCorrelationId(): string {\n if (typeof crypto !== \"undefined\" && typeof crypto.randomUUID === \"function\") {\n return `poll_${crypto.randomUUID()}`;\n }\n return `poll_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;\n}\n\n/**\n * Normalise raw location data from a DATA_STREAM event into the public\n * {@link LocationData} shape.\n */\nfunction normalise(raw: any): LocationData {\n return {\n lat: typeof raw.lat === \"number\" ? raw.lat : 0,\n lng: typeof raw.lng === \"number\" ? raw.lng : 0,\n accuracy: typeof raw.accuracy === \"number\" ? raw.accuracy : undefined,\n timestamp: raw.timestamp ? new Date(raw.timestamp).getTime() : Date.now(),\n correlationId: raw.correlationId,\n };\n}\n\n// ─── Manager ────────────────────────────────────────────────────────────────\n\n/**\n * Manages location subscriptions, caches the latest known position, and\n * supports one-shot location polls.\n *\n * @example\n * ```ts\n * const session = await mentra.connect();\n *\n * // Subscribe to continuous location updates\n * const stop = session.location.onUpdate((loc) => {\n * console.log(`${loc.lat}, ${loc.lng} (±${loc.accuracy}m)`);\n * });\n *\n * // Read cached values at any time\n * console.log(\"Last known:\", session.location.lat, session.location.lng);\n *\n * // Request a single location update\n * session.location.requestUpdate();\n *\n * // Stop all subscriptions\n * session.location.stop();\n * ```\n */\nexport class LocationManager {\n private readonly deps: LocationManagerDeps;\n\n /**\n * All currently-active registrations. Tracked so that {@link stop}\n * can clean everything up in one shot.\n */\n private registrations = new Set<Registration>();\n\n /**\n * Reference count for the location_stream subscription.\n * We only call `removeSubscription` when ref-count drops to zero.\n */\n private streamRefCount = 0;\n\n // ─── Cached State ───────────────────────────────────────────────────────\n\n /** Latest latitude, or `null` if no update has been received. */\n private _lat: number | null = null;\n\n /** Latest longitude, or `null` if no update has been received. */\n private _lng: number | null = null;\n\n /** Latest horizontal accuracy in metres, or `null` if unknown. */\n private _accuracy: number | null = null;\n\n /** Timestamp of the latest location update, or `null` if none received. */\n private _timestamp: number | null = null;\n\n /**\n * Whether the device has location permission.\n * Optimistically `true`, updated to `false` on permission errors.\n */\n private _hasPermission = true;\n\n /** Cleanup for the internal location_update message handler. */\n private locationUpdateCleanup: (() => void) | null = null;\n\n constructor(deps: LocationManagerDeps) {\n this.deps = deps;\n\n // Register a router handler for \"location_update\" stream type so we always\n // cache the latest position, even if no `onUpdate()` listener is active.\n this.locationUpdateCleanup = this.deps.router.on(LOCATION_UPDATE, (_streamType, data, _message) => {\n this.cacheLocation(data);\n });\n }\n\n // ─── Public API ─────────────────────────────────────────────────────────\n\n /**\n * Subscribe to continuous location updates.\n *\n * Registers on the DataStreamRouter for `\"location_stream\"` events and\n * adds the `\"location_stream\"` subscription to the cloud. Multiple\n * independent subscriptions are supported — each returns its own\n * cleanup function.\n *\n * @param handler - Called each time a location update arrives.\n * @param accuracy - Desired accuracy tier. Defaults to `\"standard\"`.\n * @returns A cleanup function that removes this specific subscription.\n *\n * @example\n * ```ts\n * const stop = session.location.onUpdate((loc) => {\n * console.log(`${loc.lat}, ${loc.lng}`);\n * });\n *\n * // Later:\n * stop();\n * ```\n */\n onUpdate(handler: LocationHandler, accuracy?: LocationAccuracy): () => void {\n const streamKey = LOCATION_STREAM;\n\n // Register on the router for location_stream events\n const routerCleanup = this.deps.router.on(streamKey, (_streamType, data, _message) => {\n try {\n const location = normalise(data);\n this.cacheLocation(data);\n handler(location);\n } catch (err) {\n this.deps.logger.error(\"[LocationManager] Error in onUpdate handler:\", err);\n }\n });\n\n // Also listen for location_update events (single updates, poll responses, etc.)\n const updateCleanup = this.deps.router.on(LOCATION_UPDATE, (_streamType, data, _message) => {\n try {\n const location = normalise(data);\n this.cacheLocation(data);\n handler(location);\n } catch (err) {\n this.deps.logger.error(\"[LocationManager] Error in onUpdate handler (location_update):\", err);\n }\n });\n\n const reg: Registration = { routerCleanup, updateCleanup, streamKey };\n\n this.registrations.add(reg);\n\n // Increment ref count and subscribe if first listener\n this.streamRefCount++;\n if (this.streamRefCount === 1) {\n // Add subscription with accuracy rate — v2 uses LocationStreamRequest format\n this.deps.addSubscription(streamKey);\n this.deps.logger.debug({ accuracy: accuracy ?? \"standard\" }, `[LocationManager] Subscribed to \"${streamKey}\".`);\n }\n\n // Return composite cleanup\n return () => {\n if (!this.registrations.has(reg)) return; // Already cleaned up (idempotent)\n\n routerCleanup();\n updateCleanup();\n this.registrations.delete(reg);\n\n // Decrement ref count and unsubscribe if last listener\n this.streamRefCount--;\n if (this.streamRefCount <= 0) {\n this.streamRefCount = 0;\n this.deps.removeSubscription(streamKey);\n this.deps.logger.debug(`[LocationManager] Unsubscribed from \"${streamKey}\".`);\n }\n };\n }\n\n /**\n * Request a single location update (one-shot poll).\n *\n * Sends a `location_poll_request` message to the cloud. The response\n * will arrive as a `location_update` DATA_STREAM event and will be\n * delivered to any active `onUpdate()` listeners as well as updating\n * the cached position.\n *\n * @param accuracy - Desired accuracy tier for the poll. Defaults to `\"standard\"`.\n *\n * @example\n * ```ts\n * session.location.requestUpdate();\n * // The next onUpdate() callback will fire with the fresh position.\n * ```\n */\n requestUpdate(accuracy?: LocationAccuracy): void {\n const correlationId = generateCorrelationId();\n\n const message = {\n type: AppToCloudMessageType.LOCATION_POLL_REQUEST,\n correlationId,\n packageName: this.deps.getPackageName(),\n sessionId: this.deps.getSessionId(),\n accuracy: accuracy ?? \"standard\",\n };\n\n this.deps.sendMessage(message);\n\n this.deps.logger.debug({ correlationId, accuracy: message.accuracy }, \"📍 Location poll request sent\");\n }\n\n /**\n * Stop all location subscriptions and remove every handler.\n *\n * After calling this, no location callbacks will fire until new\n * subscriptions are created via {@link onUpdate}.\n *\n * @example\n * ```ts\n * session.location.stop();\n * ```\n */\n stop(): void {\n // Iterate over a snapshot — cleanup mutates the set\n const snapshot = Array.from(this.registrations);\n for (const reg of snapshot) {\n reg.routerCleanup();\n reg.updateCleanup();\n this.registrations.delete(reg);\n }\n\n // Force unsubscribe regardless of ref count\n if (this.streamRefCount > 0) {\n this.deps.removeSubscription(LOCATION_STREAM);\n }\n this.streamRefCount = 0;\n\n this.deps.logger.debug(\"[LocationManager] All subscriptions stopped.\");\n }\n\n // ─── Cached Accessors ──────────────────────────────────────────────────\n\n /**\n * Latest known latitude, or `null` if no location update has been received.\n */\n get lat(): number | null {\n return this._lat;\n }\n\n /**\n * Latest known longitude, or `null` if no location update has been received.\n */\n get lng(): number | null {\n return this._lng;\n }\n\n /**\n * Latest horizontal accuracy in metres, or `null` if unknown.\n */\n get accuracy(): number | null {\n return this._accuracy;\n }\n\n /**\n * Unix timestamp (ms) of the latest location update, or `null` if none received.\n */\n get timestamp(): number | null {\n return this._timestamp;\n }\n\n /**\n * Whether the device has granted location permission.\n *\n * Optimistically `true` — updated to `false` if a permission error\n * is received from the cloud.\n */\n get hasPermission(): boolean {\n return this._hasPermission;\n }\n\n // ─── Internal ───────────────────────────────────────────────────────────\n\n /**\n * Update cached location values from raw incoming data.\n */\n private cacheLocation(raw: any): void {\n if (typeof raw.lat === \"number\") {\n this._lat = raw.lat;\n }\n if (typeof raw.lng === \"number\") {\n this._lng = raw.lng;\n }\n if (typeof raw.accuracy === \"number\") {\n this._accuracy = raw.accuracy;\n }\n this._timestamp = raw.timestamp ? new Date(raw.timestamp).getTime() : Date.now();\n }\n\n /**\n * Called by MentraSession if a permission error for location is received.\n * @internal\n */\n setPermission(granted: boolean): void {\n this._hasPermission = granted;\n }\n\n // ─── Cleanup ────────────────────────────────────────────────────────────\n\n /**\n * Clean up all resources.\n *\n * Called by MentraSession during disconnect/cleanup.\n * @internal\n */\n destroy(): void {\n this.stop();\n\n if (this.locationUpdateCleanup) {\n this.locationUpdateCleanup();\n this.locationUpdateCleanup = null;\n }\n\n this._lat = null;\n this._lng = null;\n this._accuracy = null;\n this._timestamp = null;\n\n this.deps.logger.debug(\"[LocationManager] Destroyed.\");\n }\n}\n",
31
+ "/**\n * 🎤 MicManager — Microphone Input Control\n *\n * v3 manager that handles audio input from the user's glasses microphone.\n * Provides subscriptions for raw PCM audio chunks and voice activity\n * detection (VAD) events.\n *\n * Audio chunks arrive as binary WebSocket frames — MentraSession calls\n * `handleBinaryAudio()` when it receives a binary frame that isn't\n * destined for an output stream. VAD events arrive as JSON DATA_STREAM\n * messages routed through the DataStreamRouter.\n *\n * @example\n * ```ts\n * const mic = new MicManager(deps);\n *\n * // Listen for raw audio\n * const stopChunks = mic.onChunk((chunk) => {\n * console.log(`Got ${chunk.data.byteLength} bytes at ${chunk.sampleRate}Hz`);\n * });\n *\n * // Listen for voice activity\n * const stopVad = mic.onVoiceActivity((vad) => {\n * console.log(vad.isSpeaking ? \"Speech started\" : \"Speech ended\");\n * });\n *\n * // Check state\n * console.log(\"Speaking:\", mic.isSpeaking);\n * console.log(\"Active:\", mic.isActive);\n *\n * // Cleanup\n * mic.stop();\n * ```\n */\n\n// ─── Dependencies ────────────────────────────────────────────────────────────\n\n/**\n * Shared dependency bag injected by MentraSession.\n * Keeps managers decoupled from the session implementation.\n */\nexport interface ManagerDeps {\n /** DataStreamRouter — register for DATA_STREAM messages by streamType key. */\n router: {\n on(key: string, handler: (streamType: string, data: any, message: any) => void): () => void;\n };\n /** MessageHandlerRegistry — register for top-level message types. */\n messageHandlers: {\n register(type: string, handler: (msg: any) => void): () => void;\n };\n addSubscription: (stream: string) => void;\n removeSubscription: (stream: string) => void;\n sendMessage: (message: any) => void;\n sendBinary: (data: ArrayBuffer | Uint8Array) => void;\n logger: {\n debug(...args: any[]): void;\n info(...args: any[]): void;\n warn(...args: any[]): void;\n error(...args: any[]): void;\n };\n getPackageName: () => string;\n getSessionId: () => string;\n}\n\n// ─── Public Types ────────────────────────────────────────────────────────────\n\n/**\n * A chunk of raw PCM audio data from the glasses microphone.\n *\n * Audio is always 16 kHz mono 16-bit signed PCM — the native format\n * of the glasses microphone hardware.\n */\nexport interface AudioChunk {\n /** Raw PCM audio data */\n data: ArrayBuffer;\n /** Sample rate in Hz (always 16000) */\n sampleRate: number;\n /** Number of audio channels (always 1 — mono) */\n channels: number;\n /** Timestamp when this chunk was received (ms since epoch) */\n timestamp: number;\n}\n\n/**\n * Voice activity detection event.\n *\n * Indicates whether the user is currently speaking. The glasses run\n * on-device VAD and send status updates as the speech state changes.\n */\nexport interface VadEvent {\n /** Whether speech is currently detected */\n isSpeaking: boolean;\n /** Timestamp when this event was received (ms since epoch) */\n timestamp: number;\n}\n\n// ─── Stream Type Constants ───────────────────────────────────────────────────\n\n/** Stream type for audio chunk subscriptions (matches StreamType.AUDIO_CHUNK) */\nconst AUDIO_CHUNK_STREAM = \"audio_chunk\";\n\n/** Stream type for VAD subscriptions (matches StreamType.VAD) */\nconst VAD_STREAM = \"VAD\";\n\n// ─── MicManager ─────────────────────────────────────────────────────────────\n\n/**\n * Manages microphone input from the user's glasses.\n *\n * Handles two types of incoming data:\n *\n * 1. **Raw PCM audio chunks** — arrive as binary WebSocket frames.\n * MentraSession calls `handleBinaryAudio()` for each binary frame\n * that isn't part of an output stream. Subscribers receive wrapped\n * `AudioChunk` objects with metadata.\n *\n * 2. **Voice Activity Detection (VAD)** — arrives as JSON DATA_STREAM\n * messages with `streamType: \"VAD\"`. The glasses send `status: true`\n * when speech starts and `status: false` when it stops. The raw\n * `status` field may be a boolean or string (\"true\"/\"false\") — this\n * manager normalizes it to a clean boolean.\n *\n * Subscription lifecycle:\n * - `onChunk()` adds an \"audio_chunk\" subscription when the first handler\n * is registered, and removes it when the last handler unsubscribes.\n * - `onVoiceActivity()` does the same for the \"VAD\" subscription.\n * - `stop()` removes all handlers and unsubscribes from both streams.\n */\nexport class MicManager {\n private readonly deps: ManagerDeps;\n\n /** Registered handlers for audio chunk data */\n private chunkHandlers = new Set<(chunk: AudioChunk) => void>();\n\n /** Registered handlers for VAD events */\n private vadHandlers = new Set<(vad: VadEvent) => void>();\n\n /** Cleanup function for the VAD router subscription */\n private vadRouterCleanup: (() => void) | null = null;\n\n /** Cached latest VAD state — true when speech is detected */\n private _isSpeaking = false;\n\n /** Cached permission state */\n private _hasPermission = true;\n\n constructor(deps: ManagerDeps) {\n this.deps = deps;\n }\n\n // ─── Public API ──────────────────────────────────────────────────────────\n\n /**\n * Whether speech is currently detected.\n *\n * This value is cached from the most recent VAD event. It is only\n * updated while there is at least one `onVoiceActivity` subscriber.\n *\n * @example\n * ```ts\n * if (mic.isSpeaking) {\n * // User is talking — maybe pause TTS\n * }\n * ```\n */\n get isSpeaking(): boolean {\n return this._isSpeaking;\n }\n\n /**\n * Whether this app has an active `onChunk` subscription.\n *\n * Returns true when at least one audio chunk handler is registered,\n * meaning the microphone stream is subscribed and binary audio data\n * is being delivered.\n */\n get isActive(): boolean {\n return this.chunkHandlers.size > 0;\n }\n\n /**\n * Whether the app has microphone permission.\n *\n * Updated when the cloud sends permission state changes. Apps that\n * require microphone access should declare `MICROPHONE` in their\n * hardware requirements.\n */\n get hasPermission(): boolean {\n return this._hasPermission;\n }\n\n /**\n * 🎤 Subscribe to raw PCM audio chunks from the microphone.\n *\n * Audio is always 16 kHz mono 16-bit signed PCM. The first subscriber\n * triggers an \"audio_chunk\" subscription to the cloud; the subscription\n * is removed when the last handler unsubscribes.\n *\n * @param handler - Called with each audio chunk as it arrives\n * @returns Cleanup function that removes this handler\n *\n * @example\n * ```ts\n * const stop = mic.onChunk((chunk) => {\n * // chunk.data is an ArrayBuffer of PCM16 samples\n * const samples = new Int16Array(chunk.data);\n * processAudio(samples);\n * });\n *\n * // Later: unsubscribe\n * stop();\n * ```\n */\n onChunk(handler: (chunk: AudioChunk) => void): () => void {\n const isFirst = this.chunkHandlers.size === 0;\n\n this.chunkHandlers.add(handler);\n\n // Subscribe to the audio_chunk stream when the first handler registers\n if (isFirst) {\n this.deps.addSubscription(AUDIO_CHUNK_STREAM);\n this.deps.logger.debug(\"Subscribed to audio_chunk stream\");\n }\n\n // Return cleanup function\n return () => {\n this.chunkHandlers.delete(handler);\n\n // Unsubscribe when the last handler is removed\n if (this.chunkHandlers.size === 0) {\n this.deps.removeSubscription(AUDIO_CHUNK_STREAM);\n this.deps.logger.debug(\"Unsubscribed from audio_chunk stream\");\n }\n };\n }\n\n /**\n * 🗣️ Subscribe to voice activity detection events.\n *\n * VAD events indicate when the user starts or stops speaking. The\n * `status` field from the glasses may be a boolean or a string\n * (\"true\"/\"false\") — this manager normalizes it to a clean boolean.\n *\n * The first subscriber triggers a \"VAD\" subscription to the cloud and\n * registers a handler on the DataStreamRouter. The subscription is\n * removed when the last handler unsubscribes.\n *\n * @param handler - Called with each VAD event\n * @returns Cleanup function that removes this handler\n *\n * @example\n * ```ts\n * const stop = mic.onVoiceActivity((vad) => {\n * if (vad.isSpeaking) {\n * console.log(\"User started speaking\");\n * } else {\n * console.log(\"User stopped speaking\");\n * }\n * });\n *\n * // Later: unsubscribe\n * stop();\n * ```\n */\n onVoiceActivity(handler: (vad: VadEvent) => void): () => void {\n const isFirst = this.vadHandlers.size === 0;\n\n this.vadHandlers.add(handler);\n\n // Subscribe and register router handler when the first handler registers\n if (isFirst) {\n this.deps.addSubscription(VAD_STREAM);\n\n // Listen for VAD DATA_STREAM messages on the router\n this.vadRouterCleanup = this.deps.router.on(VAD_STREAM, (_streamType: string, data: any, _message: any) => {\n this.handleVadMessage(data);\n });\n\n this.deps.logger.debug(\"Subscribed to VAD stream\");\n }\n\n // Return cleanup function\n return () => {\n this.vadHandlers.delete(handler);\n\n // Unsubscribe and remove router handler when the last handler is removed\n if (this.vadHandlers.size === 0) {\n this.deps.removeSubscription(VAD_STREAM);\n\n if (this.vadRouterCleanup) {\n this.vadRouterCleanup();\n this.vadRouterCleanup = null;\n }\n\n this.deps.logger.debug(\"Unsubscribed from VAD stream\");\n }\n };\n }\n\n /**\n * 🛑 Stop all microphone subscriptions.\n *\n * Removes all registered chunk and VAD handlers, unsubscribes from\n * both streams, and resets internal state. After calling `stop()`,\n * `isActive` will be false and no more callbacks will fire.\n *\n * @example\n * ```ts\n * mic.onChunk(handleChunk);\n * mic.onVoiceActivity(handleVad);\n *\n * // Later: clean up everything\n * mic.stop();\n * ```\n */\n stop(): void {\n // Clean up chunk handlers\n if (this.chunkHandlers.size > 0) {\n this.chunkHandlers.clear();\n this.deps.removeSubscription(AUDIO_CHUNK_STREAM);\n this.deps.logger.debug(\"Stopped audio_chunk subscriptions\");\n }\n\n // Clean up VAD handlers\n if (this.vadHandlers.size > 0) {\n this.vadHandlers.clear();\n this.deps.removeSubscription(VAD_STREAM);\n\n if (this.vadRouterCleanup) {\n this.vadRouterCleanup();\n this.vadRouterCleanup = null;\n }\n\n this.deps.logger.debug(\"Stopped VAD subscriptions\");\n }\n\n // Reset cached state\n this._isSpeaking = false;\n }\n\n // ─── Binary Audio Ingestion ──────────────────────────────────────────────\n\n /**\n * Handle an incoming binary audio frame from the WebSocket transport.\n *\n * MentraSession calls this method when it receives a binary WebSocket\n * frame that is identified as microphone audio (not part of an output\n * stream). The raw bytes are wrapped with metadata and dispatched to\n * all registered `onChunk` handlers.\n *\n * @param data - Raw binary audio data (PCM16, 16 kHz, mono)\n *\n * @remarks\n * This is a public method so MentraSession can call it, but it is not\n * intended to be called by app developers.\n *\n * @internal\n */\n handleBinaryAudio(data: ArrayBuffer): void {\n if (this.chunkHandlers.size === 0) {\n // No subscribers — skip processing\n return;\n }\n\n const chunk: AudioChunk = {\n data,\n sampleRate: 16000,\n channels: 1,\n timestamp: Date.now(),\n };\n\n for (const handler of this.chunkHandlers) {\n try {\n handler(chunk);\n } catch (err) {\n this.deps.logger.error(\"Audio chunk handler error:\", err);\n }\n }\n }\n\n // ─── Internal ────────────────────────────────────────────────────────────\n\n /**\n * Process an incoming VAD DATA_STREAM message.\n *\n * The glasses send VAD with `status` that can be:\n * - `true` / `false` (boolean)\n * - `\"true\"` / `\"false\"` (string)\n *\n * This method normalizes the value to a clean boolean and caches it\n * for the `isSpeaking` getter.\n */\n private handleVadMessage(data: any): void {\n if (!data) return;\n\n // Normalize status: boolean | \"true\" | \"false\" → boolean\n const rawStatus = data.status;\n let isSpeaking: boolean;\n\n if (typeof rawStatus === \"boolean\") {\n isSpeaking = rawStatus;\n } else if (typeof rawStatus === \"string\") {\n isSpeaking = rawStatus.toLowerCase() === \"true\";\n } else {\n this.deps.logger.warn(\"Unexpected VAD status type:\", typeof rawStatus, rawStatus);\n return;\n }\n\n // Update cached state\n this._isSpeaking = isSpeaking;\n\n // Build the normalized event\n const event: VadEvent = {\n isSpeaking,\n timestamp: Date.now(),\n };\n\n // Dispatch to all registered handlers\n for (const handler of this.vadHandlers) {\n try {\n handler(event);\n } catch (err) {\n this.deps.logger.error(\"VAD handler error:\", err);\n }\n }\n }\n}\n",
32
+ "/**\n * PermissionsManager — App Permission State\n *\n * Tracks which permissions the current app has been granted based on\n * the app's manifest/registration on the developer console. Permissions\n * are populated when the session connects (from CONNECTION_ACK settings)\n * and can be queried synchronously by other managers.\n *\n * This manager is read-only from the app's perspective — permissions are\n * controlled by the platform, not the app.\n *\n * @example\n * ```ts\n * // Check a single permission\n * if (permissions.has(\"camera\")) {\n * // Safe to use camera APIs\n * }\n *\n * // Get all permissions\n * const all = permissions.getAll();\n * console.log(\"Notifications allowed:\", all.notifications);\n *\n * // React to permission changes\n * const cleanup = permissions.onUpdate((perms) => {\n * console.log(\"Permissions updated:\", perms);\n * });\n * ```\n *\n * @module\n */\n\n// ─── Types ──────────────────────────────────────────────────────────────────\n\n/**\n * Permission types supported by the MentraOS platform.\n *\n * These correspond to capabilities and data streams that require\n * explicit opt-in via the developer console app manifest.\n */\nexport type PermissionType = \"location\" | \"microphone\" | \"camera\" | \"notifications\" | \"calendar\";\n\n/**\n * Complete permission record mapping every permission type to its grant status.\n */\nexport type PermissionRecord = Record<PermissionType, boolean>;\n\n/**\n * Dependencies injected by MentraSession.\n */\nexport interface PermissionsManagerDeps {\n /** Logger instance scoped to the session. */\n logger: {\n debug(...args: any[]): void;\n info(...args: any[]): void;\n warn(...args: any[]): void;\n error(...args: any[]): void;\n };\n}\n\n// ─── Constants ──────────────────────────────────────────────────────────────\n\n/** All known permission types, used for iteration and defaults. */\nconst ALL_PERMISSIONS: readonly PermissionType[] = [\n \"location\",\n \"microphone\",\n \"camera\",\n \"notifications\",\n \"calendar\",\n] as const;\n\n/**\n * Returns a fresh default permission record with all permissions denied.\n */\nfunction createDefaultPermissions(): PermissionRecord {\n return {\n location: false,\n microphone: false,\n camera: false,\n notifications: false,\n calendar: false,\n };\n}\n\n// ─── PermissionsManager ─────────────────────────────────────────────────────\n\n/**\n * Manages the permission state for the current app session.\n *\n * Permissions are populated from the CONNECTION_ACK payload when the\n * session is established. Other managers (DeviceManager, PhoneManager)\n * query this manager to gate access to protected streams.\n *\n * The manager emits updates whenever the permission set changes, allowing\n * UI or logic to react to permission grants/revocations in real time.\n */\nexport class PermissionsManager {\n /** Current permission state. */\n private permissions: PermissionRecord;\n\n /** Registered update listeners. */\n private listeners: Set<(permissions: PermissionRecord) => void> = new Set();\n\n /** Logger instance. */\n private logger: PermissionsManagerDeps[\"logger\"];\n\n constructor(deps: PermissionsManagerDeps) {\n this.logger = deps.logger;\n this.permissions = createDefaultPermissions();\n }\n\n // ─── Public API ─────────────────────────────────────────────────────────\n\n /**\n * Check whether the app has been granted a specific permission.\n *\n * @param permission - The permission type to check\n * @returns `true` if the permission is granted, `false` otherwise\n *\n * @example\n * ```ts\n * if (permissions.has(\"microphone\")) {\n * session.events.onTranscription(handler);\n * }\n * ```\n */\n has(permission: PermissionType): boolean {\n return this.permissions[permission] ?? false;\n }\n\n /**\n * Get a snapshot of all permissions as a record.\n *\n * The returned object is a copy — mutations do not affect internal state.\n *\n * @returns A record mapping every {@link PermissionType} to its grant status\n *\n * @example\n * ```ts\n * const perms = permissions.getAll();\n * console.log(\"Camera:\", perms.camera);\n * console.log(\"Location:\", perms.location);\n * ```\n */\n getAll(): PermissionRecord {\n return { ...this.permissions };\n }\n\n /**\n * Subscribe to permission updates.\n *\n * The handler is called whenever the permission set changes (e.g., on\n * CONNECTION_ACK or a mid-session settings update). It receives a\n * snapshot copy of the full permission record.\n *\n * @param handler - Callback invoked with the updated permission record\n * @returns Cleanup function that removes the listener\n *\n * @example\n * ```ts\n * const cleanup = permissions.onUpdate((perms) => {\n * if (!perms.camera) {\n * console.warn(\"Camera permission revoked\");\n * }\n * });\n *\n * // Later: stop listening\n * cleanup();\n * ```\n */\n onUpdate(handler: (permissions: PermissionRecord) => void): () => void {\n this.listeners.add(handler);\n return () => {\n this.listeners.delete(handler);\n };\n }\n\n // ─── Internal (called by MentraSession) ─────────────────────────────────\n\n /**\n * Update permissions from connection settings or a settings update payload.\n *\n * Called internally by MentraSession when:\n * - A CONNECTION_ACK is received with initial settings/permissions\n * - A mid-session settings update includes permission changes\n *\n * Accepts flexible input — extracts permissions from nested structures\n * commonly found in CONNECTION_ACK and settings_update payloads.\n *\n * @param settings - The raw settings object from the cloud message\n * @internal\n */\n updateFromSettings(settings: any): void {\n if (!settings) {\n this.logger.debug(\"PermissionsManager: No settings provided, skipping update\");\n return;\n }\n\n const previous = { ...this.permissions };\n let updated = false;\n\n // Extract permissions from various possible payload shapes:\n // { permissions: { camera: true, ... } }\n // { appPermissions: { camera: true, ... } }\n // { camera: true, microphone: false, ... } (flat)\n const permissionsSource = settings.permissions ?? settings.appPermissions ?? settings;\n\n for (const perm of ALL_PERMISSIONS) {\n if (perm in permissionsSource) {\n const value = Boolean(permissionsSource[perm]);\n if (this.permissions[perm] !== value) {\n this.permissions[perm] = value;\n updated = true;\n }\n }\n }\n\n if (updated) {\n this.logger.info(\n \"PermissionsManager: Permissions updated — \" +\n ALL_PERMISSIONS.map((p) => `${p}=${this.permissions[p]}`).join(\", \"),\n );\n\n // Log individual changes at debug level\n for (const perm of ALL_PERMISSIONS) {\n if (previous[perm] !== this.permissions[perm]) {\n this.logger.debug(`PermissionsManager: ${perm}: ${previous[perm]} → ${this.permissions[perm]}`);\n }\n }\n\n // Notify listeners with a snapshot copy\n const snapshot = this.getAll();\n for (const listener of this.listeners) {\n try {\n listener(snapshot);\n } catch (err) {\n this.logger.error(\n `PermissionsManager: Error in onUpdate listener: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n } else {\n this.logger.debug(\"PermissionsManager: No permission changes detected\");\n }\n }\n}\n",
33
+ "/**\n * PhoneManager — Phone-Scoped Event Management\n *\n * Consolidates all phone-related data streams into a single manager\n * with purpose-built sub-managers:\n *\n * - **notifications** — Phone notification events and dismissals\n * - **calendar** — Calendar event stream\n * - **battery** — Phone battery level tracking\n *\n * Each sub-manager exposes a `hasPermission` getter that delegates to\n * the {@link PermissionsManager}, giving callers a convenient way to\n * check access before subscribing.\n *\n * All handler registrations return a cleanup function. Subscriptions\n * are managed automatically — `addSubscription` is called when the\n * first handler for a stream is registered, and `removeSubscription`\n * when the last handler is removed.\n *\n * @example\n * ```ts\n * // Phone battery\n * console.log(\"Phone battery:\", phone.battery);\n * phone.onBatteryUpdate((e) => {\n * console.log(\"Phone battery:\", e.level, \"%\");\n * });\n *\n * // Notifications (with permission check)\n * if (phone.notifications.hasPermission) {\n * phone.notifications.on((n) => {\n * console.log(`${n.app}: ${n.title}`);\n * });\n * }\n *\n * // Calendar events\n * if (phone.calendar.hasPermission) {\n * phone.calendar.on((event) => {\n * console.log(event.title, event.start, \"→\", event.end);\n * });\n * }\n * ```\n *\n * @module\n */\n\nimport { StreamType } from \"../../types/streams\";\nimport type { PermissionsManager } from \"./PermissionsManager\";\n\n// ─── Event Types ────────────────────────────────────────────────────────────\n\n/**\n * Phone notification event delivered from the companion app.\n */\nexport interface PhoneNotificationEvent {\n /** Unique identifier for this notification. */\n notificationId: string;\n /** Source application package/name. */\n app: string;\n /** Notification title. */\n title: string;\n /** Notification body content. */\n content: string;\n /** Notification priority level. */\n priority: \"low\" | \"normal\" | \"high\";\n}\n\n/**\n * Event emitted when a phone notification is dismissed by the user.\n */\nexport interface NotificationDismissedEvent {\n /** Unique identifier of the dismissed notification. */\n notificationId: string;\n /** Source application package/name. */\n app: string;\n /** Notification title. */\n title: string;\n /** Notification body content. */\n content: string;\n /** Platform-specific notification key. */\n notificationKey: string;\n}\n\n/**\n * Normalised calendar event data.\n *\n * Raw wire fields are normalised for a cleaner developer experience:\n * - `dtStart` → `start`\n * - `dtEnd` → `end`\n * - `timeStamp` → `timestamp`\n */\nexport interface CalendarEventData {\n /** Calendar event identifier. */\n eventId: string;\n /** Event title/summary. */\n title: string;\n /** Normalised start time (ISO string). */\n start: string;\n /** Normalised end time (ISO string). */\n end: string;\n /** Event timezone. */\n timezone: string;\n /** Normalised timestamp of the event update (ISO string). */\n timestamp: string;\n /** Any additional fields from the raw payload. */\n [key: string]: any;\n}\n\n/**\n * Phone battery update event.\n */\nexport interface PhoneBatteryEvent {\n /** Battery level 0–100. */\n level: number;\n /** Whether the phone is currently charging. */\n charging: boolean;\n /** Estimated minutes remaining (if available). */\n timeRemaining?: number;\n}\n\n// ─── Dependency Types ───────────────────────────────────────────────────────\n\n/**\n * Dependencies injected by MentraSession into the PhoneManager.\n */\nexport interface PhoneManagerDeps {\n /** DataStreamRouter — register for stream-type events. */\n router: {\n on(key: string, handler: (streamType: string, data: any, message: any) => void): () => void;\n };\n /** MessageHandlerRegistry — register for top-level message types. */\n messageHandlers: {\n register(type: string, handler: (msg: any) => void): () => void;\n };\n /** Subscribe to a data stream (sent to cloud). */\n addSubscription: (stream: string) => void;\n /** Unsubscribe from a data stream. */\n removeSubscription: (stream: string) => void;\n /** Send an arbitrary message to the cloud. */\n sendMessage: (message: any) => void;\n /** Session-scoped logger. */\n logger: {\n debug(...args: any[]): void;\n info(...args: any[]): void;\n warn(...args: any[]): void;\n error(...args: any[]): void;\n };\n /** Returns the current app's package name. */\n getPackageName: () => string;\n /** Returns the active session ID. */\n getSessionId: () => string;\n /** PermissionsManager for gating protected streams. */\n permissions: PermissionsManager;\n}\n\n// ─── Internal Helpers ───────────────────────────────────────────────────────\n\n/**\n * Normalise raw calendar event data from the wire format.\n *\n * - `dtStart` → `start`\n * - `dtEnd` → `end`\n * - `timeStamp` → `timestamp`\n */\nfunction normaliseCalendarEvent(raw: any): CalendarEventData {\n return {\n ...raw,\n eventId: raw.eventId ?? raw.event_id ?? \"unknown\",\n title: raw.title ?? \"\",\n start: raw.dtStart ?? raw.start ?? \"\",\n end: raw.dtEnd ?? raw.end ?? \"\",\n timezone: raw.timezone ?? \"\",\n timestamp: raw.timeStamp ?? raw.timestamp ?? new Date().toISOString(),\n };\n}\n\n// ─── Stream Handler Bookkeeping ─────────────────────────────────────────────\n\n/**\n * Shared ref-counting logic for stream subscriptions.\n *\n * Tracks handler counts per stream key and calls `addSubscription` /\n * `removeSubscription` at the appropriate lifecycle boundaries.\n * Identical pattern to the `addStreamHandler` private method in DeviceManager,\n * extracted here so all three PhoneManager sub-concerns can share it.\n */\nclass StreamHandlerTracker {\n /** Ref-counted handler totals keyed by stream string. */\n private handlerCounts: Map<string, number> = new Map();\n\n /** All cleanup functions for bulk teardown. */\n private cleanups: Array<() => void> = [];\n\n constructor(\n private router: PhoneManagerDeps[\"router\"],\n private addSubscription: PhoneManagerDeps[\"addSubscription\"],\n private removeSubscription: PhoneManagerDeps[\"removeSubscription\"],\n ) {}\n\n /**\n * Register a handler on the DataStreamRouter for a given stream key,\n * managing subscription lifecycle automatically.\n *\n * - Calls `addSubscription` when the first handler for a key is added.\n * - Calls `removeSubscription` when the last handler for a key is removed.\n *\n * @param streamKey - The stream type string\n * @param handler - The stream handler function\n * @returns Cleanup function that unregisters the handler and manages subscription\n */\n add(streamKey: string, handler: (streamType: string, data: any, message: any) => void): () => void {\n const currentCount = this.handlerCounts.get(streamKey) ?? 0;\n\n // First handler for this stream — subscribe\n if (currentCount === 0) {\n this.addSubscription(streamKey);\n }\n this.handlerCounts.set(streamKey, currentCount + 1);\n\n // Register on the router\n const routerCleanup = this.router.on(streamKey, handler);\n\n let cleaned = false;\n const cleanup = () => {\n if (cleaned) return;\n cleaned = true;\n\n // Remove from router\n routerCleanup();\n\n // Decrement handler count\n const count = this.handlerCounts.get(streamKey) ?? 0;\n const newCount = count - 1;\n if (newCount <= 0) {\n this.handlerCounts.delete(streamKey);\n this.removeSubscription(streamKey);\n } else {\n this.handlerCounts.set(streamKey, newCount);\n }\n };\n\n this.cleanups.push(cleanup);\n return cleanup;\n }\n\n /**\n * Remove all tracked handlers and unsubscribe from all streams.\n * Called during session teardown.\n */\n destroyAll(): void {\n for (const fn of this.cleanups) {\n fn();\n }\n this.cleanups.length = 0;\n this.handlerCounts.clear();\n }\n}\n\n// ─── NotificationSubManager ─────────────────────────────────────────────────\n\n/**\n * Sub-manager for phone notification streams.\n *\n * Provides handlers for incoming notifications and notification dismissals,\n * plus a convenience `hasPermission` check.\n *\n * @example\n * ```ts\n * if (phone.notifications.hasPermission) {\n * phone.notifications.on((n) => {\n * showOnGlasses(`${n.app}: ${n.title}`);\n * });\n *\n * phone.notifications.onDismissed((e) => {\n * removeFromGlasses(e.notificationId);\n * });\n * }\n * ```\n */\nexport class NotificationSubManager {\n private permissions: PermissionsManager;\n private tracker: StreamHandlerTracker;\n private logger: PhoneManagerDeps[\"logger\"];\n\n /** @internal */\n constructor(permissions: PermissionsManager, tracker: StreamHandlerTracker, logger: PhoneManagerDeps[\"logger\"]) {\n this.permissions = permissions;\n this.tracker = tracker;\n this.logger = logger;\n }\n\n /**\n * Whether the app has the `notifications` permission.\n *\n * Reads from the {@link PermissionsManager} — this is a live check,\n * not a cached value.\n */\n get hasPermission(): boolean {\n return this.permissions.has(\"notifications\");\n }\n\n /**\n * Listen for incoming phone notifications.\n *\n * @param handler - Called with {@link PhoneNotificationEvent} for each notification\n * @returns Cleanup function to remove the handler\n *\n * @example\n * ```ts\n * const stop = phone.notifications.on((n) => {\n * console.log(`[${n.priority}] ${n.app}: ${n.title} — ${n.content}`);\n * });\n * ```\n */\n on(handler: (notification: PhoneNotificationEvent) => void): () => void {\n return this.tracker.add(StreamType.PHONE_NOTIFICATION, (_streamType, data) => {\n try {\n handler({\n notificationId: data.notificationId ?? data.notification_id ?? \"unknown\",\n app: data.app ?? \"unknown\",\n title: data.title ?? \"\",\n content: data.content ?? \"\",\n priority: data.priority ?? \"normal\",\n });\n } catch (err) {\n this.logger.error(\n `NotificationSubManager: Error in notification handler: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n });\n }\n\n /**\n * Listen for notification dismissal events.\n *\n * Fired when the user dismisses a notification on their phone.\n *\n * @param handler - Called with {@link NotificationDismissedEvent} for each dismissal\n * @returns Cleanup function to remove the handler\n *\n * @example\n * ```ts\n * phone.notifications.onDismissed((e) => {\n * console.log(\"Dismissed:\", e.notificationId, \"from\", e.app);\n * });\n * ```\n */\n onDismissed(handler: (event: NotificationDismissedEvent) => void): () => void {\n return this.tracker.add(StreamType.PHONE_NOTIFICATION_DISMISSED, (_streamType, data) => {\n try {\n handler({\n notificationId: data.notificationId ?? data.notification_id ?? \"unknown\",\n app: data.app ?? \"unknown\",\n title: data.title ?? \"\",\n content: data.content ?? \"\",\n notificationKey: data.notificationKey ?? data.notification_key ?? \"\",\n });\n } catch (err) {\n this.logger.error(\n `NotificationSubManager: Error in dismissal handler: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n });\n }\n}\n\n// ─── CalendarSubManager ─────────────────────────────────────────────────────\n\n/**\n * Sub-manager for calendar event streams.\n *\n * Normalises raw calendar data (e.g. `dtStart` → `start`) and provides\n * a convenience `hasPermission` check.\n *\n * @example\n * ```ts\n * if (phone.calendar.hasPermission) {\n * phone.calendar.on((event) => {\n * console.log(`${event.title}: ${event.start} → ${event.end}`);\n * });\n * }\n * ```\n */\nexport class CalendarSubManager {\n private permissions: PermissionsManager;\n private tracker: StreamHandlerTracker;\n private logger: PhoneManagerDeps[\"logger\"];\n\n /** @internal */\n constructor(permissions: PermissionsManager, tracker: StreamHandlerTracker, logger: PhoneManagerDeps[\"logger\"]) {\n this.permissions = permissions;\n this.tracker = tracker;\n this.logger = logger;\n }\n\n /**\n * Whether the app has the `calendar` permission.\n *\n * Reads from the {@link PermissionsManager} — this is a live check,\n * not a cached value.\n */\n get hasPermission(): boolean {\n return this.permissions.has(\"calendar\");\n }\n\n /**\n * Listen for calendar events from the phone.\n *\n * Raw fields are normalised:\n * - `dtStart` → `start`\n * - `dtEnd` → `end`\n * - `timeStamp` → `timestamp`\n *\n * @param handler - Called with {@link CalendarEventData} for each event\n * @returns Cleanup function to remove the handler\n *\n * @example\n * ```ts\n * const stop = phone.calendar.on((event) => {\n * console.log(`${event.title} at ${event.start} (${event.timezone})`);\n * });\n * ```\n */\n on(handler: (event: CalendarEventData) => void): () => void {\n return this.tracker.add(StreamType.CALENDAR_EVENT, (_streamType, data) => {\n try {\n handler(normaliseCalendarEvent(data));\n } catch (err) {\n this.logger.error(\n `CalendarSubManager: Error in calendar handler: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n });\n }\n}\n\n// ─── PhoneManager ───────────────────────────────────────────────────────────\n\n/**\n * Manages all phone-related data streams for a MentraSession.\n *\n * Exposes sub-managers for notifications and calendar, plus direct\n * battery-level tracking. Created by MentraSession and exposed as\n * `session.phone`.\n */\nexport class PhoneManager {\n // ─── Sub-Managers ─────────────────────────────────────────────────────\n\n /** Notification stream sub-manager. */\n readonly notifications: NotificationSubManager;\n\n /** Calendar event stream sub-manager. */\n readonly calendar: CalendarSubManager;\n\n // ─── Private ──────────────────────────────────────────────────────────\n\n private deps: PhoneManagerDeps;\n private permissions: PermissionsManager;\n private tracker: StreamHandlerTracker;\n\n /** Cached phone battery level (null until the first update is received). */\n private _battery: number | null = null;\n\n constructor(deps: PhoneManagerDeps) {\n this.deps = deps;\n this.permissions = deps.permissions;\n\n // Shared subscription tracker for all phone streams\n this.tracker = new StreamHandlerTracker(deps.router, deps.addSubscription, deps.removeSubscription);\n\n // Wire up sub-managers\n this.notifications = new NotificationSubManager(this.permissions, this.tracker, deps.logger);\n this.calendar = new CalendarSubManager(this.permissions, this.tracker, deps.logger);\n }\n\n // ─── Battery ──────────────────────────────────────────────────────────\n\n /**\n * The last-known phone battery level (0–100), or `null` if no update\n * has been received yet.\n *\n * This is a synchronous cached read — subscribe via {@link onBatteryUpdate}\n * to react to changes.\n *\n * @example\n * ```ts\n * const level = phone.battery;\n * if (level !== null && level < 20) {\n * console.warn(\"Phone battery low:\", level, \"%\");\n * }\n * ```\n */\n get battery(): number | null {\n return this._battery;\n }\n\n /**\n * Listen for phone battery update events.\n *\n * Also updates the cached {@link battery} value on every event.\n *\n * @param handler - Called with {@link PhoneBatteryEvent} on each update\n * @returns Cleanup function to remove the handler\n *\n * @example\n * ```ts\n * const stop = phone.onBatteryUpdate((e) => {\n * console.log(\"Phone battery:\", e.level, \"%\", e.charging ? \"(charging)\" : \"\");\n * });\n *\n * // Later:\n * stop();\n * ```\n */\n onBatteryUpdate(handler: (event: PhoneBatteryEvent) => void): () => void {\n return this.tracker.add(StreamType.PHONE_BATTERY_UPDATE, (_streamType, data) => {\n // Cache the battery level from every incoming event\n const level = data.level ?? data.batteryLevel ?? data.battery_level;\n if (level !== undefined) {\n this._battery = level;\n }\n\n try {\n handler({\n level: level ?? 0,\n charging: data.charging ?? false,\n timeRemaining: data.timeRemaining ?? data.time_remaining,\n });\n } catch (err) {\n this.deps.logger.error(\n `PhoneManager: Error in battery handler: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n });\n }\n\n // ─── Cleanup ──────────────────────────────────────────────────────────\n\n /**\n * Remove all registered handlers and unsubscribe from all streams.\n *\n * Called by MentraSession during disconnect/teardown.\n * @internal\n */\n destroy(): void {\n this.tracker.destroyAll();\n this._battery = null;\n this.deps.logger.debug(\"PhoneManager: Destroyed.\");\n }\n}\n",
34
+ "/**\n * 🔊 SpeakerManager — Audio Output Control\n *\n * v3 manager that wraps the existing AudioManager output functionality.\n * Handles audio playback, text-to-speech, and real-time audio streaming\n * to the user's glasses speaker.\n *\n * Wire message formats are identical to v2 so the cloud receives the\n * same AUDIO_PLAY_REQUEST, AUDIO_STOP_REQUEST, AUDIO_STREAM_START,\n * AUDIO_STREAM_END, and binary frame protocol.\n *\n * @example\n * ```ts\n * const speaker = new SpeakerManager(deps);\n *\n * // Play a URL\n * const result = await speaker.play({ url: \"https://example.com/sound.mp3\" });\n *\n * // Text-to-speech\n * await speaker.speak(\"Hello, world!\", { volume: 0.8 });\n *\n * // Real-time streaming\n * const stream = await speaker.createStream({ format: \"mp3\" });\n * stream.write(mp3Chunk);\n * await stream.end();\n * ```\n */\n\nimport { AppToCloudMessageType, CloudToAppMessageType } from \"../../types/message-types\";\n\n// ─── Dependencies ────────────────────────────────────────────────────────────\n\n/**\n * Shared dependency bag injected by MentraSession.\n * Keeps managers decoupled from the session implementation.\n */\nexport interface ManagerDeps {\n /** DataStreamRouter — register for DATA_STREAM messages by streamType key. */\n router: {\n on(key: string, handler: (streamType: string, data: any, message: any) => void): () => void;\n };\n /** MessageHandlerRegistry — register for top-level message types. */\n messageHandlers: {\n register(type: string, handler: (msg: any) => void): () => void;\n };\n addSubscription: (stream: string) => void;\n removeSubscription: (stream: string) => void;\n sendMessage: (message: any) => void;\n sendBinary: (data: ArrayBuffer | Uint8Array) => void;\n logger: {\n debug(...args: any[]): void;\n info(...args: any[]): void;\n warn(...args: any[]): void;\n error(...args: any[]): void;\n };\n getPackageName: () => string;\n getSessionId: () => string;\n}\n\n// ─── Public Types ────────────────────────────────────────────────────────────\n\n/** Audio track identifier. Multiple tracks can play simultaneously (mixing). */\nexport type TrackId = 0 | 1 | 2;\n\n/**\n * Options for playing an audio file from a URL.\n */\nexport interface PlayOptions {\n /** URL of the audio file to play */\n url: string;\n /** Volume level 0.0–1.0. Default: 1.0 */\n volume?: number;\n /**\n * Track ID for playback.\n * - 0: speaker (default audio playback)\n * - 1: app_audio (app-specific audio)\n * - 2: tts (text-to-speech audio)\n * Default: 0\n */\n trackId?: TrackId;\n /** Whether starting playback should stop other audio. Default: false */\n stopOtherAudio?: boolean;\n}\n\n/**\n * Result returned when audio playback completes.\n */\nexport interface PlayResult {\n /** Duration of the audio in milliseconds (when available) */\n duration: number;\n}\n\n/**\n * Options for text-to-speech playback.\n */\nexport interface SpeakOptions {\n /** ElevenLabs voice ID (optional — server picks a default) */\n voiceId?: string;\n /** ElevenLabs model ID (optional — defaults to eleven_flash_v2_5) */\n modelId?: string;\n /** Fine-grained voice settings */\n voiceSettings?: {\n stability?: number;\n similarityBoost?: number;\n style?: number;\n speed?: number;\n };\n /** Volume level 0.0–1.0. Default: 1.0 */\n volume?: number;\n /**\n * Track ID for playback. Default: 2 (tts)\n */\n trackId?: TrackId;\n /** Whether starting playback should stop other audio. Default: false */\n stopOtherAudio?: boolean;\n}\n\n/**\n * Options for creating a real-time audio output stream.\n */\nexport interface StreamOptions {\n /**\n * Format of the audio being written.\n * - \"mp3\": MP3 bytes passed through directly (ElevenLabs, OpenAI TTS, etc.)\n * - \"pcm16\": Raw 16-bit signed PCM samples (SDK encodes to MP3 before sending)\n * Default: \"mp3\"\n */\n format?: \"mp3\" | \"pcm16\";\n /** PCM sample rate in Hz (required when format is \"pcm16\"). Default: 24000 */\n sampleRate?: number;\n /** Number of audio channels. Default: 1 (mono) */\n channels?: 1 | 2;\n /** MP3 bitrate in kbps for PCM encoding. Default: 128 */\n bitrate?: number;\n /** Volume level 0.0–1.0. Default: 1.0 */\n volume?: number;\n /**\n * Track ID for playback. Default: 1 (app_audio)\n */\n trackId?: TrackId;\n /** Whether starting the stream should stop other audio. Default: true */\n stopOtherAudio?: boolean;\n}\n\n/** Lifecycle state of an AudioOutputStream */\nexport type AudioOutputStreamState = \"created\" | \"streaming\" | \"ending\" | \"ended\" | \"error\";\n\n/**\n * A real-time audio output stream.\n *\n * Audio data is sent as binary WebSocket frames with the protocol:\n * [36 bytes: streamId UUID as ASCII] [N bytes: audio data]\n *\n * The cloud pipes those bytes into an HTTP chunked response that the\n * phone's media player consumes like internet radio.\n */\nexport interface AudioOutputStream {\n /** Unique stream identifier (UUID) */\n readonly id: string;\n /** Current lifecycle state */\n readonly state: AudioOutputStreamState;\n /** Write audio data to the stream */\n write(chunk: Uint8Array): void;\n /** Gracefully end the stream — phone finishes buffered audio */\n end(): Promise<void>;\n /** Flush/interrupt — discard buffered audio, silence immediately */\n flush(): void;\n /** Register a callback for state changes */\n onStateChange(handler: (state: AudioOutputStreamState) => void): void;\n}\n\n// ─── Constants ───────────────────────────────────────────────────────────────\n\n/** UUID length in ASCII bytes — used as the binary frame header */\nconst STREAM_ID_LENGTH = 36;\n\n/** How long to wait for AUDIO_STREAM_READY from the cloud (ms) */\nconst STREAM_READY_TIMEOUT_MS = 10_000;\n\n/** How long to wait for AUDIO_PLAY_RESPONSE from the cloud (ms) */\nconst PLAY_RESPONSE_TIMEOUT_MS = 60_000;\n\n// ─── AudioOutputStreamImpl ──────────────────────────────────────────────────\n\n/**\n * Internal implementation of the AudioOutputStream interface.\n * Manages the binary frame protocol and lifecycle messages.\n */\nclass AudioOutputStreamImpl implements AudioOutputStream {\n public readonly id: string;\n\n private _state: AudioOutputStreamState = \"created\";\n private readonly deps: ManagerDeps;\n private readonly streamIdBytes: Uint8Array;\n private readonly options: Required<\n Pick<StreamOptions, \"format\" | \"sampleRate\" | \"channels\" | \"bitrate\" | \"volume\" | \"trackId\" | \"stopOtherAudio\">\n >;\n private stateChangeHandlers: Array<(state: AudioOutputStreamState) => void> = [];\n private streamUrl: string | null = null;\n\n constructor(streamId: string, deps: ManagerDeps, opts: StreamOptions = {}) {\n this.id = streamId;\n this.deps = deps;\n\n this.options = {\n format: opts.format ?? \"mp3\",\n sampleRate: opts.sampleRate ?? 24000,\n channels: opts.channels ?? 1,\n bitrate: opts.bitrate ?? 128,\n volume: opts.volume ?? 1.0,\n trackId: opts.trackId ?? 1,\n stopOtherAudio: opts.stopOtherAudio ?? true,\n };\n\n // Pre-encode the streamId as ASCII bytes (reused on every write)\n this.streamIdBytes = new TextEncoder().encode(this.id);\n }\n\n get state(): AudioOutputStreamState {\n return this._state;\n }\n\n /**\n * Initialize the stream — sends AUDIO_STREAM_START and waits for the\n * relay URL from the cloud, then tells the phone to play it.\n * @internal Called by SpeakerManager.createStream()\n */\n async open(): Promise<void> {\n if (this._state !== \"created\") {\n throw new Error(`Cannot open stream in state \"${this._state}\"`);\n }\n\n // Send AUDIO_STREAM_START to the cloud\n const startMessage = {\n type: AppToCloudMessageType.AUDIO_STREAM_START,\n packageName: this.deps.getPackageName(),\n sessionId: this.deps.getSessionId(),\n streamId: this.id,\n contentType: \"audio/mpeg\",\n timestamp: new Date(),\n };\n this.deps.sendMessage(startMessage);\n\n // Wait for AUDIO_STREAM_READY response with the relay URL\n this.streamUrl = await this.waitForReady();\n\n this.setState(\"streaming\");\n\n // Tell the phone to play the relay URL using existing audio play path\n const playMessage = {\n type: AppToCloudMessageType.AUDIO_PLAY_REQUEST,\n packageName: this.deps.getPackageName(),\n sessionId: this.deps.getSessionId(),\n requestId: `stream_${this.id}`,\n audioUrl: this.streamUrl,\n volume: this.options.volume,\n stopOtherAudio: this.options.stopOtherAudio,\n trackId: this.options.trackId,\n timestamp: new Date(),\n };\n this.deps.sendMessage(playMessage);\n\n this.deps.logger.debug(\"Audio output stream opened\", this.id);\n }\n\n write(chunk: Uint8Array): void {\n if (this._state !== \"streaming\") {\n this.deps.logger.debug(`Write called on non-streaming output (state=${this._state}), ignoring`);\n return;\n }\n\n if (chunk.length === 0) return;\n\n this.sendBinaryFrame(chunk);\n }\n\n async end(): Promise<void> {\n if (this._state !== \"streaming\") return;\n this.setState(\"ending\");\n\n // Tell the cloud to close the relay\n const endMessage = {\n type: AppToCloudMessageType.AUDIO_STREAM_END,\n packageName: this.deps.getPackageName(),\n sessionId: this.deps.getSessionId(),\n streamId: this.id,\n timestamp: new Date(),\n };\n this.deps.sendMessage(endMessage);\n\n this.setState(\"ended\");\n this.deps.logger.debug(\"Audio output stream ended\");\n }\n\n flush(): void {\n if (this._state !== \"streaming\") return;\n this.setState(\"ending\");\n\n // End the stream on the cloud side\n const endMessage = {\n type: AppToCloudMessageType.AUDIO_STREAM_END,\n packageName: this.deps.getPackageName(),\n sessionId: this.deps.getSessionId(),\n streamId: this.id,\n timestamp: new Date(),\n };\n this.deps.sendMessage(endMessage);\n\n // Also explicitly stop audio playback on the phone\n const stopMessage = {\n type: AppToCloudMessageType.AUDIO_STOP_REQUEST,\n packageName: this.deps.getPackageName(),\n sessionId: this.deps.getSessionId(),\n trackId: this.options.trackId,\n timestamp: new Date(),\n };\n this.deps.sendMessage(stopMessage);\n\n this.setState(\"ended\");\n this.deps.logger.debug(\"Audio output stream flushed (interrupted)\");\n }\n\n onStateChange(handler: (state: AudioOutputStreamState) => void): void {\n this.stateChangeHandlers.push(handler);\n }\n\n // ─── Internal ────────────────────────────────────────────────────────────\n\n private setState(state: AudioOutputStreamState): void {\n this._state = state;\n for (const handler of this.stateChangeHandlers) {\n try {\n handler(state);\n } catch (err) {\n this.deps.logger.error(\"AudioOutputStream state change handler error:\", err);\n }\n }\n }\n\n /**\n * Send a binary frame over the WebSocket.\n * Frame format: [36 bytes streamId ASCII] [N bytes audio data]\n */\n private sendBinaryFrame(audioData: Uint8Array): void {\n const frame = new Uint8Array(STREAM_ID_LENGTH + audioData.length);\n frame.set(this.streamIdBytes, 0);\n frame.set(audioData, STREAM_ID_LENGTH);\n\n try {\n this.deps.sendBinary(frame);\n } catch (err) {\n this.deps.logger.error(\"Failed to send binary audio frame:\", err);\n this.setState(\"error\");\n }\n }\n\n /**\n * Wait for the cloud to respond with AUDIO_STREAM_READY.\n * Listens on the MessageHandlerRegistry for the top-level response message type.\n */\n private waitForReady(): Promise<string> {\n return new Promise<string>((resolve, reject) => {\n let settled = false;\n\n const timeout = setTimeout(() => {\n if (settled) return;\n settled = true;\n unregister();\n reject(new Error(`Audio stream relay not ready after ${STREAM_READY_TIMEOUT_MS}ms`));\n }, STREAM_READY_TIMEOUT_MS);\n\n // Register on the MessageHandlerRegistry for AUDIO_STREAM_READY.\n // This is a top-level message type, not a DATA_STREAM streamType,\n // so it must go through messageHandlers (not the DataStreamRouter).\n const unregister = this.deps.messageHandlers.register(\n CloudToAppMessageType.AUDIO_STREAM_READY,\n (message: any) => {\n if (message?.streamId === this.id) {\n if (settled) return;\n settled = true;\n clearTimeout(timeout);\n unregister();\n resolve(message.streamUrl);\n }\n },\n );\n });\n }\n}\n\n// ─── SpeakerManager ─────────────────────────────────────────────────────────\n\n/**\n * Controls audio output on the user's glasses speaker.\n *\n * Provides methods for:\n * - 🎵 Playing audio files from URLs\n * - ⏹️ Stopping audio playback\n * - 🗣️ Text-to-speech via ElevenLabs\n * - 🎙️ Real-time audio streaming\n *\n * All messages use the same wire format as v2 AudioManager — the cloud\n * and phone receive identical AUDIO_PLAY_REQUEST / AUDIO_STOP_REQUEST /\n * AUDIO_STREAM_* messages.\n */\nexport class SpeakerManager {\n private readonly deps: ManagerDeps;\n\n /**\n * Map of pending play requests awaiting AUDIO_PLAY_RESPONSE.\n * Key: requestId, Value: promise resolve/reject pair.\n */\n private pendingRequests = new Map<\n string,\n {\n resolve: (result: PlayResult) => void;\n reject: (reason: any) => void;\n timer: ReturnType<typeof setTimeout>;\n }\n >();\n\n /** Currently active output stream (at most one at a time) */\n private activeStream: AudioOutputStreamImpl | null = null;\n\n /** Cached permission state */\n private _hasPermission = true;\n\n /** Cleanup function for the AUDIO_PLAY_RESPONSE message handler registration */\n private responseHandlerCleanup: (() => void) | null = null;\n\n constructor(deps: ManagerDeps) {\n this.deps = deps;\n\n // Register handler for AUDIO_PLAY_RESPONSE messages from the cloud.\n // This is a top-level message type, so it goes through the\n // MessageHandlerRegistry (not the DataStreamRouter).\n this.responseHandlerCleanup = this.deps.messageHandlers.register(\n CloudToAppMessageType.AUDIO_PLAY_RESPONSE,\n (message: any) => {\n this.handleAudioPlayResponse(message);\n },\n );\n }\n\n // ─── Public API ──────────────────────────────────────────────────────────\n\n /**\n * Whether the app has speaker permission.\n * Updated when the cloud sends permission state changes.\n */\n get hasPermission(): boolean {\n return this._hasPermission;\n }\n\n /**\n * 🎵 Play an audio file from a URL on the glasses speaker.\n *\n * If `stopOtherAudio` is false (default), resolves immediately in\n * fire-and-forget mode so multiple tracks can play concurrently.\n * If `stopOtherAudio` is true, waits for the cloud's AUDIO_PLAY_RESPONSE.\n *\n * @param opts - Playback options (url is required)\n * @returns Promise resolving with playback result\n *\n * @example\n * ```ts\n * const result = await speaker.play({\n * url: \"https://example.com/sound.mp3\",\n * volume: 0.8,\n * trackId: 0,\n * });\n * ```\n */\n async play(opts: PlayOptions): Promise<PlayResult> {\n if (!opts.url) {\n throw new Error(\"PlayOptions.url must be provided\");\n }\n\n const requestId = `audio_req_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;\n const volume = opts.volume ?? 1.0;\n const trackId = opts.trackId ?? 0;\n const stopOtherAudio = opts.stopOtherAudio ?? false;\n\n // Build wire message — identical to v2 AudioPlayRequest\n const message = {\n type: AppToCloudMessageType.AUDIO_PLAY_REQUEST,\n packageName: this.deps.getPackageName(),\n sessionId: this.deps.getSessionId(),\n requestId,\n timestamp: new Date(),\n audioUrl: opts.url,\n volume,\n stopOtherAudio,\n trackId,\n };\n\n // Fire-and-forget for concurrent playback (stopOtherAudio=false)\n if (!stopOtherAudio) {\n this.deps.sendMessage(message);\n this.deps.logger.debug(\"Audio playback started in non-blocking mode\", requestId);\n return { duration: 0 };\n }\n\n // Blocking mode — wait for AUDIO_PLAY_RESPONSE\n return new Promise<PlayResult>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.pendingRequests.delete(requestId);\n reject(new Error(\"Audio play request timed out\"));\n this.deps.logger.warn(\"Audio play request timed out\", requestId);\n }, PLAY_RESPONSE_TIMEOUT_MS);\n\n this.pendingRequests.set(requestId, { resolve, reject, timer });\n this.deps.sendMessage(message);\n });\n }\n\n /**\n * ⏹️ Stop audio playback on the glasses.\n *\n * @param trackId - Specific track to stop. If omitted, stops all tracks.\n *\n * @example\n * ```ts\n * // Stop all audio\n * await speaker.stop();\n *\n * // Stop only the TTS track\n * await speaker.stop(2);\n * ```\n */\n async stop(trackId?: TrackId): Promise<void> {\n const message = {\n type: AppToCloudMessageType.AUDIO_STOP_REQUEST,\n packageName: this.deps.getPackageName(),\n sessionId: this.deps.getSessionId(),\n trackId,\n timestamp: new Date(),\n };\n\n this.deps.sendMessage(message);\n\n const trackInfo = trackId !== undefined ? ` (track ${trackId})` : \" (all tracks)\";\n this.deps.logger.info(`Audio stop request sent${trackInfo}`);\n }\n\n /**\n * 🗣️ Convert text to speech and play it on the glasses speaker.\n *\n * Uses the server-side TTS endpoint which proxies to ElevenLabs.\n * The generated audio URL is then played via the standard play() path.\n *\n * @param text - Text to speak (required)\n * @param opts - TTS configuration (optional)\n * @returns Promise resolving with playback result\n *\n * @example\n * ```ts\n * await speaker.speak(\"Hello, world!\");\n *\n * await speaker.speak(\"Good morning!\", {\n * voiceId: \"custom_voice_id\",\n * voiceSettings: { stability: 0.5, speed: 1.2 },\n * volume: 0.8,\n * });\n * ```\n */\n async speak(text: string, opts: SpeakOptions = {}): Promise<PlayResult> {\n if (!text) {\n throw new Error(\"text must be provided\");\n }\n\n // Build TTS query parameters — identical to v2 speak() format\n const queryParams = new URLSearchParams();\n queryParams.append(\"text\", text);\n\n if (opts.voiceId) {\n queryParams.append(\"voice_id\", opts.voiceId);\n }\n\n if (opts.modelId) {\n queryParams.append(\"model_id\", opts.modelId);\n }\n\n if (opts.voiceSettings) {\n // Map camelCase API to the snake_case the TTS endpoint expects\n const settings: Record<string, any> = {};\n if (opts.voiceSettings.stability !== undefined) settings.stability = opts.voiceSettings.stability;\n if (opts.voiceSettings.similarityBoost !== undefined)\n settings.similarity_boost = opts.voiceSettings.similarityBoost;\n if (opts.voiceSettings.style !== undefined) settings.style = opts.voiceSettings.style;\n if (opts.voiceSettings.speed !== undefined) settings.speed = opts.voiceSettings.speed;\n queryParams.append(\"voice_settings\", JSON.stringify(settings));\n }\n\n // The TTS URL is constructed the same way as v2 — the cloud resolves it.\n // v2 used session.getHttpsServerUrl() but in v3 we send the query params\n // as part of the play request and let the cloud construct the final URL.\n const ttsUrl = `/api/tts?${queryParams.toString()}`;\n\n this.deps.logger.debug(\"Generating speech from text\", text);\n\n return this.play({\n url: ttsUrl,\n volume: opts.volume,\n stopOtherAudio: opts.stopOtherAudio ?? false,\n trackId: opts.trackId ?? 2, // Default to track 2 (tts)\n });\n }\n\n /**\n * 🎙️ Create a real-time audio output stream.\n *\n * Opens a streaming relay on the cloud and tells the phone to play it.\n * Write audio chunks to the returned stream and they play on the glasses\n * speaker in real-time — like internet radio.\n *\n * Only one stream can be active at a time. Call `end()` or `flush()` on\n * the current stream before creating a new one.\n *\n * @param opts - Stream configuration\n * @returns The AudioOutputStream (already connected and playing)\n *\n * @example\n * ```ts\n * // MP3 pass-through (most common)\n * const stream = await speaker.createStream({ format: \"mp3\" });\n * elevenLabs.on(\"chunk\", (mp3) => stream.write(mp3));\n * elevenLabs.on(\"end\", () => stream.end());\n *\n * // Interrupt on user speech\n * mic.onVoiceActivity((vad) => {\n * if (vad.isSpeaking) stream.flush();\n * });\n * ```\n */\n async createStream(opts: StreamOptions = {}): Promise<AudioOutputStream> {\n // Enforce one-at-a-time — callers must end/flush the current stream first\n if (this.activeStream && this.activeStream.state === \"streaming\") {\n const err = new Error(\n `AUDIO_STREAM_ALREADY_ACTIVE: Stream ${this.activeStream.id} is still active. ` +\n `Call end() or flush() before creating a new output stream.`,\n ) as Error & { code?: string };\n err.code = \"AUDIO_STREAM_ALREADY_ACTIVE\";\n this.deps.logger.warn(\"Refusing to create a second output stream while one is active\");\n throw err;\n }\n\n // Generate a unique stream ID\n const streamId = crypto.randomUUID();\n\n const stream = new AudioOutputStreamImpl(streamId, this.deps, opts);\n\n // Open the stream (sends AUDIO_STREAM_START, waits for relay URL, tells phone to play)\n await stream.open();\n\n this.activeStream = stream;\n\n // Clean up reference when the stream ends\n stream.onStateChange((state) => {\n if ((state === \"ended\" || state === \"error\") && this.activeStream === stream) {\n this.activeStream = null;\n }\n });\n\n return stream;\n }\n\n // ─── Cleanup ─────────────────────────────────────────────────────────────\n\n /**\n * Cancel all pending requests and end any active stream.\n * Called by MentraSession during disconnect/cleanup.\n * @internal\n */\n destroy(): void {\n // Unregister the AUDIO_PLAY_RESPONSE message handler\n if (this.responseHandlerCleanup) {\n this.responseHandlerCleanup();\n this.responseHandlerCleanup = null;\n }\n\n // Cancel all pending play requests\n for (const [requestId, pending] of this.pendingRequests) {\n clearTimeout(pending.timer);\n pending.reject(new Error(\"SpeakerManager destroyed\"));\n this.deps.logger.debug(\"Audio request cancelled during cleanup\", requestId);\n }\n this.pendingRequests.clear();\n\n // End any active output stream\n if (this.activeStream && this.activeStream.state === \"streaming\") {\n this.activeStream.end().catch(() => {});\n this.activeStream = null;\n }\n }\n\n // ─── Internal ────────────────────────────────────────────────────────────\n\n /**\n * Handle AUDIO_PLAY_RESPONSE from the cloud.\n * Resolves or rejects the corresponding pending play() promise.\n */\n private handleAudioPlayResponse(response: any): void {\n const requestId: string | undefined = response?.requestId;\n if (!requestId) return;\n\n const pending = this.pendingRequests.get(requestId);\n if (!pending) {\n this.deps.logger.debug(\"Received audio play response for unknown request\", requestId);\n return;\n }\n\n clearTimeout(pending.timer);\n this.pendingRequests.delete(requestId);\n\n if (response.success) {\n pending.resolve({\n duration: response.duration ?? 0,\n });\n this.deps.logger.info(\"Audio play response received\", requestId, \"duration:\", response.duration);\n } else {\n pending.reject(new Error(response.error || \"Audio playback failed\"));\n this.deps.logger.warn(\"Audio play failed\", requestId, response.error);\n }\n }\n}\n",
35
+ "/**\n * StorageManager — v3 SDK Key-Value Storage API\n *\n * Wraps the existing SimpleStorage patterns from v2 with a cleaner,\n * composable API. Provides localStorage-like semantics with cloud\n * synchronisation via HTTP REST endpoints.\n *\n * **Mental Model:** Local cache (RAM) is the source of truth for reads.\n * Writes are applied to RAM immediately and batched/debounced to the\n * server for persistence. On disconnect, pending writes are flushed.\n *\n * Data is isolated by userId and packageName on the server side.\n * The REST endpoints live on the SDK's own HTTP server (same origin\n * as the WebSocket connection, minus the `/app-ws` path).\n *\n * REST API:\n * - `GET /api/sdk/simple-storage/:userId` → fetch all data\n * - `PUT /api/sdk/simple-storage/:userId` → batch upsert `{ data: { key: value } }`\n * - `DELETE /api/sdk/simple-storage/:userId/:key` → delete single key\n * - `DELETE /api/sdk/simple-storage/:userId` → clear all data\n *\n * @module\n */\n\n// ─── Public Types ───────────────────────────────────────────────────────────\n\n/**\n * Dependencies injected by MentraSession.\n *\n * Structural type — no concrete imports so the manager stays unit-testable\n * with plain stubs.\n */\nexport interface StorageManagerDeps {\n /** DataStreamRouter — not used by StorageManager but part of the shared shape. */\n router: {\n on(key: string, handler: (streamType: string, data: any, message: any) => void): () => void;\n };\n /** MessageHandlerRegistry — not used by StorageManager but part of the shared shape. */\n messageHandlers: {\n register(type: string, handler: (msg: any) => void): () => void;\n };\n /** Add a subscription string (not used by StorageManager). */\n addSubscription: (stream: string) => void;\n /** Remove a subscription string (not used by StorageManager). */\n removeSubscription: (stream: string) => void;\n /** Send an arbitrary JSON message over the WebSocket. */\n sendMessage: (message: any) => void;\n /** Structured logger. */\n logger: {\n debug(...args: any[]): void;\n info(...args: any[]): void;\n warn(...args: any[]): void;\n error(...args: any[]): void;\n };\n /** Package name — used for auth headers. */\n getPackageName: () => string;\n /** Current session ID. */\n getSessionId: () => string;\n /** Server URL for HTTP API calls (WebSocket URL or HTTP base). */\n getServerUrl?: () => string | null;\n}\n\n/**\n * Configuration options for StorageManager.\n */\nexport interface StorageManagerConfig {\n /** User ID for storage isolation. */\n userId: string;\n /** API key for authentication. */\n apiKey?: string;\n}\n\n// ─── Internal Types ─────────────────────────────────────────────────────────\n\n/** Shape of the GET response from the storage API. */\ninterface StorageResponse {\n success: boolean;\n data?: Record<string, string>;\n}\n\n// ─── Constants ──────────────────────────────────────────────────────────────\n\n/** Maximum size for a single value in bytes/characters. */\nconst MAX_VALUE_SIZE = 100_000; // 100 KB\n\n/** Debounce idle time before flushing pending writes to the server. */\nconst DEBOUNCE_MS = 3_000; // 3 seconds\n\n/** Maximum wait time before forcing a flush, regardless of activity. */\nconst MAX_WAIT_MS = 10_000; // 10 seconds\n\n// ─── Manager ────────────────────────────────────────────────────────────────\n\n/**\n * Key-value storage with local caching and debounced cloud sync.\n *\n * Provides a familiar `get`/`set`/`delete`/`clear` interface backed by\n * an in-memory cache with automatic persistence to the MentraOS cloud.\n * Writes are batched and debounced to minimise network traffic.\n *\n * @example\n * ```ts\n * const session = await mentra.connect();\n *\n * // Set a value\n * await session.storage.set(\"username\", \"alice\");\n *\n * // Get a value\n * const name = await session.storage.get(\"username\");\n * console.log(name); // \"alice\"\n *\n * // Check existence\n * const exists = await session.storage.has(\"username\");\n *\n * // Delete a key\n * await session.storage.delete(\"username\");\n *\n * // Flush pending writes immediately\n * await session.storage.flush();\n * ```\n */\nexport class StorageManager {\n private readonly deps: StorageManagerDeps;\n private readonly userId: string;\n private readonly apiKey: string;\n\n /** Local cache — `null` means \"not yet loaded from server\". */\n private cache: Record<string, any> | null = null;\n\n /** Base URL for HTTP API calls. */\n private readonly baseUrl: string;\n\n // ─── Debounce / Batching State ──────────────────────────────────────────\n\n /** Pending writes waiting to be flushed. */\n private pendingWrites = new Map<string, any>();\n\n /** Timer for the idle debounce window. */\n private debounceTimer: ReturnType<typeof setTimeout> | undefined;\n\n /** Timer for the maximum wait window. */\n private maxWaitTimer: ReturnType<typeof setTimeout> | undefined;\n\n /** Timestamp of the first write in the current batch. */\n private firstWriteTime: number | undefined;\n\n constructor(deps: StorageManagerDeps, config: StorageManagerConfig) {\n this.deps = deps;\n this.userId = config.userId;\n this.apiKey = config.apiKey ?? \"unknown-api-key\";\n this.baseUrl = this.resolveBaseUrl();\n }\n\n // ─── Public API ─────────────────────────────────────────────────────────\n\n /**\n * Get a value by key.\n *\n * Reads from the local cache. If the cache has not been populated yet,\n * it is fetched from the server first.\n *\n * @param key - The storage key to retrieve.\n * @returns The stored value, or `undefined` if the key does not exist.\n *\n * @example\n * ```ts\n * const value = await session.storage.get(\"theme\");\n * ```\n */\n async get(key: string): Promise<any> {\n try {\n await this.ensureCacheLoaded();\n return this.cache?.[key];\n } catch (error) {\n this.deps.logger.error(\"[StorageManager] Error getting item:\", error);\n return undefined;\n }\n }\n\n /**\n * Set a value for a key.\n *\n * The local cache is updated immediately. The write is batched and\n * debounced — it will be persisted to the server after 3 seconds of\n * idle time or 10 seconds maximum, whichever comes first.\n *\n * @param key - The storage key.\n * @param value - The value to store. Will be serialised as JSON on the server.\n * @throws If the serialised value exceeds the 100 KB size limit.\n *\n * @example\n * ```ts\n * await session.storage.set(\"score\", \"42\");\n * ```\n */\n async set(key: string, value: any): Promise<void> {\n const serialised = typeof value === \"string\" ? value : JSON.stringify(value);\n\n if (serialised.length > MAX_VALUE_SIZE) {\n throw new Error(\n `StorageManager value exceeds 100KB limit (${serialised.length} chars). ` +\n `For large files, use your own S3 bucket storage.`,\n );\n }\n\n await this.ensureCacheLoaded();\n\n // Optimistic update — RAM is source of truth\n if (this.cache) {\n this.cache[key] = serialised;\n }\n\n // Add to pending batch\n this.pendingWrites.set(key, serialised);\n\n // Schedule debounced flush\n this.scheduleFlush();\n }\n\n /**\n * Delete a single key.\n *\n * Removes the key from the local cache immediately and sends a DELETE\n * request to the server. Unlike `set()`, deletes are flushed immediately\n * for consistency.\n *\n * @param key - The storage key to remove.\n *\n * @example\n * ```ts\n * await session.storage.delete(\"old-key\");\n * ```\n */\n async delete(key: string): Promise<void> {\n try {\n await this.ensureCacheLoaded();\n\n // Remove from local cache\n if (this.cache) {\n delete this.cache[key];\n }\n\n // Remove from pending writes if queued\n this.pendingWrites.delete(key);\n\n // Flush delete immediately to server\n const response = await fetch(\n `${this.baseUrl}/api/sdk/simple-storage/${encodeURIComponent(this.userId)}/${encodeURIComponent(key)}`,\n {\n method: \"DELETE\",\n headers: this.getAuthHeaders(),\n },\n );\n\n if (!response.ok) {\n const errorText = await response.text();\n this.deps.logger.error(\"[StorageManager] Failed to delete key from server:\", errorText);\n }\n } catch (error) {\n this.deps.logger.error(\"[StorageManager] Error deleting item:\", error);\n }\n }\n\n /**\n * Clear all stored data.\n *\n * Empties the local cache and sends a DELETE request to remove all\n * data from the server.\n *\n * @example\n * ```ts\n * await session.storage.clear();\n * ```\n */\n async clear(): Promise<void> {\n try {\n // Clear local state\n this.cache = {};\n this.pendingWrites.clear();\n this.clearTimers();\n\n // Clear on server\n const response = await fetch(`${this.baseUrl}/api/sdk/simple-storage/${encodeURIComponent(this.userId)}`, {\n method: \"DELETE\",\n headers: this.getAuthHeaders(),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n this.deps.logger.error(\"[StorageManager] Failed to clear storage on server:\", errorText);\n }\n } catch (error) {\n this.deps.logger.error(\"[StorageManager] Error clearing storage:\", error);\n }\n }\n\n /**\n * Get all storage keys.\n *\n * @returns An array of all keys currently in storage.\n *\n * @example\n * ```ts\n * const allKeys = await session.storage.keys();\n * console.log(\"Stored keys:\", allKeys);\n * ```\n */\n async keys(): Promise<string[]> {\n try {\n await this.ensureCacheLoaded();\n return Object.keys(this.cache || {});\n } catch (error) {\n this.deps.logger.error(\"[StorageManager] Error getting keys:\", error);\n return [];\n }\n }\n\n /**\n * Check whether a key exists in storage.\n *\n * @param key - The storage key to check.\n * @returns `true` if the key exists.\n *\n * @example\n * ```ts\n * if (await session.storage.has(\"user-prefs\")) {\n * // load prefs\n * }\n * ```\n */\n async has(key: string): Promise<boolean> {\n try {\n await this.ensureCacheLoaded();\n return key in (this.cache || {});\n } catch (error) {\n this.deps.logger.error(\"[StorageManager] Error checking key:\", error);\n return false;\n }\n }\n\n /**\n * Get a shallow copy of all stored key-value pairs.\n *\n * @returns A record of all stored data.\n *\n * @example\n * ```ts\n * const allData = await session.storage.getAll();\n * console.log(allData);\n * ```\n */\n async getAll(): Promise<Record<string, any>> {\n try {\n await this.ensureCacheLoaded();\n return { ...(this.cache || {}) };\n } catch (error) {\n this.deps.logger.error(\"[StorageManager] Error getting all data:\", error);\n return {};\n }\n }\n\n /**\n * Set multiple key-value pairs at once.\n *\n * All values are validated for size before any are applied. The local\n * cache is updated immediately and writes are batched for persistence.\n *\n * @param data - A record of key-value pairs to set.\n * @throws If any value exceeds the 100 KB size limit.\n *\n * @example\n * ```ts\n * await session.storage.setMultiple({\n * theme: \"dark\",\n * language: \"en\",\n * fontSize: \"14\",\n * });\n * ```\n */\n async setMultiple(data: Record<string, any>): Promise<void> {\n // Validate all values first\n for (const [key, value] of Object.entries(data)) {\n const serialised = typeof value === \"string\" ? value : JSON.stringify(value);\n if (serialised.length > MAX_VALUE_SIZE) {\n throw new Error(`StorageManager value for key \"${key}\" exceeds 100KB limit (${serialised.length} chars).`);\n }\n }\n\n await this.ensureCacheLoaded();\n\n // Update cache and pending writes\n for (const [key, value] of Object.entries(data)) {\n const serialised = typeof value === \"string\" ? value : JSON.stringify(value);\n if (this.cache) {\n this.cache[key] = serialised;\n }\n this.pendingWrites.set(key, serialised);\n }\n\n // Schedule debounced flush\n this.scheduleFlush();\n }\n\n /**\n * Flush all pending writes to the server immediately.\n *\n * This is called automatically by the debounce/max-wait timers, but can\n * also be called explicitly (e.g., before disconnect). If there are no\n * pending writes, this is a no-op.\n *\n * @throws If the server returns an error (413 for size limit, 429 for rate limit).\n *\n * @example\n * ```ts\n * await session.storage.flush();\n * ```\n */\n async flush(): Promise<void> {\n if (this.pendingWrites.size === 0) return;\n\n // Clear all timers\n this.clearTimers();\n\n // Snapshot and clear pending writes\n const batch = Object.fromEntries(this.pendingWrites);\n this.pendingWrites.clear();\n\n try {\n const response = await fetch(`${this.baseUrl}/api/sdk/simple-storage/${encodeURIComponent(this.userId)}`, {\n method: \"PUT\",\n headers: this.getAuthHeaders(),\n body: JSON.stringify({ data: batch }),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n this.deps.logger.error(\"[StorageManager] Failed to persist writes:\", errorText);\n\n if (response.status === 413) {\n throw new Error(\"StorageManager total size exceeds 1MB limit. Delete unused keys.\");\n }\n if (response.status === 429) {\n throw new Error(\"StorageManager rate limit exceeded.\");\n }\n throw new Error(`StorageManager flush failed: ${errorText}`);\n }\n } catch (error) {\n this.deps.logger.error(\"[StorageManager] Error flushing writes:\", error);\n throw error;\n }\n }\n\n // ─── Cleanup ────────────────────────────────────────────────────────────\n\n /**\n * Flush pending writes and clean up all resources.\n *\n * Called by MentraSession during disconnect/cleanup.\n * @internal\n */\n async destroy(): Promise<void> {\n // Attempt to flush any pending writes before shutting down\n try {\n await this.flush();\n } catch (error) {\n this.deps.logger.error(\"[StorageManager] Error flushing on destroy:\", error);\n }\n\n this.clearTimers();\n this.pendingWrites.clear();\n this.cache = null;\n\n this.deps.logger.debug(\"[StorageManager] Destroyed.\");\n }\n\n // ─── Internal: Cache Loading ────────────────────────────────────────────\n\n /**\n * Ensure the local cache is populated from the server.\n * Only fetches once — subsequent calls are no-ops.\n */\n private async ensureCacheLoaded(): Promise<void> {\n if (this.cache !== null) return;\n await this.fetchFromServer();\n }\n\n /**\n * Fetch all stored data from the server and populate the local cache.\n */\n private async fetchFromServer(): Promise<void> {\n try {\n const response = await fetch(`${this.baseUrl}/api/sdk/simple-storage/${encodeURIComponent(this.userId)}`, {\n headers: this.getAuthHeaders(),\n });\n\n if (response.ok) {\n const result = (await response.json()) as StorageResponse;\n if (result.success && result.data) {\n this.cache = result.data;\n } else {\n this.cache = {};\n }\n } else {\n this.deps.logger.error(\"[StorageManager] Failed to fetch storage from server:\", await response.text());\n this.cache = {};\n }\n } catch (error) {\n this.deps.logger.error(\"[StorageManager] Error fetching storage from server:\", error);\n this.cache = {};\n }\n }\n\n // ─── Internal: Debounce / Batching ──────────────────────────────────────\n\n /**\n * Schedule a debounced flush of pending writes.\n *\n * Uses a two-timer strategy:\n * 1. **Idle timer** — fires after {@link DEBOUNCE_MS} of inactivity (reset on each write).\n * 2. **Max wait timer** — fires after {@link MAX_WAIT_MS} from the first write in the batch,\n * ensuring writes are never delayed indefinitely during continuous activity.\n */\n private scheduleFlush(): void {\n // Track first write time for max-wait calculation\n if (!this.firstWriteTime) {\n this.firstWriteTime = Date.now();\n }\n\n // Clear existing idle debounce timer\n if (this.debounceTimer !== undefined) {\n clearTimeout(this.debounceTimer);\n this.debounceTimer = undefined;\n }\n\n // Calculate remaining time until max-wait deadline\n const elapsedMs = Date.now() - this.firstWriteTime;\n const remainingMaxWaitMs = MAX_WAIT_MS - elapsedMs;\n\n // If max wait already exceeded, flush immediately\n if (remainingMaxWaitMs <= 0) {\n this.flush().catch((err) => {\n this.deps.logger.error(\"[StorageManager] Error in scheduled flush:\", err);\n });\n return;\n }\n\n // Set idle debounce timer (capped at remaining max-wait)\n this.debounceTimer = setTimeout(\n () => {\n this.flush().catch((err) => {\n this.deps.logger.error(\"[StorageManager] Error in debounced flush:\", err);\n });\n },\n Math.min(DEBOUNCE_MS, remainingMaxWaitMs),\n );\n\n // Set max-wait timer if not already running\n if (this.maxWaitTimer === undefined && remainingMaxWaitMs > 0) {\n this.maxWaitTimer = setTimeout(() => {\n this.flush().catch((err) => {\n this.deps.logger.error(\"[StorageManager] Error in max-wait flush:\", err);\n });\n }, remainingMaxWaitMs);\n }\n }\n\n /**\n * Clear all flush timers and reset batch tracking state.\n */\n private clearTimers(): void {\n if (this.debounceTimer !== undefined) {\n clearTimeout(this.debounceTimer);\n this.debounceTimer = undefined;\n }\n if (this.maxWaitTimer !== undefined) {\n clearTimeout(this.maxWaitTimer);\n this.maxWaitTimer = undefined;\n }\n this.firstWriteTime = undefined;\n }\n\n // ─── Internal: HTTP Helpers ─────────────────────────────────────────────\n\n /**\n * Resolve the base URL for HTTP API calls.\n *\n * Converts the WebSocket URL (e.g., `wss://host/app-ws`) to an HTTP URL\n * (e.g., `https://host`). Falls back to `http://localhost:8002` if no\n * server URL is available.\n */\n private resolveBaseUrl(): string {\n const serverUrl = this.deps.getServerUrl?.() ?? null;\n if (!serverUrl) return \"http://localhost:8002\";\n return serverUrl.replace(/\\/app-ws$/, \"\").replace(/^ws/, \"http\");\n }\n\n /**\n * Generate auth headers for API requests.\n *\n * Uses the `packageName:apiKey` format expected by the SDK server's\n * auth middleware.\n */\n private getAuthHeaders(): Record<string, string> {\n return {\n \"Authorization\": `Bearer ${this.deps.getPackageName()}:${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n };\n }\n}\n",
36
+ "/**\n * ⏰ TimeUtils — Stateless Timezone Utilities\n *\n * Pure utility class for timezone-aware date operations. Uses the built-in\n * `Intl.DateTimeFormat` API for formatting — no external dependencies.\n *\n * This class has **no transport layer**, no subscriptions, and no wire\n * messages. It is a convenience wrapper for apps that need to display\n * or reason about times in the user's local timezone (which may differ\n * from the server's timezone).\n *\n * @example\n * ```ts\n * const time = new TimeUtils(\"America/New_York\");\n *\n * // Current time in the user's timezone\n * const now = time.now();\n *\n * // Format a date for display\n * const formatted = time.format(now, { hour: \"numeric\", minute: \"2-digit\" });\n * // => \"3:45 PM\"\n *\n * // Convert a UTC date to the user's local timezone\n * const local = time.toLocal(new Date(\"2024-01-15T20:00:00Z\"));\n *\n * // Change timezone at runtime (e.g., user travelled)\n * time.setTimezone(\"Europe/London\");\n * ```\n *\n * @module\n */\n\n// ─── TimeUtils ──────────────────────────────────────────────────────────────\n\n/**\n * Timezone-aware date utility class.\n *\n * Wraps the `Intl.DateTimeFormat` API to provide convenient methods for\n * creating, converting, and formatting dates in a specific timezone.\n *\n * The timezone can be changed at runtime via {@link setTimezone}, making\n * this class suitable for long-lived sessions where the user may travel\n * across timezone boundaries.\n */\nexport class TimeUtils {\n /**\n * The current IANA timezone identifier (e.g., `\"America/New_York\"`,\n * `\"Europe/London\"`, `\"Asia/Tokyo\"`).\n */\n private _zone: string;\n\n /**\n * Create a new TimeUtils instance.\n *\n * @param timezone - IANA timezone identifier. Must be a valid timezone\n * string recognised by `Intl.DateTimeFormat` (e.g., `\"America/New_York\"`,\n * `\"UTC\"`, `\"Asia/Tokyo\"`).\n *\n * @throws {RangeError} If the provided timezone string is not a valid\n * IANA timezone identifier.\n *\n * @example\n * ```ts\n * const time = new TimeUtils(\"America/Los_Angeles\");\n * ```\n */\n constructor(timezone: string) {\n // Validate the timezone by attempting to create a formatter with it.\n // Intl.DateTimeFormat will throw a RangeError for invalid timezones.\n TimeUtils.validateTimezone(timezone);\n this._zone = timezone;\n }\n\n // ─── Accessors ──────────────────────────────────────────────────────────\n\n /**\n * The current IANA timezone identifier.\n *\n * @example\n * ```ts\n * console.log(time.zone); // \"America/New_York\"\n * ```\n */\n get zone(): string {\n return this._zone;\n }\n\n // ─── Public API ─────────────────────────────────────────────────────────\n\n /**\n * Get the current date/time.\n *\n * Returns a standard `Date` object representing the current instant.\n * The `Date` itself is always UTC internally — use {@link format} or\n * {@link toLocal} to interpret it in the configured timezone.\n *\n * @returns A `Date` representing the current moment.\n *\n * @example\n * ```ts\n * const now = time.now();\n * console.log(time.format(now)); // formatted in the configured timezone\n * ```\n */\n now(): Date {\n return new Date();\n }\n\n /**\n * Convert a `Date` to a new `Date` whose UTC fields represent the\n * wall-clock time in the configured timezone.\n *\n * This is useful when you need to extract hours/minutes/seconds that\n * correspond to the local timezone without using `Intl` formatting.\n *\n * **Note:** The returned `Date` is a synthetic object — its\n * `getUTCHours()` etc. return the *local* values, but calling\n * `toISOString()` on it will produce a misleading string. Prefer\n * {@link format} for display purposes.\n *\n * @param date - The date to convert. Defaults to `new Date()` (now).\n * @returns A new `Date` whose UTC methods return local-timezone values.\n *\n * @example\n * ```ts\n * const utcDate = new Date(\"2024-01-15T20:00:00Z\");\n * const local = time.toLocal(utcDate);\n * console.log(local.getUTCHours()); // 15 if timezone is \"America/New_York\" (EST = UTC-5)\n * ```\n */\n toLocal(date: Date = new Date()): Date {\n // Use Intl to get the timezone offset, then shift the date.\n // formatToParts gives us the local components — we reconstruct a Date from them.\n const parts = new Intl.DateTimeFormat(\"en-US\", {\n timeZone: this._zone,\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n hour12: false,\n }).formatToParts(date);\n\n const get = (type: string): string => {\n const part = parts.find((p) => p.type === type);\n return part?.value ?? \"0\";\n };\n\n const year = parseInt(get(\"year\"), 10);\n const month = parseInt(get(\"month\"), 10) - 1; // JS months are 0-indexed\n const day = parseInt(get(\"day\"), 10);\n let hour = parseInt(get(\"hour\"), 10);\n const minute = parseInt(get(\"minute\"), 10);\n const second = parseInt(get(\"second\"), 10);\n\n // hour12: false can yield \"24\" for midnight in some locales — normalise\n if (hour === 24) hour = 0;\n\n // Construct a Date using UTC setters so the UTC fields hold local values\n return new Date(Date.UTC(year, month, day, hour, minute, second));\n }\n\n /**\n * Format a `Date` for display in the configured timezone.\n *\n * Delegates to `Intl.DateTimeFormat` with the configured timezone\n * injected automatically. Any valid `Intl.DateTimeFormatOptions` can\n * be passed through.\n *\n * If no options are provided, a sensible default is used:\n * `\"1/15/2024, 3:45:00 PM\"` (locale-dependent).\n *\n * @param date - The date to format.\n * @param opts - Optional `Intl.DateTimeFormatOptions` to control output.\n * @returns The formatted date string.\n *\n * @example\n * ```ts\n * // Default format\n * time.format(new Date());\n * // => \"1/15/2024, 3:45:00 PM\"\n *\n * // Custom format — time only\n * time.format(new Date(), { hour: \"numeric\", minute: \"2-digit\" });\n * // => \"3:45 PM\"\n *\n * // Custom format — full date\n * time.format(new Date(), {\n * weekday: \"long\",\n * year: \"numeric\",\n * month: \"long\",\n * day: \"numeric\",\n * });\n * // => \"Monday, January 15, 2024\"\n *\n * // With a specific locale\n * time.format(new Date(), { hour: \"numeric\", minute: \"2-digit\" });\n * ```\n */\n format(date: Date, opts?: Intl.DateTimeFormatOptions): string {\n const mergedOpts: Intl.DateTimeFormatOptions = {\n ...opts,\n timeZone: this._zone,\n };\n\n return new Intl.DateTimeFormat(undefined, mergedOpts).format(date);\n }\n\n /**\n * Change the timezone at runtime.\n *\n * Validates the new timezone before applying it. If the timezone is\n * invalid, a `RangeError` is thrown and the previous timezone is retained.\n *\n * @param tz - New IANA timezone identifier.\n * @throws {RangeError} If the timezone string is not valid.\n *\n * @example\n * ```ts\n * time.setTimezone(\"Europe/London\");\n * console.log(time.zone); // \"Europe/London\"\n * ```\n */\n setTimezone(tz: string): void {\n TimeUtils.validateTimezone(tz);\n this._zone = tz;\n }\n\n // ─── Internal ───────────────────────────────────────────────────────────\n\n /**\n * Validate that a timezone string is recognised by the runtime's\n * `Intl.DateTimeFormat` implementation.\n *\n * @param tz - The timezone string to validate.\n * @throws {RangeError} If the timezone is not valid.\n */\n private static validateTimezone(tz: string): void {\n // Intl.DateTimeFormat throws RangeError for unrecognised timeZone values.\n // We intentionally let that error propagate with a clear message.\n try {\n Intl.DateTimeFormat(undefined, { timeZone: tz });\n } catch {\n throw new RangeError(\n `Invalid timezone: \"${tz}\". ` +\n `Must be a valid IANA timezone identifier (e.g., \"America/New_York\", \"UTC\", \"Asia/Tokyo\").`,\n );\n }\n }\n}\n",
37
+ "/**\n * TranscriptionManager — v3 SDK Transcription API\n *\n * Replaces the old `session.events.onTranscription*()` methods with a cleaner,\n * composable API that supports multiple simultaneous subscriptions.\n *\n * @example\n * ```ts\n * const session = await mentra.connect();\n *\n * // Subscribe to all transcription (auto-detect language)\n * const stopAll = session.transcription.on((evt) => {\n * console.log(`[${evt.language}] ${evt.text}`);\n * });\n *\n * // Also subscribe to a specific language — independent of the above\n * const stopEn = session.transcription.forLanguage(\"en\", (evt) => {\n * console.log(`English: ${evt.text}`);\n * });\n *\n * // Multiple languages in one call\n * const stopMulti = session.transcription.forLanguage([\"ja\", \"es\"], (evt) => {\n * console.log(`${evt.language}: ${evt.text}`);\n * });\n *\n * // Configure hints / vocabulary / diarization\n * session.transcription.configure({\n * languageHints: [\"en\", \"ja\"],\n * vocabulary: [\"MentraOS\", \"HIPAA\"],\n * diarization: true,\n * });\n *\n * // Cleanup individual subscriptions\n * stopEn();\n *\n * // Or tear down everything\n * session.transcription.stop();\n * ```\n *\n * @module\n */\n\nimport { StreamType } from \"../../types\";\n\n// ─── Public Types ───────────────────────────────────────────────────────────\n\n/**\n * Configuration options that influence transcription behaviour on the cloud.\n *\n * Passed to {@link TranscriptionManager.configure}. Applies globally to all\n * active subscriptions managed by this instance.\n */\nexport interface TranscriptionConfig {\n /** ISO 639-1 language hints to improve detection accuracy (e.g. `[\"en\", \"ja\", \"es\"]`). */\n languageHints?: string[];\n /** Custom vocabulary / boosted terms (e.g. `[\"MentraOS\", \"HIPAA\"]`). */\n vocabulary?: string[];\n /** Enable speaker diarisation. Defaults to `true`. */\n diarization?: boolean;\n}\n\n/**\n * Normalised transcription event delivered to subscriber callbacks.\n *\n * This is the *public* shape — it is mapped from the raw cloud\n * `TranscriptionData` message inside the manager so consumers never\n * need to think about wire-level details.\n */\nexport interface TranscriptionEvent {\n /** The transcribed text. */\n text: string;\n /** `true` when the cloud considers this utterance segment finalised. */\n isFinal: boolean;\n /** ISO 639-1 detected language code (e.g. `\"en\"`, `\"ja\"`). */\n language: string;\n /** Speaker identifier when diarisation is enabled. */\n speakerId?: string;\n /** Stable identifier for a contiguous utterance. Interim and final events for the same utterance share this ID. */\n utteranceId?: string;\n /** Recognition confidence in the range `[0, 1]`. */\n confidence?: number;\n /** Start time of the utterance segment in milliseconds. */\n startTime: number;\n /** End time of the utterance segment in milliseconds. */\n endTime: number;\n /** Audio duration in milliseconds. */\n duration?: number;\n /** Provider-specific metadata (token-level details, etc.). */\n metadata?: any;\n}\n\n/** Callback signature for transcription subscribers. */\nexport type TranscriptionHandler = (data: TranscriptionEvent) => void;\n\n// ─── Internal Types ─────────────────────────────────────────────────────────\n\n/**\n * Dependencies injected by MentraSession.\n *\n * This is intentionally a *structural* type — we don't import the concrete\n * `DataStreamRouter` class so that the manager remains unit-testable with\n * plain stubs.\n */\nexport interface TranscriptionManagerDeps {\n /** Register for DATA_STREAM messages by streamType key (exact or prefix). Returns a cleanup function. */\n router: {\n on(key: string, handler: (streamType: string, data: any, message: any) => void): () => void;\n };\n /** Add a subscription string (triggers SUBSCRIPTION_UPDATE to cloud). */\n addSubscription: (stream: string) => void;\n /** Remove a subscription string. */\n removeSubscription: (stream: string) => void;\n /** Send an arbitrary JSON message to the cloud. */\n sendMessage: (message: any) => void;\n /** Structured logger. */\n logger: {\n debug(...args: any[]): void;\n info(...args: any[]): void;\n warn(...args: any[]): void;\n error(...args: any[]): void;\n };\n}\n\n/**\n * Internal bookkeeping for a single `on()` / `forLanguage()` registration.\n *\n * Each call to a public subscription method produces one `Registration` per\n * stream key it subscribes to, enabling independent cleanup.\n */\ninterface Registration {\n /** The subscription strings this registration added (e.g. `\"transcription:en\"`). */\n streams: string[];\n /** Cleanup functions returned by `router.on()` for each stream key. */\n routerCleanups: Array<() => void>;\n}\n\n// ─── Helpers ────────────────────────────────────────────────────────────────\n\n/** Stream prefix used on the wire. */\nconst STREAM_PREFIX = StreamType.TRANSCRIPTION; // \"transcription\"\n\n/** Build the subscription string for a language code. */\nfunction subscriptionKey(lang: string): string {\n return `${STREAM_PREFIX}:${lang}`;\n}\n\n/**\n * Map raw cloud `TranscriptionData` into the public {@link TranscriptionEvent}.\n *\n * The cloud sends fields like `detectedLanguage`, `transcribeLanguage`, and\n * `metadata` that we normalise into a friendlier shape.\n */\nfunction normalise(streamType: string, raw: any): TranscriptionEvent {\n // Derive the language from `detectedLanguage` first, then fall back to the\n // subscription language embedded in the streamType (\"transcription:en\" → \"en\"),\n // and finally to an empty string.\n const language = raw.detectedLanguage ?? raw.transcribeLanguage ?? streamType.replace(`${STREAM_PREFIX}:`, \"\") ?? \"\";\n\n return {\n text: raw.text ?? \"\",\n isFinal: !!raw.isFinal,\n language,\n speakerId: raw.speakerId,\n utteranceId: raw.utteranceId,\n confidence: raw.confidence,\n startTime: raw.startTime ?? 0,\n endTime: raw.endTime ?? 0,\n duration: raw.duration,\n metadata: raw.metadata,\n };\n}\n\n// ─── Manager ────────────────────────────────────────────────────────────────\n\n/**\n * Manages transcription subscriptions and dispatches normalised events to\n * application-level handlers.\n *\n * Every public subscription method (`on`, `forLanguage`) is **independent** —\n * multiple can be active simultaneously and each returns its own cleanup\n * function. Calling {@link stop} tears down *all* active subscriptions.\n */\nexport class TranscriptionManager {\n private readonly deps: TranscriptionManagerDeps;\n\n /**\n * All currently-active registrations. We track them so that {@link stop}\n * can clean everything up in one shot.\n */\n private registrations = new Set<Registration>();\n\n /**\n * Reference count per subscription stream string.\n *\n * Multiple independent registrations may share the same underlying stream\n * key (e.g. two `forLanguage(\"en\", …)` calls). We only call\n * `deps.removeSubscription` when the ref-count drops to zero.\n */\n private refCounts = new Map<string, number>();\n\n /** Latest config applied via {@link configure}. */\n private currentConfig: TranscriptionConfig | null = null;\n\n constructor(deps: TranscriptionManagerDeps) {\n this.deps = deps;\n }\n\n // ─── Public API ─────────────────────────────────────────────────────────\n\n /**\n * Subscribe to **all** transcription events (auto-detect, all languages).\n *\n * Registers a prefix handler on the router for `\"transcription\"` so that\n * events for *any* language are delivered to `handler`. The cloud\n * subscription is `\"transcription:auto\"`.\n *\n * @param handler - Called for every incoming transcription event.\n * @returns A cleanup function that removes this specific subscription.\n */\n on(handler: TranscriptionHandler): () => void {\n const stream = subscriptionKey(\"auto\"); // \"transcription:auto\"\n\n // Register on the router using the bare prefix so we receive\n // transcription:en, transcription:ja, transcription:auto, etc.\n const routerCleanup = this.deps.router.on(STREAM_PREFIX, (_streamType, data, _message) => {\n try {\n handler(normalise(_streamType, data));\n } catch (err) {\n this.deps.logger.error(`[TranscriptionManager] Error in on() handler:`, err);\n }\n });\n\n const reg: Registration = {\n streams: [stream],\n routerCleanups: [routerCleanup],\n };\n\n this.addRegistration(reg);\n\n return () => this.removeRegistration(reg);\n }\n\n /**\n * Subscribe to transcription for one or more specific languages.\n *\n * Each call is **independent** — multiple can be active simultaneously.\n * When an array is provided the handler fires for events in *any* of the\n * listed languages.\n *\n * @param lang - ISO 639-1 language code(s) (e.g. `\"en\"` or `[\"en\", \"ja\"]`).\n * @param handler - Called for every matching transcription event.\n * @returns A cleanup function that removes this specific subscription.\n */\n forLanguage(lang: string | string[], handler: TranscriptionHandler): () => void {\n const langs = Array.isArray(lang) ? lang : [lang];\n\n if (langs.length === 0) {\n this.deps.logger.warn(\"[TranscriptionManager] forLanguage() called with empty language array — no-op.\");\n return () => {};\n }\n\n const streams: string[] = [];\n const routerCleanups: Array<() => void> = [];\n\n for (const l of langs) {\n const stream = subscriptionKey(l); // e.g. \"transcription:en\"\n\n const cleanup = this.deps.router.on(stream, (_streamType, data, _message) => {\n try {\n handler(normalise(_streamType, data));\n } catch (err) {\n this.deps.logger.error(`[TranscriptionManager] Error in forLanguage(\"${l}\") handler:`, err);\n }\n });\n\n streams.push(stream);\n routerCleanups.push(cleanup);\n }\n\n const reg: Registration = { streams, routerCleanups };\n this.addRegistration(reg);\n\n return () => this.removeRegistration(reg);\n }\n\n /**\n * Apply transcription configuration (language hints, custom vocabulary,\n * diarisation toggle).\n *\n * The configuration is sent to the cloud immediately and cached so that\n * it can be re-sent if the session reconnects.\n *\n * @param config - Configuration to apply.\n */\n configure(config: TranscriptionConfig): void {\n this.currentConfig = { ...config };\n\n this.deps.sendMessage({\n type: \"transcription_config\",\n languageHints: config.languageHints,\n vocabulary: config.vocabulary,\n diarization: config.diarization ?? true,\n });\n\n this.deps.logger.debug(\"[TranscriptionManager] Configuration sent:\", config);\n }\n\n /**\n * Stop **all** transcription subscriptions and remove every handler.\n *\n * After calling this, no transcription callbacks will fire until new\n * subscriptions are created via {@link on} or {@link forLanguage}.\n */\n stop(): void {\n // Iterate over a snapshot — removeRegistration mutates the set.\n const snapshot = Array.from(this.registrations);\n for (const reg of snapshot) {\n this.removeRegistration(reg);\n }\n\n this.currentConfig = null;\n this.deps.logger.debug(\"[TranscriptionManager] All subscriptions stopped.\");\n }\n\n // ─── Introspection (useful for testing / debugging) ─────────────────────\n\n /** Returns `true` if there is at least one active subscription. */\n get active(): boolean {\n return this.registrations.size > 0;\n }\n\n /** Returns the current configuration, or `null` if none has been set. */\n get config(): TranscriptionConfig | null {\n return this.currentConfig ? { ...this.currentConfig } : null;\n }\n\n // ─── Internal ───────────────────────────────────────────────────────────\n\n /**\n * Track a new registration: increment ref-counts and call\n * `addSubscription` for any stream that is newly referenced.\n */\n private addRegistration(reg: Registration): void {\n this.registrations.add(reg);\n\n for (const stream of reg.streams) {\n const prev = this.refCounts.get(stream) ?? 0;\n this.refCounts.set(stream, prev + 1);\n\n // Only subscribe on the wire when the first handler for this stream\n // comes online.\n if (prev === 0) {\n this.deps.addSubscription(stream);\n this.deps.logger.debug(`[TranscriptionManager] Subscribed to \"${stream}\".`);\n }\n }\n }\n\n /**\n * Tear down a registration: unregister router handlers, decrement\n * ref-counts, and call `removeSubscription` when a stream drops to zero\n * references.\n */\n private removeRegistration(reg: Registration): void {\n if (!this.registrations.has(reg)) return; // Already removed (idempotent).\n\n // 1. Remove router handlers.\n for (const cleanup of reg.routerCleanups) {\n try {\n cleanup();\n } catch {\n // Best-effort — the router may have already been cleared.\n }\n }\n\n // 2. Decrement ref-counts and unsubscribe when necessary.\n for (const stream of reg.streams) {\n const count = this.refCounts.get(stream) ?? 0;\n const next = count - 1;\n\n if (next <= 0) {\n this.refCounts.delete(stream);\n this.deps.removeSubscription(stream);\n this.deps.logger.debug(`[TranscriptionManager] Unsubscribed from \"${stream}\".`);\n } else {\n this.refCounts.set(stream, next);\n }\n }\n\n // 3. Remove from the active set.\n this.registrations.delete(reg);\n }\n}\n",
38
+ "/**\n * TranslationManager — v3 SDK Translation API\n *\n * Replaces the old `session.events.ontranslationForLanguage()` method with a\n * cleaner, composable API that supports multiple simultaneous subscriptions.\n *\n * @example\n * ```ts\n * const session = await mentra.connect();\n *\n * // Subscribe to ALL active translation events\n * const stopAll = session.translation.on((evt) => {\n * console.log(`[${evt.sourceLanguage} → ${evt.targetLanguage}] ${evt.text}`);\n * });\n *\n * // Auto-detect source, translate to Spanish\n * const stopEs = session.translation.to(\"es\", (evt) => {\n * console.log(`Spanish: ${evt.text}`);\n * });\n *\n * // Auto-detect source, translate to multiple targets\n * const stopMulti = session.translation.to([\"es\", \"ja\"], (evt) => {\n * console.log(`${evt.targetLanguage}: ${evt.text}`);\n * });\n *\n * // Explicit source → target\n * const stopEnJa = session.translation.fromTo(\"en\", \"ja\", (evt) => {\n * console.log(`EN→JA: ${evt.text}`);\n * });\n *\n * // Explicit source → multiple targets\n * const stopEnMulti = session.translation.fromTo(\"en\", [\"ja\", \"es\"], (evt) => {\n * console.log(`EN→${evt.targetLanguage}: ${evt.text}`);\n * });\n *\n * // Cleanup individual subscriptions\n * stopEs();\n *\n * // Or tear down everything\n * session.translation.stop();\n * ```\n *\n * @module\n */\n\nimport { StreamType } from \"../../types\";\n\n// ─── Public Types ───────────────────────────────────────────────────────────\n\n/**\n * Normalised translation event delivered to subscriber callbacks.\n *\n * This is the *public* shape — it is mapped from the raw cloud\n * `TranslationData` message inside the manager so consumers never\n * need to think about wire-level details.\n */\nexport interface TranslationEvent {\n /** The translated text. */\n text: string;\n /** `true` when the cloud considers this segment finalised. */\n isFinal: boolean;\n /** ISO 639-1 source language code (e.g. `\"en\"`, `\"ja\"`). */\n sourceLanguage: string;\n /** ISO 639-1 target language code (e.g. `\"es\"`, `\"ja\"`). */\n targetLanguage: string;\n /** The original (untranslated) text, when available. */\n originalText?: string;\n /** Stable identifier for a contiguous utterance. */\n utteranceId?: string;\n /** Translation confidence in the range `[0, 1]`. */\n confidence?: number;\n /** Start time of the segment in milliseconds. */\n startTime: number;\n /** End time of the segment in milliseconds. */\n endTime: number;\n}\n\n/** Callback signature for translation subscribers. */\nexport type TranslationHandler = (data: TranslationEvent) => void;\n\n// ─── Internal Types ─────────────────────────────────────────────────────────\n\n/**\n * Dependencies injected by MentraSession.\n *\n * This is intentionally a *structural* type — we don't import the concrete\n * `DataStreamRouter` class so that the manager remains unit-testable with\n * plain stubs.\n */\nexport interface TranslationManagerDeps {\n /** Register for DATA_STREAM messages by streamType key (exact or prefix). Returns a cleanup function. */\n router: {\n on(key: string, handler: (streamType: string, data: any, message: any) => void): () => void;\n };\n /** Add a subscription string (triggers SUBSCRIPTION_UPDATE to cloud). */\n addSubscription: (stream: string) => void;\n /** Remove a subscription string. */\n removeSubscription: (stream: string) => void;\n /** Send an arbitrary JSON message to the cloud. */\n sendMessage: (message: any) => void;\n /** Structured logger. */\n logger: {\n debug(...args: any[]): void;\n info(...args: any[]): void;\n warn(...args: any[]): void;\n error(...args: any[]): void;\n };\n}\n\n/**\n * Internal bookkeeping for a single `on()` / `to()` / `fromTo()` registration.\n *\n * Each call to a public subscription method produces one `Registration` per\n * stream key it subscribes to, enabling independent cleanup.\n */\ninterface Registration {\n /** The subscription strings this registration added (e.g. `\"translation:auto-es\"`). */\n streams: string[];\n /** Cleanup functions returned by `router.on()` for each stream key. */\n routerCleanups: Array<() => void>;\n}\n\n// ─── Helpers ────────────────────────────────────────────────────────────────\n\n/** Stream prefix used on the wire. */\nconst STREAM_PREFIX = StreamType.TRANSLATION; // \"translation\"\n\n/**\n * Build the wire subscription key for a translation pair.\n *\n * Wire protocol:\n * - `to(\"es\")` → `\"translation:auto-es\"`\n * - `fromTo(\"en\", \"ja\")` → `\"translation:en-ja\"`\n */\nfunction subscriptionKey(source: string, target: string): string {\n return `${STREAM_PREFIX}:${source}-${target}`;\n}\n\n/**\n * Parse a stream type string like `\"translation:en-ja\"` into its source and\n * target language components. Returns `null` if parsing fails.\n */\nfunction parseStreamType(streamType: string): { source: string; target: string } | null {\n const prefixLen = STREAM_PREFIX.length + 1; // \"translation:\"\n if (!streamType.startsWith(`${STREAM_PREFIX}:`)) return null;\n\n const pair = streamType.slice(prefixLen);\n const dashIdx = pair.indexOf(\"-\");\n if (dashIdx === -1) return null;\n\n return {\n source: pair.slice(0, dashIdx),\n target: pair.slice(dashIdx + 1),\n };\n}\n\n/**\n * Map raw cloud `TranslationData` into the public {@link TranslationEvent}.\n *\n * The cloud sends fields like `transcribeLanguage`, `translateLanguage`, and\n * `originalText` that we normalise into a friendlier shape.\n */\nfunction normalise(streamType: string, raw: any): TranslationEvent {\n // Derive languages from the raw data first, falling back to parsing the\n // streamType for robustness.\n const parsed = parseStreamType(streamType);\n\n const sourceLanguage = raw.transcribeLanguage ?? parsed?.source ?? \"\";\n const targetLanguage = raw.translateLanguage ?? parsed?.target ?? \"\";\n\n return {\n text: raw.text ?? \"\",\n isFinal: !!raw.isFinal,\n sourceLanguage,\n targetLanguage,\n originalText: raw.originalText,\n utteranceId: raw.utteranceId,\n confidence: raw.confidence,\n startTime: raw.startTime ?? 0,\n endTime: raw.endTime ?? 0,\n };\n}\n\n// ─── Manager ────────────────────────────────────────────────────────────────\n\n/**\n * Manages translation subscriptions and dispatches normalised events to\n * application-level handlers.\n *\n * Every public subscription method (`on`, `to`, `fromTo`) is **independent** —\n * multiple can be active simultaneously and each returns its own cleanup\n * function. Calling {@link stop} tears down *all* active subscriptions.\n */\nexport class TranslationManager {\n private readonly deps: TranslationManagerDeps;\n\n /**\n * All currently-active registrations. We track them so that {@link stop}\n * can clean everything up in one shot.\n */\n private registrations = new Set<Registration>();\n\n /**\n * Reference count per subscription stream string.\n *\n * Multiple independent registrations may share the same underlying stream\n * key (e.g. two `to(\"es\", …)` calls). We only call\n * `deps.removeSubscription` when the ref-count drops to zero.\n */\n private refCounts = new Map<string, number>();\n\n constructor(deps: TranslationManagerDeps) {\n this.deps = deps;\n }\n\n // ─── Public API ─────────────────────────────────────────────────────────\n\n /**\n * Subscribe to **all** active translation events.\n *\n * Registers a prefix handler on the router for `\"translation\"` so that\n * events for *any* language pair are delivered to `handler`. No wire\n * subscription is added for the bare prefix — the individual `to()` and\n * `fromTo()` calls produce the actual subscriptions. This handler simply\n * listens to whatever translations are already flowing.\n *\n * @param handler - Called for every incoming translation event.\n * @returns A cleanup function that removes this specific subscription.\n */\n on(handler: TranslationHandler): () => void {\n // Register on the router using the bare prefix so we receive\n // translation:en-ja, translation:auto-es, etc.\n const routerCleanup = this.deps.router.on(STREAM_PREFIX, (_streamType, data, _message) => {\n try {\n handler(normalise(_streamType, data));\n } catch (err) {\n this.deps.logger.error(\"[TranslationManager] Error in on() handler:\", err);\n }\n });\n\n // The catch-all listener does NOT produce a wire subscription — only\n // specific to()/fromTo() calls do. We still track the registration so\n // that stop() can tear it down.\n const reg: Registration = {\n streams: [],\n routerCleanups: [routerCleanup],\n };\n\n this.registrations.add(reg);\n\n return () => this.removeRegistration(reg);\n }\n\n /**\n * Auto-detect source language and translate to one or more target\n * languages.\n *\n * Each call is **independent** — multiple can be active simultaneously.\n * When an array is provided the handler fires for events in *any* of the\n * listed target languages.\n *\n * Wire protocol:\n * - `to(\"es\")` → subscribes `\"translation:auto-es\"`\n * - `to([\"es\", \"ja\"])` → subscribes `\"translation:auto-es\"` + `\"translation:auto-ja\"`\n *\n * @param target - ISO 639-1 target language code(s).\n * @param handler - Called for every matching translation event.\n * @returns A cleanup function that removes this specific subscription.\n */\n to(target: string | string[], handler: TranslationHandler): () => void {\n const targets = Array.isArray(target) ? target : [target];\n\n if (targets.length === 0) {\n this.deps.logger.warn(\"[TranslationManager] to() called with empty target array — no-op.\");\n return () => {};\n }\n\n const streams: string[] = [];\n const routerCleanups: Array<() => void> = [];\n\n for (const t of targets) {\n const stream = subscriptionKey(\"auto\", t); // e.g. \"translation:auto-es\"\n\n const cleanup = this.deps.router.on(stream, (_streamType, data, _message) => {\n try {\n handler(normalise(_streamType, data));\n } catch (err) {\n this.deps.logger.error(`[TranslationManager] Error in to(\"${t}\") handler:`, err);\n }\n });\n\n streams.push(stream);\n routerCleanups.push(cleanup);\n }\n\n const reg: Registration = { streams, routerCleanups };\n this.addRegistration(reg);\n\n return () => this.removeRegistration(reg);\n }\n\n /**\n * Translate from an explicit source language to one or more target\n * languages.\n *\n * Wire protocol:\n * - `fromTo(\"en\", \"ja\")` → subscribes `\"translation:en-ja\"`\n * - `fromTo(\"en\", [\"ja\", \"es\"])` → subscribes `\"translation:en-ja\"` + `\"translation:en-es\"`\n *\n * @param source - ISO 639-1 source language code.\n * @param target - ISO 639-1 target language code(s).\n * @param handler - Called for every matching translation event.\n * @returns A cleanup function that removes this specific subscription.\n */\n fromTo(source: string, target: string | string[], handler: TranslationHandler): () => void {\n const targets = Array.isArray(target) ? target : [target];\n\n if (targets.length === 0) {\n this.deps.logger.warn(\"[TranslationManager] fromTo() called with empty target array — no-op.\");\n return () => {};\n }\n\n const streams: string[] = [];\n const routerCleanups: Array<() => void> = [];\n\n for (const t of targets) {\n const stream = subscriptionKey(source, t); // e.g. \"translation:en-ja\"\n\n const cleanup = this.deps.router.on(stream, (_streamType, data, _message) => {\n try {\n handler(normalise(_streamType, data));\n } catch (err) {\n this.deps.logger.error(`[TranslationManager] Error in fromTo(\"${source}\", \"${t}\") handler:`, err);\n }\n });\n\n streams.push(stream);\n routerCleanups.push(cleanup);\n }\n\n const reg: Registration = { streams, routerCleanups };\n this.addRegistration(reg);\n\n return () => this.removeRegistration(reg);\n }\n\n /**\n * Stop **all** translation subscriptions and remove every handler.\n *\n * After calling this, no translation callbacks will fire until new\n * subscriptions are created via {@link on}, {@link to}, or {@link fromTo}.\n */\n stop(): void {\n // Iterate over a snapshot — removeRegistration mutates the set.\n const snapshot = Array.from(this.registrations);\n for (const reg of snapshot) {\n this.removeRegistration(reg);\n }\n\n this.deps.logger.debug(\"[TranslationManager] All subscriptions stopped.\");\n }\n\n // ─── Introspection (useful for testing / debugging) ─────────────────────\n\n /** Returns `true` if there is at least one active subscription. */\n get active(): boolean {\n return this.registrations.size > 0;\n }\n\n // ─── Internal ───────────────────────────────────────────────────────────\n\n /**\n * Track a new registration: increment ref-counts and call\n * `addSubscription` for any stream that is newly referenced.\n */\n private addRegistration(reg: Registration): void {\n this.registrations.add(reg);\n\n for (const stream of reg.streams) {\n const prev = this.refCounts.get(stream) ?? 0;\n this.refCounts.set(stream, prev + 1);\n\n // Only subscribe on the wire when the first handler for this stream\n // comes online.\n if (prev === 0) {\n this.deps.addSubscription(stream);\n this.deps.logger.debug(`[TranslationManager] Subscribed to \"${stream}\".`);\n }\n }\n }\n\n /**\n * Tear down a registration: unregister router handlers, decrement\n * ref-counts, and call `removeSubscription` when a stream drops to zero\n * references.\n */\n private removeRegistration(reg: Registration): void {\n if (!this.registrations.has(reg)) return; // Already removed (idempotent).\n\n // 1. Remove router handlers.\n for (const cleanup of reg.routerCleanups) {\n try {\n cleanup();\n } catch {\n // Best-effort — the router may have already been cleared.\n }\n }\n\n // 2. Decrement ref-counts and unsubscribe when necessary.\n for (const stream of reg.streams) {\n const count = this.refCounts.get(stream) ?? 0;\n const next = count - 1;\n\n if (next <= 0) {\n this.refCounts.delete(stream);\n this.deps.removeSubscription(stream);\n this.deps.logger.debug(`[TranslationManager] Unsubscribed from \"${stream}\".`);\n } else {\n this.refCounts.set(stream, next);\n }\n }\n\n // 3. Remove from the active set.\n this.registrations.delete(reg);\n }\n}\n",
39
+ "/**\n * DataStreamRouter — Typed Message Dispatch\n *\n * Replaces the 413-line if/else chain in the old AppSession.handleMessage()\n * with a clean registry pattern. Each manager registers handlers for the\n * message types it cares about. The router dispatches incoming messages\n * to all matching handlers.\n *\n * Two levels of dispatch:\n *\n * 1. **MessageHandlerRegistry** — routes by top-level `message.type`\n * (e.g., \"tpa_connection_ack\", \"settings_update\", \"data_stream\",\n * \"device_state_update\", \"capabilities_update\", etc.)\n *\n * 2. **DataStreamRouter** — routes DATA_STREAM messages by `streamType`\n * (e.g., \"transcription:en\", \"translation:en-ja\", \"button_press\",\n * \"phone_notification\", etc.)\n *\n * MentraSession wires them together:\n * - Creates MessageHandlerRegistry\n * - Creates DataStreamRouter\n * - Registers DataStreamRouter.handle as the handler for \"data_stream\"\n * - Each manager registers its handlers on one or both registries\n *\n * @example\n * ```ts\n * const messages = new MessageHandlerRegistry();\n * const streams = new DataStreamRouter();\n *\n * // MentraSession registers the bridge\n * messages.register(\"data_stream\", (msg) => streams.handle(msg));\n *\n * // TranscriptionManager registers for transcription streams\n * streams.on(\"transcription\", (streamType, data) => { ... });\n *\n * // DeviceManager registers for direct messages\n * messages.register(\"device_state_update\", (msg) => { ... });\n * messages.register(\"capabilities_update\", (msg) => { ... });\n *\n * // Dispatch an incoming message — ~5 lines instead of 413\n * const msg = JSON.parse(raw);\n * if (msg?.type) messages.dispatch(msg);\n * ```\n */\n\n// ─── MessageHandlerRegistry ─────────────────────────────────────────────────\n\n/**\n * Handler for a top-level message type.\n * Receives the full parsed message object.\n */\nexport type MessageHandler = (message: any) => void;\n\n/**\n * Routes incoming messages by their `type` field to registered handlers.\n * Multiple handlers can be registered for the same message type — all fire.\n *\n * This replaces the massive if/else chain with O(1) lookup + iteration.\n */\nexport class MessageHandlerRegistry {\n /**\n * Map from message type string → array of handlers.\n * Using an array per type supports multiple managers registering\n * for the same message type (e.g., multiple subsystems interested\n * in CONNECTION_ACK).\n */\n private handlers = new Map<string, MessageHandler[]>();\n\n /**\n * Register a handler for a specific message type.\n * Multiple handlers per type are supported — all will fire.\n * Returns a cleanup function that removes this specific handler.\n *\n * @param type - The message `type` field value to match\n * @param handler - Function called with the full message object\n * @returns Cleanup function to unregister this handler\n */\n register(type: string, handler: MessageHandler): () => void {\n let list = this.handlers.get(type);\n if (!list) {\n list = [];\n this.handlers.set(type, list);\n }\n list.push(handler);\n\n // Return cleanup function\n return () => {\n const arr = this.handlers.get(type);\n if (arr) {\n const idx = arr.indexOf(handler);\n if (idx !== -1) {\n arr.splice(idx, 1);\n }\n if (arr.length === 0) {\n this.handlers.delete(type);\n }\n }\n };\n }\n\n /**\n * Dispatch a message to all handlers registered for its `type`.\n * Returns true if at least one handler was called, false otherwise.\n *\n * Handlers are called synchronously in registration order.\n * Errors in one handler do not prevent other handlers from running.\n */\n dispatch(message: { type: string; [key: string]: any }): boolean {\n const list = this.handlers.get(message.type);\n if (!list || list.length === 0) {\n return false;\n }\n\n for (const handler of list) {\n try {\n handler(message);\n } catch (err) {\n // Don't let one handler's error kill dispatch to other handlers.\n // In production, MentraSession's logger will catch these via\n // a global error boundary. Here we just ensure dispatch continues.\n console.error(`[MessageHandlerRegistry] Handler error for type=\"${message.type}\":`, err);\n }\n }\n\n return true;\n }\n\n /**\n * Check whether any handlers are registered for a message type.\n */\n has(type: string): boolean {\n const list = this.handlers.get(type);\n return !!list && list.length > 0;\n }\n\n /**\n * Remove all handlers for all message types.\n * Called during session cleanup/disconnect.\n */\n clear(): void {\n this.handlers.clear();\n }\n}\n\n// ─── DataStreamRouter ───────────────────────────────────────────────────────\n\n/**\n * Handler for a DATA_STREAM sub-message.\n *\n * @param streamType - The full stream type string (e.g., \"transcription:en\", \"button_press\")\n * @param data - The payload data from the DATA_STREAM message (already unwrapped)\n * @param message - The full raw DATA_STREAM message (for handlers that need metadata)\n */\nexport type StreamHandler = (streamType: string, data: any, message: any) => void;\n\n/**\n * Routes DATA_STREAM messages to handlers based on `streamType`.\n *\n * Supports two matching strategies:\n *\n * 1. **Exact match** — `streamType === registeredKey`\n * e.g., registered \"button_press\" matches incoming \"button_press\"\n *\n * 2. **Prefix match** — `streamType.startsWith(registeredPrefix)`\n * e.g., registered \"transcription\" matches \"transcription:en\", \"transcription:auto\"\n * e.g., registered \"translation\" matches \"translation:en-ja\", \"translation:auto-es\"\n * e.g., registered \"touch_event\" matches \"touch_event:triple_tap\"\n *\n * ALL matching handlers fire (not just the first match).\n * This is critical for supporting multiple simultaneous forLanguage() calls:\n *\n * ```ts\n * // Both handlers fire for \"transcription:en\" messages\n * router.on(\"transcription:en\", handlerA);\n * router.on(\"transcription:en\", handlerB);\n *\n * // Prefix handler also fires for \"transcription:en\" messages\n * router.on(\"transcription\", handlerC); // matches all transcription:*\n * ```\n *\n * Matching order: exact matches first, then prefix matches (longest prefix first).\n * Within the same key, handlers fire in registration order.\n */\nexport class DataStreamRouter {\n /**\n * Map from stream key (exact or prefix) → array of handlers.\n */\n private handlers = new Map<string, StreamHandler[]>();\n\n /**\n * Cached sorted prefix keys for efficient matching.\n * Invalidated when handlers are added or removed.\n * Sorted by length descending so longest prefix matches first.\n */\n private prefixKeysCache: string[] | null = null;\n\n /**\n * Register a handler for a stream type or prefix.\n *\n * @param key - Stream type to match. Can be:\n * - Exact: \"button_press\", \"transcription:en\", \"phone_notification\"\n * - Prefix: \"transcription\" (matches \"transcription:en\", \"transcription:auto\", etc.)\n * @param handler - Called with (streamType, data, fullMessage) for each match\n * @returns Cleanup function to unregister this handler\n *\n * @example\n * ```ts\n * // Exact match — only \"button_press\"\n * const stop = router.on(\"button_press\", (st, data) => { ... });\n *\n * // Prefix match — all transcription streams\n * const stop = router.on(\"transcription\", (st, data) => { ... });\n *\n * // Specific language\n * const stop = router.on(\"transcription:en\", (st, data) => { ... });\n *\n * // Later: unsubscribe\n * stop();\n * ```\n */\n on(key: string, handler: StreamHandler): () => void {\n let list = this.handlers.get(key);\n if (!list) {\n list = [];\n this.handlers.set(key, list);\n }\n list.push(handler);\n this.prefixKeysCache = null; // Invalidate cache\n\n // Return cleanup function\n return () => {\n const arr = this.handlers.get(key);\n if (arr) {\n const idx = arr.indexOf(handler);\n if (idx !== -1) {\n arr.splice(idx, 1);\n }\n if (arr.length === 0) {\n this.handlers.delete(key);\n this.prefixKeysCache = null; // Invalidate cache\n }\n }\n };\n }\n\n /**\n * Dispatch a DATA_STREAM message to all matching handlers.\n *\n * Expects a message shaped like:\n * ```json\n * {\n * \"type\": \"data_stream\",\n * \"streamType\": \"transcription:en\",\n * \"data\": { \"text\": \"hello\", \"isFinal\": true, ... }\n * }\n * ```\n *\n * Returns true if at least one handler was called.\n */\n handle(message: any): boolean {\n const streamType: string | undefined = message?.streamType;\n if (!streamType) return false;\n\n const data = message?.data ?? message;\n\n let matched = false;\n\n // 1. Exact match — highest priority\n const exactHandlers = this.handlers.get(streamType);\n if (exactHandlers && exactHandlers.length > 0) {\n for (const handler of exactHandlers) {\n try {\n handler(streamType, data, message);\n matched = true;\n } catch (err) {\n console.error(`[DataStreamRouter] Handler error for streamType=\"${streamType}\":`, err);\n }\n }\n }\n\n // 2. Prefix match — check all registered keys that are prefixes of streamType\n // e.g., key \"transcription\" matches streamType \"transcription:en\"\n // But NOT if the key IS the exact streamType (already handled above).\n const prefixKeys = this.getPrefixKeys();\n for (const key of prefixKeys) {\n // Skip exact match (already handled)\n if (key === streamType) continue;\n\n // Key must be a proper prefix: streamType starts with key,\n // and the character after the key is ':' or end-of-string.\n // This prevents \"touch_event\" from matching \"touch_event_other\" —\n // it only matches \"touch_event:triple_tap\" (colon separator).\n if (streamType.startsWith(key)) {\n const nextChar = streamType[key.length];\n if (nextChar === undefined || nextChar === \":\") {\n const handlers = this.handlers.get(key);\n if (handlers) {\n for (const handler of handlers) {\n try {\n handler(streamType, data, message);\n matched = true;\n } catch (err) {\n console.error(\n `[DataStreamRouter] Prefix handler error for key=\"${key}\" streamType=\"${streamType}\":`,\n err,\n );\n }\n }\n }\n }\n }\n }\n\n return matched;\n }\n\n /**\n * Check whether any handlers are registered for a stream key.\n */\n has(key: string): boolean {\n const list = this.handlers.get(key);\n return !!list && list.length > 0;\n }\n\n /**\n * Get all registered stream keys (both exact and prefix).\n * Useful for deriving the current subscription set.\n */\n getRegisteredKeys(): string[] {\n return Array.from(this.handlers.keys()).filter((key) => {\n const list = this.handlers.get(key);\n return list && list.length > 0;\n });\n }\n\n /**\n * Remove all handlers.\n * Called during session cleanup/disconnect.\n */\n clear(): void {\n this.handlers.clear();\n this.prefixKeysCache = null;\n }\n\n // ─── Internal ───────────────────────────────────────────────────────────\n\n /**\n * Get sorted prefix keys for matching, with longest first.\n * Uses a cache that's invalidated when handlers change.\n */\n private getPrefixKeys(): string[] {\n if (this.prefixKeysCache === null) {\n this.prefixKeysCache = Array.from(this.handlers.keys()).sort((a, b) => b.length - a.length);\n }\n return this.prefixKeysCache;\n }\n}\n\n// ─── Subscription Derivation ────────────────────────────────────────────────\n\n/**\n * Derive the set of subscription strings from the DataStreamRouter's\n * registered handler keys. This is used by MentraSession to compute\n * the SUBSCRIPTION_UPDATE message payload.\n *\n * The logic:\n * - Each registered key on the DataStreamRouter represents a desired subscription.\n * - Keys like \"transcription\" (prefix) map to \"transcription:auto\" subscription.\n * - Keys like \"transcription:en\" (exact) map to \"transcription:en\" subscription.\n * - Non-subscribable keys (e.g., internal-only) are filtered out.\n *\n * @param router - The DataStreamRouter to derive subscriptions from\n * @param additionalSubscriptions - Extra subscriptions from other sources\n * (e.g., DeviceManager state subscriptions that don't go through the router)\n * @returns Deduplicated array of subscription strings\n */\nexport function deriveSubscriptions(router: DataStreamRouter, additionalSubscriptions?: Set<string>): string[] {\n const subs = new Set<string>();\n\n for (const key of router.getRegisteredKeys()) {\n // The \"transcription\" prefix key means \"subscribe to transcription:auto\"\n if (key === \"transcription\") {\n subs.add(\"transcription:auto\");\n }\n // The \"translation\" prefix key means \"subscribe to all active translations\"\n // Individual translation targets are registered as \"translation:auto-es\" etc.\n else if (key === \"translation\") {\n // Generic translation listener — the individual to()/fromTo() calls\n // register more specific keys that produce the actual subscriptions.\n // The prefix handler is just for the .on() catch-all.\n // Don't produce a subscription for the bare prefix.\n }\n // Everything else is used as-is\n else {\n subs.add(key);\n }\n }\n\n // Merge additional subscriptions\n if (additionalSubscriptions) {\n for (const sub of additionalSubscriptions) {\n subs.add(sub);\n }\n }\n\n return Array.from(subs);\n}\n",
40
+ "import type { Logger } from \"pino\";\nimport { DataStreamRouter, MessageHandlerRegistry } from \"../DataStreamRouter\";\n\nexport class _MessageRouter {\n readonly messageHandlers = new MessageHandlerRegistry();\n readonly dataStreamRouter = new DataStreamRouter();\n\n private readonly logger: Logger;\n\n constructor(logger: Logger) {\n this.logger = logger;\n }\n\n handleRawText(raw: string): boolean {\n let message: any;\n\n try {\n message = JSON.parse(raw);\n } catch (error) {\n this.logger.warn({ raw }, \"MentraSession received invalid JSON\");\n throw error instanceof Error ? error : new Error(String(error));\n }\n\n if (!message?.type) {\n this.logger.debug({ message }, \"MentraSession ignored message without type\");\n return false;\n }\n\n const handled = this.messageHandlers.dispatch(message);\n if (!handled && message.type !== \"pong\") {\n this.logger.debug({ type: message.type }, \"MentraSession received unhandled message type\");\n }\n\n return handled;\n }\n\n destroy(): void {\n this.messageHandlers.clear();\n }\n}\n",
41
+ "/**\n * Transport Interface\n *\n * Runtime-agnostic message transport contract for MentraSession.\n * On a cloud server, this is a WebSocket. On a phone, it's a native bridge.\n * In tests, it's a mock. MentraSession never imports WebSocket directly —\n * it receives a Transport from the host environment.\n *\n * This interface has ZERO Node.js/Bun/server dependencies.\n * It runs in any JS engine (V8, JSC, Hermes, QuickJS).\n *\n * @see WebSocketTransport — cloud/server implementation\n * @see NativeBridgeTransport — phone/local runtime implementation (future)\n */\n\n// ─── Transport States ───────────────────────────────────────────────────────\n\n/**\n * Transport connection states, mirroring WebSocket readyState values\n * for compatibility with existing code that checks readyState.\n */\nexport const TransportState = {\n CONNECTING: 0,\n OPEN: 1,\n CLOSING: 2,\n CLOSED: 3,\n} as const;\n\nexport type TransportState = (typeof TransportState)[keyof typeof TransportState];\n\n// ─── Transport Interface ────────────────────────────────────────────────────\n\n/**\n * The Transport interface is the only bridge between MentraSession\n * and the outside world. Everything else (managers, message dispatch,\n * subscription logic) is pure TypeScript that runs identically\n * regardless of which Transport implementation is in use.\n *\n * Implementations:\n * - `WebSocketTransport` — wraps `ws` for cloud/server apps (used by MentraApp)\n * - `NativeBridgeTransport` — wraps `globalThis.__mentraTransport` for local apps\n * - `MockTransport` — for unit tests\n *\n * @example\n * ```ts\n * // Cloud app — MentraApp creates this automatically\n * const transport = new WebSocketTransport(wsUrl);\n *\n * // Local app — phone runtime provides this\n * const transport = new NativeBridgeTransport(globalThis.__mentraTransport);\n *\n * // Either way, MentraSession works the same\n * const session = new MentraSession({ transport, ... });\n * ```\n */\nexport interface Transport {\n /**\n * Send a JSON-serialized string message to the cloud/host.\n * Implementations should silently drop or queue messages\n * when the transport is not in OPEN state.\n */\n send(data: string): void;\n\n /**\n * Send binary data (e.g., audio stream frames).\n * Used by SpeakerManager for audio output streaming.\n */\n sendBinary(data: ArrayBuffer | Uint8Array): void;\n\n /**\n * Register a handler for incoming text (JSON) messages.\n * Only one handler should be active at a time — subsequent\n * calls replace the previous handler.\n */\n onMessage(handler: (data: string) => void): void;\n\n /**\n * Register a handler for incoming binary messages.\n * Used by MicManager for raw audio chunks.\n * Only one handler should be active at a time.\n */\n onBinary(handler: (data: ArrayBuffer) => void): void;\n\n /**\n * Register a handler for transport close events.\n * `code` and `reason` follow WebSocket close frame semantics.\n * Only one handler should be active at a time.\n */\n onClose(handler: (code: number, reason: string) => void): void;\n\n /**\n * Register a handler for transport errors.\n * Only one handler should be active at a time.\n */\n onError(handler: (error: Error) => void): void;\n\n /**\n * Close the transport. After calling this, no more messages\n * will be sent or received. The `onClose` handler will fire.\n */\n close(code?: number, reason?: string): void;\n\n /**\n * Current state of the transport.\n * Uses the same numeric values as WebSocket.readyState:\n * 0 = CONNECTING\n * 1 = OPEN\n * 2 = CLOSING\n * 3 = CLOSED\n */\n readonly readyState: TransportState;\n}\n\n// ─── Transport Events (for typed event patterns) ────────────────────────────\n\n/**\n * Options passed to Transport implementations at construction time.\n * Each implementation may support additional options beyond these.\n */\nexport interface TransportOptions {\n /** URL or address to connect to (WebSocket URL, bridge identifier, etc.) */\n url?: string;\n\n /** Headers to send during connection upgrade (WebSocket only) */\n headers?: Record<string, string>;\n\n /** Connection timeout in milliseconds */\n timeoutMs?: number;\n}\n\n// ─── Utilities ──────────────────────────────────────────────────────────────\n\n/**\n * Check if a transport is currently open and ready to send messages.\n */\nexport function isTransportOpen(transport: Transport): boolean {\n return transport.readyState === TransportState.OPEN;\n}\n\n/**\n * Check if a transport is in a terminal state (closing or closed).\n */\nexport function isTransportClosed(transport: Transport): boolean {\n return transport.readyState === TransportState.CLOSING || transport.readyState === TransportState.CLOSED;\n}\n",
42
+ "import type { Logger } from \"pino\";\nimport { Transport, TransportState } from \"../../transport/Transport\";\n\ninterface ConnectableTransport extends Transport {\n connect?: () => Promise<void>;\n}\n\nexport interface _ConnectionManagerDeps {\n transport: Transport;\n logger: Logger;\n autoReconnect: boolean;\n maxReconnectAttempts: number;\n reconnectDelay: number;\n onTransportReady: () => void;\n onTextMessage: (raw: string) => void;\n onBinaryMessage: (data: ArrayBuffer) => void;\n onClose: (info: { code: number; reason: string; permanent: boolean }) => void;\n onError: (error: Error) => void;\n}\n\nconst PING_INTERVAL_MS = 15_000;\n\nexport class _ConnectionManager {\n private readonly deps: _ConnectionManagerDeps;\n\n private connected = false;\n private parked = false;\n private explicitDisconnect = false;\n private reconnectAttempts = 0;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private pingInterval: ReturnType<typeof setInterval> | null = null;\n private parkedTimer: ReturnType<typeof setTimeout> | null = null;\n\n constructor(deps: _ConnectionManagerDeps) {\n this.deps = deps;\n this.attachTransportHandlers();\n }\n\n get isConnected(): boolean {\n return this.connected && this.deps.transport.readyState === TransportState.OPEN;\n }\n\n get isParked(): boolean {\n return this.parked;\n }\n\n async connect(): Promise<void> {\n this.explicitDisconnect = false;\n this.parked = false;\n this.stopParkedTimer();\n\n const transport = this.deps.transport as ConnectableTransport;\n if (typeof transport.connect === \"function\") {\n await transport.connect();\n } else if (this.deps.transport.readyState !== TransportState.OPEN) {\n throw new Error(\"Transport is not open and does not expose connect()\");\n }\n\n this.deps.onTransportReady();\n }\n\n disconnect(): void {\n this.explicitDisconnect = true;\n this.parked = false;\n this.connected = false;\n this.stopReconnectTimer();\n this.stopPingInterval();\n this.stopParkedTimer();\n this.deps.transport.close(1000, \"Client disconnect\");\n }\n\n markConnected(): void {\n this.connected = true;\n this.parked = false;\n this.reconnectAttempts = 0;\n this.stopParkedTimer();\n this.startPingInterval();\n }\n\n park(timeoutMs: number, onTimeout: () => void): void {\n this.connected = false;\n this.parked = true;\n this.stopReconnectTimer();\n this.stopPingInterval();\n this.stopParkedTimer();\n\n this.parkedTimer = setTimeout(() => {\n this.parkedTimer = null;\n this.parked = false;\n onTimeout();\n }, timeoutMs);\n }\n\n destroy(): void {\n this.connected = false;\n this.parked = false;\n this.stopReconnectTimer();\n this.stopPingInterval();\n this.stopParkedTimer();\n }\n\n private attachTransportHandlers(): void {\n this.deps.transport.onMessage((raw) => {\n this.deps.onTextMessage(raw);\n });\n\n this.deps.transport.onBinary((data) => {\n this.deps.onBinaryMessage(data);\n });\n\n this.deps.transport.onClose((code, reason) => {\n const permanent = this.explicitDisconnect || !this.deps.autoReconnect;\n\n this.connected = false;\n this.stopPingInterval();\n this.deps.onClose({ code, reason, permanent });\n\n if (!permanent && !this.parked) {\n this.scheduleReconnect();\n }\n });\n\n this.deps.transport.onError((error) => {\n this.deps.onError(error);\n });\n }\n\n private scheduleReconnect(): void {\n if (this.reconnectAttempts >= this.deps.maxReconnectAttempts) {\n this.deps.onClose({\n code: 4000,\n reason: \"Maximum reconnection attempts exceeded\",\n permanent: true,\n });\n return;\n }\n\n const delay = this.deps.reconnectDelay * Math.pow(2, this.reconnectAttempts);\n this.reconnectAttempts += 1;\n this.stopReconnectTimer();\n\n this.deps.logger.warn(\n { attempt: this.reconnectAttempts, delay },\n \"MentraSession transport closed; scheduling reconnect\",\n );\n\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null;\n this.connect().catch((error) => {\n this.deps.onError(error instanceof Error ? error : new Error(String(error)));\n this.scheduleReconnect();\n });\n }, delay);\n }\n\n private startPingInterval(): void {\n this.stopPingInterval();\n this.pingInterval = setInterval(() => {\n if (this.deps.transport.readyState !== TransportState.OPEN) {\n return;\n }\n\n this.deps.transport.send(JSON.stringify({ type: \"ping\" }));\n }, PING_INTERVAL_MS);\n }\n\n private stopPingInterval(): void {\n if (this.pingInterval) {\n clearInterval(this.pingInterval);\n this.pingInterval = null;\n }\n }\n\n private stopReconnectTimer(): void {\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n }\n\n private stopParkedTimer(): void {\n if (this.parkedTimer) {\n clearTimeout(this.parkedTimer);\n this.parkedTimer = null;\n }\n }\n}\n",
43
+ "import type { Logger } from \"pino\";\nimport { AppToCloudMessageType } from \"../../types/message-types\";\n\nexport interface _SubscriptionManagerDeps {\n logger: Logger;\n isConnected: () => boolean;\n sendMessage: (message: unknown) => void;\n getPackageName: () => string;\n getSessionId: () => string;\n}\n\nexport class _SubscriptionManager {\n private readonly deps: _SubscriptionManagerDeps;\n private readonly subscriptions = new Set<string>();\n private syncScheduled = false;\n\n constructor(deps: _SubscriptionManagerDeps) {\n this.deps = deps;\n }\n\n add(stream: string): void {\n if (this.subscriptions.has(stream)) return;\n this.subscriptions.add(stream);\n this.scheduleSync();\n }\n\n remove(stream: string): void {\n if (!this.subscriptions.has(stream)) return;\n this.subscriptions.delete(stream);\n this.scheduleSync();\n }\n\n /**\n * Send the full subscription set to the cloud immediately.\n * Called directly after CONNECTION_ACK / RECONNECT_ACK to ensure\n * the cloud and SDK are in sync. Bypasses the microtask batch.\n */\n sync(): void {\n this.syncScheduled = false;\n this.deps.sendMessage({\n type: AppToCloudMessageType.SUBSCRIPTION_UPDATE,\n packageName: this.deps.getPackageName(),\n sessionId: this.deps.getSessionId(),\n subscriptions: this.snapshot(),\n timestamp: new Date(),\n });\n }\n\n snapshot(): string[] {\n return Array.from(this.subscriptions);\n }\n\n clear(): void {\n this.subscriptions.clear();\n this.syncScheduled = false;\n }\n\n /**\n * Batch multiple add/remove calls within the same microtask into a\n * single SUBSCRIPTION_UPDATE message. If onSession registers 5\n * subscriptions synchronously, only one message is sent at the end\n * of the current tick instead of 5.\n */\n private scheduleSync(): void {\n if (!this.deps.isConnected() || this.syncScheduled) return;\n this.syncScheduled = true;\n queueMicrotask(() => {\n if (!this.syncScheduled) return;\n this.syncScheduled = false;\n if (this.deps.isConnected()) {\n this.sync();\n }\n });\n }\n}\n",
44
+ "/**\n * Error Utilities\n *\n * Shared error handling utilities to replace the ~20 copy-pasted\n * error wrapping patterns throughout the old AppSession.\n *\n * Before (scattered across AppSession):\n * } catch (error) {\n * const errorMessage = error instanceof Error ? error.message : String(error);\n * this.logger.error({ error: errorMessage }, \"Something failed\");\n * }\n *\n * After:\n * } catch (error) {\n * this.logger.error({ error: toErrorMessage(error) }, \"Something failed\");\n * }\n */\n\n// ─── Error Message Extraction ───────────────────────────────────────────────\n\n/**\n * Safely extract a human-readable error message from any thrown value.\n *\n * JavaScript allows throwing anything — Error objects, strings, numbers,\n * null, undefined, objects, etc. This function normalizes all of them\n * into a string suitable for logging.\n *\n * @param error - Any value that was thrown or caught\n * @returns A string error message\n *\n * @example\n * ```ts\n * try {\n * await doSomething();\n * } catch (error) {\n * logger.error({ error: toErrorMessage(error) }, \"doSomething failed\");\n * }\n * ```\n */\nexport function toErrorMessage(error: unknown): string {\n if (error instanceof Error) {\n return error.message;\n }\n if (typeof error === \"string\") {\n return error;\n }\n if (error === null) {\n return \"null\";\n }\n if (error === undefined) {\n return \"undefined\";\n }\n if (typeof error === \"object\") {\n // Some libraries throw plain objects with a message field\n const obj = error as Record<string, unknown>;\n if (typeof obj.message === \"string\") {\n return obj.message;\n }\n try {\n return JSON.stringify(error);\n } catch {\n return String(error);\n }\n }\n return String(error);\n}\n\n// ─── Error Wrapping ─────────────────────────────────────────────────────────\n\n/**\n * Wrap an unknown caught value into a proper Error instance.\n * If it's already an Error, returns it as-is.\n * Otherwise, creates a new Error with the extracted message.\n *\n * Useful when you need to re-throw or pass an Error object\n * but the catch block might have received a non-Error value.\n *\n * @param error - Any value that was thrown or caught\n * @param fallbackMessage - Optional message to use if the error has no message\n * @returns An Error instance\n *\n * @example\n * ```ts\n * try {\n * await riskyOperation();\n * } catch (error) {\n * throw toError(error, \"riskyOperation failed\");\n * }\n * ```\n */\nexport function toError(error: unknown, fallbackMessage?: string): Error {\n if (error instanceof Error) {\n return error;\n }\n const message = toErrorMessage(error);\n return new Error(message || fallbackMessage || \"Unknown error\");\n}\n\n// ─── Safe Execution ─────────────────────────────────────────────────────────\n\n/**\n * Execute a function and swallow any errors, optionally logging them.\n * Used for fire-and-forget operations where failure is acceptable\n * (e.g., cleanup handlers, optional notifications, analytics).\n *\n * @param fn - The function to execute\n * @param onError - Optional error handler (e.g., logger.warn)\n *\n * @example\n * ```ts\n * // Cleanup that shouldn't throw\n * safeExec(() => transport.close(), (err) => logger.warn({ err }, \"close failed\"));\n *\n * // Fire-and-forget analytics\n * safeExec(() => trackEvent(\"session_start\"));\n * ```\n */\nexport function safeExec(fn: () => void, onError?: (error: Error) => void): void {\n try {\n fn();\n } catch (err) {\n if (onError) {\n onError(toError(err));\n }\n }\n}\n\n/**\n * Execute an async function and swallow any errors, optionally logging them.\n * Async version of safeExec.\n *\n * @param fn - The async function to execute\n * @param onError - Optional error handler\n *\n * @example\n * ```ts\n * await safeExecAsync(\n * () => session.storage.set(\"lastActive\", Date.now()),\n * (err) => logger.warn({ err }, \"Failed to persist lastActive\")\n * );\n * ```\n */\nexport async function safeExecAsync(fn: () => Promise<void>, onError?: (error: Error) => void): Promise<void> {\n try {\n await fn();\n } catch (err) {\n if (onError) {\n onError(toError(err));\n }\n }\n}\n\n// ─── Deprecation Warnings ───────────────────────────────────────────────────\n\n/**\n * Set of deprecation keys that have already been warned about.\n * Prevents spamming the console with the same deprecation warning\n * on every access — warns once per session, not once per call.\n */\nconst _deprecationWarnings = new Set<string>();\n\n/**\n * Log a deprecation warning once per key per process lifetime.\n * Subsequent calls with the same key are silently ignored.\n *\n * @param key - Unique identifier for this deprecation (e.g., \"session.layouts\")\n * @param message - The deprecation message to log\n * @param logger - Optional structured logger; falls back to console.warn\n *\n * @example\n * ```ts\n * get layouts() {\n * warnOnce(\n * \"session.layouts\",\n * \"session.layouts is deprecated. Use session.display instead.\",\n * this.logger\n * );\n * return this.display;\n * }\n * ```\n */\nexport function warnOnce(key: string, message: string, logger?: { warn: (msg: string) => void }): void {\n if (_deprecationWarnings.has(key)) return;\n _deprecationWarnings.add(key);\n\n const formatted = `⚠️ DEPRECATION: ${message}`;\n if (logger) {\n logger.warn(formatted);\n } else {\n console.warn(formatted);\n }\n}\n\n/**\n * Reset all deprecation warning state.\n * Only useful in tests to ensure warnings fire again.\n */\nexport function resetDeprecationWarnings(): void {\n _deprecationWarnings.clear();\n}\n\n// ─── Timeout Utility ────────────────────────────────────────────────────────\n\n/**\n * Create a promise that rejects after a timeout.\n * Useful for racing against operations that might hang.\n *\n * @param ms - Timeout in milliseconds\n * @param message - Error message on timeout\n * @returns A promise that rejects after `ms` milliseconds\n *\n * @example\n * ```ts\n * const result = await Promise.race([\n * doSlowThing(),\n * timeout(5000, \"doSlowThing timed out after 5s\"),\n * ]);\n * ```\n */\nexport function timeout(ms: number, message?: string): Promise<never> {\n return new Promise<never>((_, reject) => {\n setTimeout(() => {\n reject(new Error(message ?? `Operation timed out after ${ms}ms`));\n }, ms);\n });\n}\n",
45
+ "// src/index.ts\nexport * from \"./types/token\";\n\n// Message type enums\nexport * from \"./types/message-types\";\n\n// Base message type\nexport * from \"./types/messages/base\";\n\n// Messages by direction - export everything except the conflicting type guards\nexport * from \"./types/messages/glasses-to-cloud\";\nexport * from \"./types/messages/cloud-to-glasses\";\nexport * from \"./types/messages/app-to-cloud\";\n\n// Utility exports\nexport * from \"./utils/bitmap-utils\";\nexport * from \"./utils/animation-utils\";\n\n// Export cloud-to-app type guards and runtime exports\nexport {\n // Type guards (excluding isPhotoResponse and isStreamStatus which conflict)\n isAppConnectionAck,\n isAppConnectionError,\n isAppStopped,\n isSettingsUpdate,\n isCapabilitiesUpdate,\n isDataStream,\n isAudioChunk,\n isStreamStatusCheckResponse,\n isDashboardModeChanged,\n isDashboardAlwaysOnChanged,\n isManagedStreamStatus,\n isRequestTelemetry,\n isTelemetryResponse,\n // Re-export the cloud-to-app versions of these type guards since they're the ones\n // that should be used when dealing with CloudToAppMessage types\n isPhotoResponse as isPhotoResponseFromCloud,\n isRgbLedControlResponse as isRgbLedControlResponseFromCloud,\n isStreamStatus as isStreamStatusFromCloud,\n} from \"./types\";\n\n// Export cloud-to-app types (type-only exports)\nexport type {\n AppConnectionAck,\n AppConnectionError,\n AppStopped,\n SettingsUpdate as AppSettingsUpdate, // Alias to avoid conflict with cloud-to-glasses SettingsUpdate\n CapabilitiesUpdate,\n DataStream,\n CloudToAppMessage,\n TranslationData,\n ToolCall,\n StandardConnectionError,\n CustomMessage,\n ManagedStreamStatus,\n StreamStatusCheckResponse,\n OutputStatus,\n MentraosSettingsUpdate,\n TranscriptionData,\n TranscriptionMetadata,\n SonioxToken,\n AudioChunk,\n PermissionError,\n PermissionErrorDetail,\n AudioPlayResponse,\n RequestTelemetry,\n TelemetryLogEntry,\n TelemetryResponse,\n} from \"./types\";\n\n// Stream types\nexport * from \"./types/streams\";\n\n// Layout types\nexport * from \"./types/layouts\";\n\n// Dashboard types\nexport * from \"./types/dashboard\";\n\n// RTMP streaming types\nexport * from \"./types/rtmp-stream\";\n\n// Other system enums\nexport { AppType, LayoutType, ViewType, AppSettingType, HardwareType, HardwareRequirementLevel } from \"./types/enums\";\n\n// Core model interfaces\nexport * from \"./types/models\";\n\n// Webhook interfaces\nexport * from \"./types/webhooks\";\n\n// Capability Discovery types\nexport * from \"./types/capabilities\";\n\n// App session and server exports\nexport * from \"./app/index\";\nexport * from \"./MiniAppServer\";\n\n// v3 session — explicit re-exports to avoid name collisions with v2 types\n// The full set of manager types is available via \"@mentra/sdk/session\" entrypoint\nexport { MentraSession } from \"./session/MentraSession\";\nexport type { MentraSessionConfig } from \"./session/MentraSession\";\n\n// Logging exports\nexport * from \"./logging/logger\";\n\n// Error classes\nexport {\n MentraError,\n MentraAuthError,\n MentraConnectionError,\n MentraTimeoutError,\n MentraValidationError,\n MentraPermissionError,\n} from \"./logging/errors\";\n\n// Re-export common types for convenience\n// This allows developers to import commonly used types directly from the package root\n// without having to know exactly which file they come from\n\n// From messages/glasses-to-cloud.ts\nexport type {\n ButtonPress,\n HeadPosition,\n TouchEvent,\n GlassesBatteryUpdate,\n PhoneBatteryUpdate,\n GlassesConnectionState,\n LocationUpdate,\n CalendarEvent,\n Vad,\n PhoneNotification,\n PhoneNotificationDismissed,\n StartApp,\n StopApp,\n ConnectionInit,\n DashboardState,\n OpenDashboard,\n GlassesToCloudMessage,\n PhotoResponse,\n RgbLedControlResponse,\n ConnectionState,\n PhotoErrorDetails,\n} from \"./types/messages/glasses-to-cloud\";\n\n// These are enums (runtime values) — must NOT be re-exported as `export type`\n// or they become unusable as values (TS1362). The `export *` at the top of\n// this file already exports them correctly; these explicit exports are kept\n// here as documentation but as value exports.\nexport { PhotoErrorCode, PhotoStage, StreamStatus, KeepAliveAck } from \"./types/messages/glasses-to-cloud\";\n\n// From messages/cloud-to-glasses.ts\nexport type {\n ConnectionAck,\n ConnectionError,\n AuthError,\n DisplayEvent,\n AppStateChange,\n MicrophoneStateChange,\n CloudToGlassesMessage,\n PhotoRequestToGlasses,\n RgbLedControlToGlasses,\n CameraFovSetToGlasses,\n SettingsUpdate,\n StartStream,\n StopStream,\n KeepStreamAlive,\n LedColor,\n} from \"./types/messages/cloud-to-glasses\";\n\n// From messages/app-to-cloud.ts\nexport type {\n AppConnectionInit,\n AppSubscriptionUpdate,\n StreamRequest,\n StreamStopRequest,\n AppToCloudMessage,\n PhotoRequest,\n RgbLedControlRequest,\n CameraFovSetRequest,\n CameraRoiPosition,\n} from \"./types/messages/app-to-cloud\";\n\n// From layout.ts\nexport type {\n TextWall,\n DoubleTextWall,\n DashboardCard,\n ReferenceCard,\n Layout,\n DisplayRequest,\n BitmapView,\n ClearView,\n} from \"./types/layouts\";\n\n// Type guards - re-export the most commonly used ones for convenience\nexport {\n isButtonPress,\n isHeadPosition,\n isConnectionInit,\n isStartApp,\n isStopApp,\n isPhotoResponse as isPhotoResponseFromGlasses,\n isRgbLedControlResponse as isRgbLedControlResponseFromGlasses,\n isStreamStatus as isStreamStatusFromGlasses,\n isKeepAliveAck,\n isPhoneNotificationDismissed,\n} from \"./types/messages/glasses-to-cloud\";\n\nexport {\n isConnectionAck,\n isDisplayEvent,\n isAppStateChange,\n isPhotoRequest,\n isSettingsUpdate as isSettingsUpdateToGlasses,\n isStartStream,\n isStopStream,\n isKeepStreamAlive,\n isRgbLedControl,\n} from \"./types/messages/cloud-to-glasses\";\n\nexport {\n isAppConnectionInit,\n isAppSubscriptionUpdate,\n isDisplayRequest,\n isStreamRequest,\n isStreamStopRequest,\n isPhotoRequest as isPhotoRequestFromApp,\n isRgbLedControlRequest,\n isCameraFovSetRequest,\n isOwnershipRelease,\n} from \"./types/messages/app-to-cloud\";\n\n// Export setting-related types\nexport { validateAppConfig } from \"./types/models\";\n\nexport type {\n BaseAppSetting,\n AppSetting,\n AppSettings,\n AppConfig,\n ToolSchema,\n ToolParameterSchema,\n HardwareRequirement,\n PreviewImage,\n PhotoOrientation,\n} from \"./types/models\";\n\n// Export RTMP streaming types\nexport type { VideoConfig, AudioConfig, StreamConfig, StreamStatusHandler } from \"./types/rtmp-stream\";\n\n// Export app session modules\nexport * from \"./app/session/modules\";\n\n// Export photo data types\nexport type { PhotoData } from \"./types/photo-data\";\n\n// Export device state types (WebSocket-based observables)\nexport type { DeviceState } from \"./app/session/device-state\";\nexport { Observable } from \"./utils/Observable\";\n\n// Re-export types from @mentra/types so SDK users don't need to install it separately\nexport type { GlassesInfo } from \"@mentra/types\";\n\n/**\n * WebSocket error information\n */\nexport interface WebSocketError {\n code: string;\n message: string;\n details?: unknown;\n}\n\nexport type { AuthenticatedRequest, AuthVariables, MentraAuthContext, MentraAuthHonoContext } from \"./types/index\";\n\n// Frontend authentication helpers for Bun fullstack apps\nexport {\n createAuthMiddleware,\n createMentraAuthRoutes,\n generateFrontendToken,\n getMentraAuth,\n requireMentraAuth,\n} from \"./app/webview/index\";\n",
14
46
  "/**\n * 🎨 Bitmap Utilities Module\n *\n * Provides helper functions for working with bitmap images in MentraOS applications.\n * Includes file loading, data validation, and format conversion utilities.\n *\n * @example\n * ```typescript\n * import { BitmapUtils } from '@mentra/sdk';\n *\n * // Load a single BMP file\n * const bmpHex = await BitmapUtils.loadBmpAsHex('./my-image.bmp');\n * session.layouts.showBitmapView(bmpHex);\n *\n * // Load multiple animation frames\n * const frames = await BitmapUtils.loadBmpFrames('./animations', 10);\n * session.layouts.showBitmapAnimation(frames, 1500, true);\n * ```\n */\n\nimport * as fs from \"fs/promises\";\nimport * as path from \"path\";\nimport { Jimp } from \"jimp\";\n\n/**\n * Validation result for bitmap data\n */\nexport interface BitmapValidation {\n /** Whether the bitmap data is valid */\n isValid: boolean;\n /** Total byte count of the bitmap */\n byteCount: number;\n /** Number of black (non-FF) pixels found */\n blackPixels: number;\n /** Array of validation error messages */\n errors: string[];\n /** Additional metadata about the bitmap */\n metadata?: {\n /** Expected bitmap dimensions (if detectable) */\n dimensions?: { width: number; height: number };\n /** Bitmap file format info */\n format?: string;\n };\n}\n\n/**\n * Options for loading bitmap frames\n */\nexport interface LoadFramesOptions {\n /** File name pattern (default: 'animation_10_frame_{i}.bmp') */\n filePattern?: string;\n /** Starting frame number (default: 1) */\n startFrame?: number;\n /** Validate each frame during loading (default: true) */\n validateFrames?: boolean;\n /** Continue loading if a frame is missing (default: false) */\n skipMissingFrames?: boolean;\n}\n\n/**\n * Utility class for working with bitmap images in MentraOS applications\n */\nexport class BitmapUtils {\n static async convert24BitTo1BitBMP(input24BitBmp: Buffer): Promise<Buffer> {\n // Read header information from 24-bit BMP\n const width = input24BitBmp.readUInt32LE(18);\n const height = Math.abs(input24BitBmp.readInt32LE(22)); // Height can be negative (top-down BMP)\n const isTopDown = input24BitBmp.readInt32LE(22) < 0;\n const bitsPerPixel = input24BitBmp.readUInt16LE(28);\n\n if (bitsPerPixel !== 24) {\n throw new Error(\"Input must be a 24-bit BMP\");\n }\n\n // Calculate row sizes (both must be 4-byte aligned)\n const rowSize24 = Math.ceil((width * 3) / 4) * 4;\n const rowSize1 = Math.ceil(width / 32) * 4; // 32 pixels per 4 bytes\n\n // Calculate sizes for 1-bit BMP\n const colorTableSize = 8; // 2 colors * 4 bytes each\n const headerSize = 54 + colorTableSize;\n const pixelDataSize = rowSize1 * height;\n const fileSize = headerSize + pixelDataSize;\n\n // Create new buffer for 1-bit BMP\n const output1BitBmp = Buffer.alloc(fileSize);\n let offset = 0;\n\n // Write BMP file header (14 bytes)\n output1BitBmp.write(\"BM\", offset);\n offset += 2; // Signature\n output1BitBmp.writeUInt32LE(fileSize, offset);\n offset += 4; // File size\n output1BitBmp.writeUInt16LE(0, offset);\n offset += 2; // Reserved 1\n output1BitBmp.writeUInt16LE(0, offset);\n offset += 2; // Reserved 2\n output1BitBmp.writeUInt32LE(headerSize, offset);\n offset += 4; // Pixel data offset\n\n // Write DIB header (40 bytes)\n output1BitBmp.writeUInt32LE(40, offset);\n offset += 4; // DIB header size\n output1BitBmp.writeInt32LE(width, offset);\n offset += 4; // Width\n output1BitBmp.writeInt32LE(height, offset);\n offset += 4; // Height (positive for bottom-up)\n output1BitBmp.writeUInt16LE(1, offset);\n offset += 2; // Planes\n output1BitBmp.writeUInt16LE(1, offset);\n offset += 2; // Bits per pixel (1-bit)\n output1BitBmp.writeUInt32LE(0, offset);\n offset += 4; // Compression (none)\n output1BitBmp.writeUInt32LE(pixelDataSize, offset);\n offset += 4; // Image size\n output1BitBmp.writeInt32LE(2835, offset);\n offset += 4; // X pixels per meter (72 DPI)\n output1BitBmp.writeInt32LE(2835, offset);\n offset += 4; // Y pixels per meter (72 DPI)\n output1BitBmp.writeUInt32LE(2, offset);\n offset += 4; // Colors used\n output1BitBmp.writeUInt32LE(2, offset);\n offset += 4; // Important colors\n\n // Write color table (8 bytes)\n // Black (index 0): B=0, G=0, R=0, Reserved=0\n output1BitBmp.writeUInt32LE(0x00000000, offset);\n offset += 4;\n // White (index 1): B=255, G=255, R=255, Reserved=0\n output1BitBmp.writeUInt8(255, offset++); // Blue\n output1BitBmp.writeUInt8(255, offset++); // Green\n output1BitBmp.writeUInt8(255, offset++); // Red\n output1BitBmp.writeUInt8(0, offset++); // Reserved\n\n // Convert pixel data from 24-bit to 1-bit\n const pixelDataStart24 = 54; // 24-bit BMP has no color table\n\n for (let y = 0; y < height; y++) {\n // BMP files are usually stored bottom-up\n const sourceY = isTopDown ? y : height - 1 - y;\n const destY = height - 1 - y; // Always write bottom-up for compatibility\n\n // Initialize the row with zeros\n const rowData = Buffer.alloc(rowSize1);\n\n for (let x = 0; x < width; x++) {\n // Get pixel from 24-bit BMP\n const offset24 = pixelDataStart24 + sourceY * rowSize24 + x * 3;\n const blue = input24BitBmp[offset24];\n const green = input24BitBmp[offset24 + 1];\n const red = input24BitBmp[offset24 + 2];\n\n // Determine if pixel is white (assuming pure black or white)\n // White = 1, Black = 0\n const isWhite = red > 128 || green > 128 || blue > 128 ? 1 : 0;\n\n // Calculate bit position\n const byteIndex = Math.floor(x / 8);\n const bitPosition = 7 - (x % 8); // MSB first\n\n // Set bit if white\n if (isWhite) {\n rowData[byteIndex] |= 1 << bitPosition;\n }\n }\n\n // Write row to output buffer\n const destOffset = offset + destY * rowSize1;\n rowData.copy(output1BitBmp, destOffset);\n }\n\n return output1BitBmp;\n }\n\n /**\n * Load a BMP file as hex string from filesystem\n *\n * @param filePath - Path to the BMP file\n * @returns Promise resolving to hex-encoded bitmap data\n * @throws Error if file cannot be read or is not a valid BMP\n *\n * @example\n * ```typescript\n * const bmpBase64 = await BitmapUtils.loadBmpFromFileAsBase64('./assets/icon.bmp');\n * session.layouts.showBitmapView(bmpBase64);\n * ```\n */\n static async fileToBase64(filePath: string): Promise<string> {\n try {\n const bmpData = await fs.readFile(filePath);\n\n return this.bufferToBase64(bmpData);\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(\n `Failed to load BMP file ${filePath}: ${error.message}`,\n );\n }\n throw new Error(`Failed to load BMP file ${filePath}: Unknown error`);\n }\n }\n\n static async bufferToBase64(bmpData: Buffer): Promise<string> {\n return bmpData.toString(\"base64\");\n }\n\n static async padBase64Bitmap(\n bmpBase64: string,\n padding?: { left: number; top: number },\n ): Promise<string> {\n const buffer = Buffer.from(bmpBase64, \"base64\");\n const paddedBuffer = await this.padBmpForGlasses(\n buffer,\n padding?.left,\n padding?.top,\n );\n return paddedBuffer.toString(\"base64\");\n }\n\n static async padBmpForGlasses(\n bmpData: Buffer,\n leftPadding: number = 50,\n topPadding: number = 35,\n ): Promise<Buffer> {\n try {\n // Basic BMP validation - check for BMP signature\n if (bmpData.length < 14 || bmpData[0] !== 0x42 || bmpData[1] !== 0x4d) {\n throw new Error(\n `Bmp data is not a valid BMP file (missing BM signature)`,\n );\n }\n\n let finalBmpData = bmpData;\n\n // Load the image with Jimp\n const image = await Jimp.read(bmpData);\n\n // Check if we need to add padding\n if (image.width !== 576 || image.height !== 135) {\n console.log(\n `Adding padding to BMP since it isn't 576x135 (assuming it's 526x100!)(current: ${image.width}x${image.height})`,\n );\n\n // Create a new 576x135 white canvas\n const paddedImage = new Jimp({\n width: 576,\n height: 135,\n color: 0x00000000,\n });\n\n // // Calculate position to place the original image (with padding)\n const leftPadding = 50; // 45px padding on left\n const topPadding = 35; // 35px padding on top\n\n // Composite the original image onto the white canvas\n // paddedImage.composite(image, leftPadding, topPadding);\n paddedImage.composite(image, leftPadding, topPadding);\n\n finalBmpData = await this.convert24BitTo1BitBMP(\n await paddedImage.getBuffer(\"image/bmp\"),\n );\n }\n // No padding needed, just return as hex\n console.log(`finalBmpData: ${finalBmpData.length} bytes`);\n return finalBmpData;\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`Failed to load BMP data: ${error.message}`);\n }\n throw new Error(`Failed to load BMP data: Unknown error`);\n }\n }\n\n /**\n * Load multiple BMP frames as hex array for animations\n *\n * @param basePath - Directory containing the frame files\n * @param frameCount - Number of frames to load\n * @param options - Loading options and configuration\n * @returns Promise resolving to array of hex-encoded bitmap data\n *\n * @example\n * ```typescript\n * // Load 10 frames with default pattern\n * const frames = await BitmapUtils.loadBmpFrames('./animations', 10);\n *\n * // Load with custom pattern\n * const customFrames = await BitmapUtils.loadBmpFrames('./sprites', 8, {\n * filePattern: 'sprite_{i}.bmp',\n * startFrame: 0\n * });\n * ```\n */\n static async loadBmpFrames(\n basePath: string,\n frameCount: number,\n options: LoadFramesOptions = {},\n ): Promise<string[]> {\n const {\n filePattern = \"animation_10_frame_{i}.bmp\",\n startFrame = 1,\n validateFrames = true,\n skipMissingFrames = false,\n } = options;\n\n const frames: string[] = [];\n const errors: string[] = [];\n\n for (let i = 0; i < frameCount; i++) {\n const frameNumber = startFrame + i;\n const fileName = filePattern.replace(\"{i}\", frameNumber.toString());\n const filePath = path.join(basePath, fileName);\n\n try {\n const frameBase64 = await this.fileToBase64(filePath);\n\n if (validateFrames) {\n const validation = this.validateBase64Bitmap(frameBase64);\n if (!validation.isValid) {\n const errorMsg = `Frame ${frameNumber} validation failed: ${validation.errors.join(\n \", \",\n )}`;\n if (skipMissingFrames) {\n console.warn(`⚠️ ${errorMsg} - skipping`);\n continue;\n } else {\n throw new Error(errorMsg);\n }\n }\n console.log(\n `✅ Frame ${frameNumber} validated (${validation.blackPixels} black pixels)`,\n );\n }\n\n frames.push(frameBase64);\n } catch (error) {\n const errorMsg = `Failed to load frame ${frameNumber} (${fileName}): ${\n error instanceof Error ? error.message : \"Unknown error\"\n }`;\n\n if (skipMissingFrames) {\n console.warn(`⚠️ ${errorMsg} - skipping`);\n continue;\n } else {\n errors.push(errorMsg);\n }\n }\n }\n\n if (errors.length > 0) {\n throw new Error(`Failed to load frames:\\n${errors.join(\"\\n\")}`);\n }\n\n if (frames.length === 0) {\n throw new Error(`No valid frames loaded from ${basePath}`);\n }\n\n console.log(`📚 Loaded ${frames.length} animation frames from ${basePath}`);\n return frames;\n }\n\n /**\n * Validate BMP hex data integrity and extract metadata\n *\n * @param hexString - Hex-encoded bitmap data\n * @returns Validation result with detailed information\n *\n * @example\n * ```typescript\n * const validation = BitmapUtils.validateBmpHex(bmpHex);\n * if (!validation.isValid) {\n * console.error('Invalid bitmap:', validation.errors);\n * } else {\n * console.log(`Valid bitmap: ${validation.blackPixels} black pixels`);\n * }\n * ```\n */\n static validateBase64Bitmap(bmpFrame: string): BitmapValidation {\n const errors: string[] = [];\n let byteCount = 0;\n let blackPixels = 0;\n const metadata: BitmapValidation[\"metadata\"] = {};\n\n try {\n const hexString = Buffer.from(bmpFrame, \"base64\").toString(\"hex\");\n // Basic hex validation\n if (typeof hexString !== \"string\" || hexString.length === 0) {\n errors.push(\"Hex string is empty or invalid\");\n return { isValid: false, byteCount: 0, blackPixels: 0, errors };\n }\n\n if (hexString.length % 2 !== 0) {\n errors.push(\"Hex string length must be even\");\n return { isValid: false, byteCount: 0, blackPixels: 0, errors };\n }\n\n // Convert to buffer\n const buffer = Buffer.from(hexString, \"hex\");\n byteCount = buffer.length;\n\n // BMP signature validation\n if (buffer.length < 14) {\n errors.push(\n \"File too small to be a valid BMP (minimum 14 bytes for header)\",\n );\n } else {\n if (buffer[0] !== 0x42 || buffer[1] !== 0x4d) {\n errors.push('Invalid BMP signature (should start with \"BM\")');\n }\n }\n\n // Size validation for MentraOS (576x135 = ~9782 bytes expected)\n const expectedSize = 9782;\n if (buffer.length < expectedSize - 100) {\n // Allow some tolerance\n errors.push(\n `BMP too small (${buffer.length} bytes, expected ~${expectedSize})`,\n );\n } else if (buffer.length > expectedSize + 1000) {\n // Allow some tolerance\n errors.push(\n `BMP too large (${buffer.length} bytes, expected ~${expectedSize})`,\n );\n }\n\n // Extract BMP metadata if header is valid\n if (buffer.length >= 54) {\n try {\n // BMP width and height are at offsets 18 and 22 (little-endian)\n const width = buffer.readUInt32LE(18);\n const height = buffer.readUInt32LE(22);\n metadata.dimensions = { width, height };\n metadata.format = \"BMP\";\n\n // Validate dimensions for MentraOS glasses\n if (width !== 576 || height !== 135) {\n errors.push(\n `Invalid dimensions (${width}x${height}, expected 576x135 for MentraOS)`,\n );\n }\n } catch (e) {\n errors.push(\"Failed to parse BMP header metadata\");\n }\n }\n\n // Pixel data validation (assumes 54-byte header + pixel data)\n if (buffer.length > 62) {\n const pixelData = buffer.slice(62); // Skip BMP header\n blackPixels = Array.from(pixelData).filter((b) => b !== 0xff).length;\n\n if (blackPixels === 0) {\n errors.push(\"No black pixels found (image appears to be all white)\");\n }\n } else {\n errors.push(\"File too small to contain pixel data\");\n }\n } catch (error) {\n errors.push(\n `Failed to parse hex data: ${\n error instanceof Error ? error.message : \"Unknown error\"\n }`,\n );\n }\n\n return {\n isValid: errors.length === 0,\n byteCount,\n blackPixels,\n errors,\n metadata: Object.keys(metadata).length > 0 ? metadata : undefined,\n };\n }\n\n /**\n * Convert bitmap data between different formats\n *\n * @param data - Input bitmap data\n * @param fromFormat - Source format ('hex' | 'base64' | 'buffer')\n * @param toFormat - Target format ('hex' | 'base64' | 'buffer')\n * @returns Converted bitmap data\n *\n * @example\n * ```typescript\n * const base64Data = BitmapUtils.convertFormat(hexData, 'hex', 'base64');\n * const bufferData = BitmapUtils.convertFormat(base64Data, 'base64', 'buffer');\n * ```\n */\n static convertFormat(\n data: string | Buffer,\n fromFormat: \"hex\" | \"base64\" | \"buffer\",\n toFormat: \"hex\" | \"base64\" | \"buffer\",\n ): string | Buffer {\n let buffer: Buffer;\n\n // Convert input to buffer\n switch (fromFormat) {\n case \"hex\":\n buffer = Buffer.from(data as string, \"hex\");\n break;\n case \"base64\":\n buffer = Buffer.from(data as string, \"base64\");\n break;\n case \"buffer\":\n buffer = data as Buffer;\n break;\n default:\n throw new Error(`Unsupported source format: ${fromFormat}`);\n }\n\n // Convert buffer to target format\n switch (toFormat) {\n case \"hex\":\n return buffer.toString(\"hex\");\n case \"base64\":\n return buffer.toString(\"base64\");\n case \"buffer\":\n return buffer;\n default:\n throw new Error(`Unsupported target format: ${toFormat}`);\n }\n }\n\n /**\n * Get bitmap information without full validation\n *\n * @param hexString - Hex-encoded bitmap data\n * @returns Basic bitmap information\n *\n * @example\n * ```typescript\n * const info = BitmapUtils.getBitmapInfo(bmpHex);\n * console.log(`Bitmap: ${info.width}x${info.height}, ${info.blackPixels} black pixels`);\n * ```\n */\n static getBitmapInfo(hexString: string): {\n byteCount: number;\n blackPixels: number;\n width?: number;\n height?: number;\n isValidBmp: boolean;\n } {\n try {\n const buffer = Buffer.from(hexString, \"hex\");\n const isValidBmp =\n buffer.length >= 14 && buffer[0] === 0x42 && buffer[1] === 0x4d;\n\n let width: number | undefined;\n let height: number | undefined;\n\n if (isValidBmp && buffer.length >= 54) {\n try {\n width = buffer.readUInt32LE(18);\n height = buffer.readUInt32LE(22);\n } catch (e) {\n // Ignore metadata parsing errors\n }\n }\n\n const pixelData = buffer.slice(62);\n const blackPixels = Array.from(pixelData).filter(\n (b) => b !== 0xff,\n ).length;\n\n return {\n byteCount: buffer.length,\n blackPixels,\n width,\n height,\n isValidBmp,\n };\n } catch (error) {\n return {\n byteCount: 0,\n blackPixels: 0,\n isValidBmp: false,\n };\n }\n }\n}\n",
15
47
  "/**\n * 🎬 Animation Utilities Module\n *\n * Provides helper functions for creating and managing bitmap animations in MentraOS applications.\n * Includes timing utilities, animation factories, and performance optimization helpers.\n *\n * @example\n * ```typescript\n * import { AnimationUtils } from '@mentra/sdk';\n *\n * // Create animation from files\n * const animation = await AnimationUtils.createBitmapAnimation(\n * session, './frames', 10, 1750, true\n * );\n *\n * // Simple delay utility\n * await AnimationUtils.delay(2000);\n *\n * // Stop animation\n * animation.stop();\n * ```\n */\n\nimport { AppSession } from \"../app/session\";\nimport { BitmapUtils, LoadFramesOptions } from \"./bitmap-utils\";\n\n/**\n * Configuration options for bitmap animations\n */\nexport interface AnimationConfig {\n /** Time between frames in milliseconds (default: 1750ms - optimized for MentraOS) */\n intervalMs?: number;\n /** Whether to loop the animation continuously (default: false) */\n repeat?: boolean;\n /** Validate frames before starting animation (default: true) */\n validateFrames?: boolean;\n /** Options for loading frames from files */\n loadOptions?: LoadFramesOptions;\n /** Callback fired when animation starts */\n onStart?: () => void;\n /** Callback fired when animation stops/completes */\n onStop?: () => void;\n /** Callback fired on each frame display */\n onFrame?: (frameIndex: number, totalFrames: number) => void;\n /** Callback fired if animation encounters an error */\n onError?: (error: string) => void;\n}\n\n/**\n * Animation controller interface\n */\nexport interface AnimationController {\n /** Stop the animation */\n stop: () => void;\n /** Check if animation is currently running */\n isRunning: () => boolean;\n /** Get current frame index */\n getCurrentFrame: () => number;\n /** Get total frame count */\n getTotalFrames: () => number;\n}\n\n/**\n * Performance timing information\n */\nexport interface TimingInfo {\n /** Target interval between frames */\n targetInterval: number;\n /** Actual measured interval between frames */\n actualInterval: number;\n /** Timing drift (difference between target and actual) */\n drift: number;\n /** Frame rate (frames per second) */\n fps: number;\n}\n\n/**\n * Utility class for creating and managing animations in MentraOS applications\n */\nexport class AnimationUtils {\n /**\n * Simple async delay helper\n *\n * @param ms - Milliseconds to delay\n * @returns Promise that resolves after the specified delay\n *\n * @example\n * ```typescript\n * console.log('Starting...');\n * await AnimationUtils.delay(2000);\n * console.log('2 seconds later!');\n * ```\n */\n static delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n\n /**\n * Create bitmap animation from files with advanced configuration\n *\n * @param session - MentraOS app session\n * @param basePath - Directory containing animation frames\n * @param frameCount - Number of frames to load\n * @param config - Animation configuration options\n * @returns Promise resolving to animation controller\n *\n * @example\n * ```typescript\n * // Simple animation\n * const animation = await AnimationUtils.createBitmapAnimation(\n * session, './animations', 10\n * );\n *\n * // Advanced configuration\n * const advancedAnimation = await AnimationUtils.createBitmapAnimation(\n * session, './sprites', 8, {\n * intervalMs: 1000,\n * repeat: true,\n * loadOptions: { filePattern: 'sprite_{i}.bmp', startFrame: 0 },\n * onFrame: (frame, total) => console.log(`Frame ${frame}/${total}`),\n * onError: (error) => console.error('Animation error:', error)\n * }\n * );\n * ```\n */\n static async createBitmapAnimation(\n session: AppSession,\n basePath: string,\n frameCount: number,\n config: AnimationConfig = {},\n ): Promise<AnimationController> {\n const {\n intervalMs = 1750, // Optimized for MentraOS hardware\n repeat = false,\n validateFrames = true,\n loadOptions = {},\n onStart,\n onStop,\n onFrame,\n onError,\n } = config;\n\n try {\n console.log(\n `🎬 Loading ${frameCount} animation frames from ${basePath}...`,\n );\n\n // Load frames with validation\n const frames = await BitmapUtils.loadBmpFrames(basePath, frameCount, {\n validateFrames,\n ...loadOptions,\n });\n\n if (frames.length === 0) {\n throw new Error(\"No frames loaded for animation\");\n }\n\n console.log(\n `📚 Animation ready: ${frames.length} frames at ${intervalMs}ms intervals`,\n );\n\n // Create enhanced animation with the loaded frames\n return this.createBitmapAnimationFromFrames(session, frames, {\n intervalMs,\n repeat,\n onStart,\n onStop,\n onFrame,\n onError,\n });\n } catch (error) {\n const errorMsg = `Failed to create animation: ${error instanceof Error ? error.message : \"Unknown error\"}`;\n console.error(`❌ ${errorMsg}`);\n if (onError) {\n onError(errorMsg);\n }\n throw new Error(errorMsg);\n }\n }\n\n /**\n * Create bitmap animation from pre-loaded frame data\n *\n * @param session - MentraOS app session\n * @param frames - Array of hex-encoded bitmap data\n * @param config - Animation configuration options\n * @returns Animation controller\n *\n * @example\n * ```typescript\n * const frames = ['424d461a...', '424d461b...', '424d461c...'];\n * const animation = AnimationUtils.createBitmapAnimationFromFrames(\n * session, frames, { intervalMs: 1500, repeat: true }\n * );\n * ```\n */\n static createBitmapAnimationFromFrames(\n session: AppSession,\n frames: string[],\n config: Omit<AnimationConfig, \"loadOptions\" | \"validateFrames\"> = {},\n ): AnimationController {\n const {\n intervalMs = 1750,\n repeat = false,\n onStart,\n onStop,\n onFrame,\n onError,\n } = config;\n\n let isRunning = false;\n const currentFrame = 0;\n let animationController: { stop: () => void } | null = null;\n\n const controller: AnimationController = {\n stop: () => {\n if (animationController) {\n animationController.stop();\n animationController = null;\n }\n isRunning = false;\n if (onStop) {\n onStop();\n }\n console.log(\"🛑 Animation stopped\");\n },\n\n isRunning: () => isRunning,\n\n getCurrentFrame: () => currentFrame,\n\n getTotalFrames: () => frames.length,\n };\n\n try {\n // Start the animation using the session's built-in method\n animationController = session.layouts.showBitmapAnimation(\n frames,\n intervalMs,\n repeat,\n );\n isRunning = true;\n\n if (onStart) {\n onStart();\n }\n\n console.log(\n `🎬 Animation started: ${frames.length} frames at ${intervalMs}ms${repeat ? \" (repeating)\" : \"\"}`,\n );\n\n // If we have frame callbacks, we need to track timing manually\n // This is a limitation of the current SDK - we can't hook into individual frame displays\n if (onFrame) {\n let frameTracker = 0;\n\n // Call onFrame for the first frame immediately\n onFrame(frameTracker, frames.length);\n\n const frameInterval = setInterval(() => {\n if (!isRunning) {\n clearInterval(frameInterval);\n return;\n }\n\n frameTracker = (frameTracker + 1) % frames.length;\n onFrame(frameTracker, frames.length);\n\n // If not repeating and we've shown all frames, stop tracking\n if (!repeat && frameTracker === frames.length - 1) {\n clearInterval(frameInterval);\n }\n }, intervalMs);\n\n // Override stop to also clear frame tracking\n const originalStop = controller.stop;\n controller.stop = () => {\n clearInterval(frameInterval);\n originalStop();\n };\n }\n } catch (error) {\n const errorMsg = `Failed to start animation: ${error instanceof Error ? error.message : \"Unknown error\"}`;\n console.error(`❌ ${errorMsg}`);\n if (onError) {\n onError(errorMsg);\n }\n throw new Error(errorMsg);\n }\n\n return controller;\n }\n\n /**\n * Create a sequence of bitmap displays with custom timing\n *\n * @param session - MentraOS app session\n * @param sequence - Array of frame data with individual timing\n * @returns Promise that resolves when sequence completes\n *\n * @example\n * ```typescript\n * await AnimationUtils.createBitmapSequence(session, [\n * { frame: frame1Hex, duration: 1000 },\n * { frame: frame2Hex, duration: 500 },\n * { frame: frame3Hex, duration: 2000 }\n * ]);\n * ```\n */\n static async createBitmapSequence(\n session: AppSession,\n sequence: Array<{ frame: string; duration: number }>,\n ): Promise<void> {\n console.log(\n `🎭 Starting bitmap sequence: ${sequence.length} frames with custom timing`,\n );\n\n for (let i = 0; i < sequence.length; i++) {\n const { frame, duration } = sequence[i];\n\n try {\n console.log(\n `📽️ Sequence frame ${i + 1}/${sequence.length} (${duration}ms)`,\n );\n session.layouts.showBitmapView(frame);\n\n if (i < sequence.length - 1) {\n // Don't delay after the last frame\n await this.delay(duration);\n }\n } catch (error) {\n console.error(`❌ Error in sequence frame ${i + 1}:`, error);\n throw error;\n }\n }\n\n console.log(\"✅ Bitmap sequence completed\");\n }\n\n /**\n * Measure animation timing performance\n *\n * @param targetInterval - Expected interval between frames in ms\n * @param measureDuration - How long to measure in ms (default: 10 seconds)\n * @returns Promise resolving to timing performance data\n *\n * @example\n * ```typescript\n * const timing = await AnimationUtils.measureTiming(1750, 10000);\n * console.log(`Target: ${timing.targetInterval}ms, Actual: ${timing.actualInterval}ms`);\n * console.log(`Drift: ${timing.drift}ms, FPS: ${timing.fps.toFixed(1)}`);\n * ```\n */\n static async measureTiming(\n targetInterval: number,\n measureDuration: number = 10000,\n ): Promise<TimingInfo> {\n return new Promise((resolve) => {\n const timestamps: number[] = [];\n const startTime = Date.now();\n\n const measureInterval = setInterval(() => {\n timestamps.push(Date.now());\n }, targetInterval);\n\n setTimeout(() => {\n clearInterval(measureInterval);\n\n if (timestamps.length < 2) {\n resolve({\n targetInterval,\n actualInterval: targetInterval,\n drift: 0,\n fps: 1000 / targetInterval,\n });\n return;\n }\n\n // Calculate actual interval\n const intervals = [];\n for (let i = 1; i < timestamps.length; i++) {\n intervals.push(timestamps[i] - timestamps[i - 1]);\n }\n\n const actualInterval =\n intervals.reduce((a, b) => a + b, 0) / intervals.length;\n const drift = actualInterval - targetInterval;\n const fps = 1000 / actualInterval;\n\n resolve({\n targetInterval,\n actualInterval,\n drift,\n fps,\n });\n }, measureDuration);\n });\n }\n\n /**\n * Create optimized animation settings for different hardware\n *\n * @param deviceType - Target device type\n * @returns Recommended animation configuration\n *\n * @example\n * ```typescript\n * const config = AnimationUtils.getOptimizedConfig('even-realities-g1');\n * const animation = await AnimationUtils.createBitmapAnimation(\n * session, './frames', 10, config\n * );\n * ```\n */\n static getOptimizedConfig(\n deviceType: \"even-realities-g1\" | \"generic\",\n ): AnimationConfig {\n switch (deviceType) {\n case \"even-realities-g1\":\n return {\n intervalMs: 1650, // Tested optimal timing for Even Realities G1\n repeat: false,\n validateFrames: true,\n loadOptions: {\n validateFrames: true,\n skipMissingFrames: false,\n },\n };\n\n case \"generic\":\n default:\n return {\n intervalMs: 1000,\n repeat: false,\n validateFrames: true,\n loadOptions: {\n validateFrames: true,\n skipMissingFrames: false,\n },\n };\n }\n }\n\n /**\n * Preload and cache animation frames for better performance\n *\n * @param basePath - Directory containing frames\n * @param frameCount - Number of frames to preload\n * @param options - Loading options\n * @returns Promise resolving to cached frame data\n *\n * @example\n * ```typescript\n * // Preload frames\n * const cachedFrames = await AnimationUtils.preloadFrames('./animations', 10);\n *\n * // Use cached frames multiple times\n * const animation1 = AnimationUtils.createBitmapAnimationFromFrames(session, cachedFrames);\n * const animation2 = AnimationUtils.createBitmapAnimationFromFrames(session, cachedFrames);\n * ```\n */\n static async preloadFrames(\n basePath: string,\n frameCount: number,\n options: LoadFramesOptions = {},\n ): Promise<string[]> {\n console.log(`📦 Preloading ${frameCount} frames from ${basePath}...`);\n\n const frames = await BitmapUtils.loadBmpFrames(basePath, frameCount, {\n validateFrames: true,\n ...options,\n });\n\n console.log(\n `✅ Preloaded ${frames.length} frames (${frames.reduce((total: number, frame: string) => total + frame.length, 0)} total characters)`,\n );\n\n return frames;\n }\n}\n",
16
- "// src/messages/cloud-to-app.ts\n\nimport {BaseMessage} from \"./base\"\nimport {CloudToAppMessageType, GlassesToCloudMessageType} from \"../message-types\"\nimport {ExtendedStreamType, StreamType} from \"../streams\"\nimport type {AppSettings, AppConfig} from \"../models\"\nimport type {DashboardMode} from \"../dashboard\"\nimport type {Capabilities} from \"../capabilities\"\nimport type {\n LocationUpdate,\n CalendarEvent,\n RtmpStreamStatus,\n PhotoResponse,\n RgbLedControlResponse,\n} from \"./glasses-to-cloud\"\nimport type {AppSession} from \"../../app/session\"\nimport type {GlassesInfo} from \"@mentra/types\"\n\n//===========================================================\n// Responses\n//===========================================================\n\n/**\n * Connection acknowledgment to App\n */\nexport interface AppConnectionAck extends BaseMessage {\n type: CloudToAppMessageType.CONNECTION_ACK\n settings?: AppSettings\n mentraosSettings?: Record<string, any> // MentraOS system settings\n config?: AppConfig // App config sent from cloud\n capabilities?: Capabilities // Device capability profile\n}\n\n/**\n * Connection error to App\n */\nexport interface AppConnectionError extends BaseMessage {\n type: CloudToAppMessageType.CONNECTION_ERROR\n message: string\n code?: string\n}\n\n//===========================================================\n// Permission messages\n//===========================================================\n\n/**\n * Permission error detail for a specific stream\n */\nexport interface PermissionErrorDetail {\n /** The stream type that was rejected */\n stream: string\n /** The permission required for this stream */\n requiredPermission: string\n /** Detailed message explaining the rejection */\n message: string\n}\n\n/**\n * Permission error notification to App\n * Sent when subscriptions are rejected due to missing permissions\n */\nexport interface PermissionError extends BaseMessage {\n type: CloudToAppMessageType.PERMISSION_ERROR\n /** General error message */\n message: string\n /** Array of details for each rejected stream */\n details: PermissionErrorDetail[]\n}\n\n//===========================================================\n// Updates\n//===========================================================\n\n/**\n * App stopped notification to App\n */\nexport interface AppStopped extends BaseMessage {\n type: CloudToAppMessageType.APP_STOPPED\n reason: \"user_disabled\" | \"system_stop\" | \"error\"\n message?: string\n}\n\n/**\n * Settings update to App\n */\nexport interface SettingsUpdate extends BaseMessage {\n type: CloudToAppMessageType.SETTINGS_UPDATE\n packageName: string\n settings: AppSettings\n}\n\n/**\n * Device capabilities update to App\n * Sent when the connected glasses model changes or capabilities are updated\n */\nexport interface CapabilitiesUpdate extends BaseMessage {\n type: CloudToAppMessageType.CAPABILITIES_UPDATE\n capabilities: Capabilities | null\n modelName: string | null\n}\n\n/**\n * Device state update to App\n * Sent when any device state changes (WiFi, battery, hotspot, connection, etc.)\n * Apps receive this automatically - no subscription needed\n */\nexport interface DeviceStateUpdate extends BaseMessage {\n type: CloudToAppMessageType.DEVICE_STATE_UPDATE\n state: Partial<GlassesInfo> // Only changed fields (or full snapshot)\n fullSnapshot?: boolean // True on initial connection or reconnection\n timestamp: Date\n}\n\n/**\n * MentraOS settings update to App\n */\nexport interface MentraosSettingsUpdate extends BaseMessage {\n type: \"augmentos_settings_update\"\n sessionId: string\n settings: Record<string, any>\n timestamp: Date\n}\n\n//===========================================================\n// Audio-related data types\n//===========================================================\n/**\n * Transcription data\n */\nexport interface TranscriptionData extends BaseMessage {\n type: StreamType.TRANSCRIPTION\n text: string // The transcribed text\n isFinal: boolean // Whether this is a final transcription\n utteranceId?: string // Unique ID for this speech segment - interim and final for same utterance share the same ID\n transcribeLanguage?: string // Subscription language code (used for routing, e.g., \"en-US\")\n detectedLanguage?: string // Actual detected language from speech recognition (e.g., \"ja\", \"en\")\n startTime: number // Start time in milliseconds\n endTime: number // End time in milliseconds\n speakerId?: string // ID of the speaker if available (from diarization)\n duration?: number // Audio duration in milliseconds\n provider?: string // The transcription provider (e.g., \"azure\", \"soniox\")\n confidence?: number // Confidence score (0-1)\n metadata?: TranscriptionMetadata // Token-level metadata (always included)\n}\n\n/**\n * Metadata for transcription containing token-level details\n */\nexport interface TranscriptionMetadata {\n provider: \"soniox\" | \"azure\" | string\n soniox?: {\n tokens: SonioxToken[]\n }\n azure?: {\n // Azure-specific metadata can be added later\n tokens?: any[]\n }\n alibaba?: {\n // Alibaba-specific metadata can be added later\n tokens?: any[]\n }\n}\n\n/**\n * Soniox token with word-level details\n */\nexport interface SonioxToken {\n text: string\n startMs?: number\n endMs?: number\n confidence: number\n isFinal: boolean\n speaker?: string\n}\n\n/**\n * Translation data\n */\nexport interface TranslationData extends BaseMessage {\n type: StreamType.TRANSLATION\n text: string // The transcribed text\n originalText?: string // The original transcribed text before translation\n isFinal: boolean // Whether this is a final transcription\n startTime: number // Start time in milliseconds\n endTime: number // End time in milliseconds\n speakerId?: string // ID of the speaker if available\n duration?: number // Audio duration in milliseconds\n transcribeLanguage?: string // The language code of the transcribed text\n translateLanguage?: string // The language code of the translated text\n didTranslate?: boolean // Whether the text was translated\n provider?: string // The translation provider (e.g., \"azure\", \"google\")\n confidence?: number // Confidence score (0-1)\n}\n\n/**\n * Audio chunk data\n */\nexport interface AudioChunk extends BaseMessage {\n type: StreamType.AUDIO_CHUNK\n arrayBuffer: ArrayBufferLike // The audio data\n sampleRate?: number // Audio sample rate (e.g., 16000 Hz)\n}\n\n/**\n * Tool call from cloud to App\n * Represents a tool invocation with filled parameters\n */\nexport interface ToolCall {\n toolId: string // The ID of the tool that was called\n toolParameters: Record<string, string | number | boolean> // The parameters of the tool that was called\n timestamp: Date // Timestamp when the tool was called\n userId: string // ID of the user who triggered the tool call\n activeSession: AppSession | null\n}\n\n//===========================================================\n// Stream data\n//===========================================================\n\n/**\n * Stream data to App\n */\nexport interface DataStream extends BaseMessage {\n type: CloudToAppMessageType.DATA_STREAM\n streamType: ExtendedStreamType\n data: unknown // Type depends on the streamType\n}\n\n//===========================================================\n// Dashboard messages\n//===========================================================\n\n/**\n * Dashboard mode changed notification\n */\nexport interface DashboardModeChanged extends BaseMessage {\n type: CloudToAppMessageType.DASHBOARD_MODE_CHANGED\n mode: DashboardMode\n}\n\n/**\n * Dashboard always-on state changed notification\n */\nexport interface DashboardAlwaysOnChanged extends BaseMessage {\n type: CloudToAppMessageType.DASHBOARD_ALWAYS_ON_CHANGED\n enabled: boolean\n}\n\n/**\n * Standard connection error (for server compatibility)\n */\nexport interface StandardConnectionError extends BaseMessage {\n type: \"connection_error\"\n message: string\n}\n\n/**\n * Custom message for general-purpose communication (cloud to App)\n */\nexport interface CustomMessage extends BaseMessage {\n type: CloudToAppMessageType.CUSTOM_MESSAGE\n action: string // Identifies the specific action/message type\n payload: any // Custom data payload\n}\n\n/**\n * Output status for a re-stream destination\n */\nexport interface OutputStatus {\n /** The destination URL */\n url: string\n /** Friendly name if provided */\n name?: string\n /** Status of this output */\n status: \"active\" | \"error\" | \"stopped\"\n /** Error message if status is error */\n error?: string\n}\n\n/**\n * Managed RTMP stream status update\n * Sent when managed stream status changes or URLs are ready\n */\nexport interface ManagedStreamStatus extends BaseMessage {\n type: CloudToAppMessageType.MANAGED_STREAM_STATUS\n status: \"initializing\" | \"preparing\" | \"active\" | \"stopping\" | \"stopped\" | \"error\"\n hlsUrl?: string\n dashUrl?: string\n webrtcUrl?: string\n /** Cloudflare Stream player/preview URL for embedding */\n previewUrl?: string\n /** Thumbnail image URL */\n thumbnailUrl?: string\n message?: string\n streamId?: string\n /** Status of re-stream outputs if configured */\n outputs?: OutputStatus[]\n}\n\n/**\n * Stream status check response\n * Returns information about any existing streams for the user\n */\nexport interface StreamStatusCheckResponse extends BaseMessage {\n type: CloudToAppMessageType.STREAM_STATUS_CHECK_RESPONSE\n hasActiveStream: boolean\n streamInfo?: {\n type: \"managed\" | \"unmanaged\"\n streamId: string\n status: string\n createdAt: Date\n // For managed streams\n hlsUrl?: string\n dashUrl?: string\n webrtcUrl?: string\n previewUrl?: string\n thumbnailUrl?: string\n activeViewers?: number\n // For unmanaged streams\n rtmpUrl?: string\n requestingAppId?: string\n }\n}\n\n/**\n * Audio play response to App\n */\nexport interface AudioPlayResponse extends BaseMessage {\n type: CloudToAppMessageType.AUDIO_PLAY_RESPONSE\n requestId: string\n success: boolean\n error?: string // Error message (if failed)\n duration?: number // Duration of audio in milliseconds (if successful)\n}\n\n/**\n * Union type for all messages from cloud to Apps\n */\nexport type CloudToAppMessage =\n | AppConnectionAck\n | AppConnectionError\n | StandardConnectionError\n | DataStream\n | AppStopped\n | SettingsUpdate\n | CapabilitiesUpdate\n | DeviceStateUpdate\n | TranscriptionData\n | TranslationData\n | AudioChunk\n | LocationUpdate\n | CalendarEvent\n | PhotoResponse\n | DashboardModeChanged\n | DashboardAlwaysOnChanged\n | CustomMessage\n | ManagedStreamStatus\n | StreamStatusCheckResponse\n | MentraosSettingsUpdate\n // New App-to-App communication response messages\n | AppMessageReceived\n | AppUserJoined\n | AppUserLeft\n | AppRoomUpdated\n | AppDirectMessageResponse\n | RtmpStreamStatus\n | PhotoResponse\n | RgbLedControlResponse\n | PermissionError\n | AudioPlayResponse\n\n//===========================================================\n// Type guards\n//===========================================================\n\nexport function isAppConnectionAck(message: CloudToAppMessage): message is AppConnectionAck {\n return message.type === CloudToAppMessageType.CONNECTION_ACK\n}\n\nexport function isAppConnectionError(message: CloudToAppMessage): message is AppConnectionError {\n return message.type === CloudToAppMessageType.CONNECTION_ERROR || (message as any).type === \"connection_error\"\n}\n\nexport function isAppStopped(message: CloudToAppMessage): message is AppStopped {\n return message.type === CloudToAppMessageType.APP_STOPPED\n}\n\nexport function isSettingsUpdate(message: CloudToAppMessage): message is SettingsUpdate {\n return message.type === CloudToAppMessageType.SETTINGS_UPDATE\n}\n\nexport function isCapabilitiesUpdate(message: CloudToAppMessage): message is CapabilitiesUpdate {\n return message.type === CloudToAppMessageType.CAPABILITIES_UPDATE\n}\n\nexport function isDeviceStateUpdate(message: CloudToAppMessage): message is DeviceStateUpdate {\n return message.type === CloudToAppMessageType.DEVICE_STATE_UPDATE\n}\n\nexport function isDataStream(message: CloudToAppMessage): message is DataStream {\n return message.type === CloudToAppMessageType.DATA_STREAM\n}\n\nexport function isAudioChunk(message: CloudToAppMessage): message is AudioChunk {\n return message.type === StreamType.AUDIO_CHUNK\n}\n\nexport function isDashboardModeChanged(message: CloudToAppMessage): message is DashboardModeChanged {\n return message.type === CloudToAppMessageType.DASHBOARD_MODE_CHANGED\n}\n\nexport function isDashboardAlwaysOnChanged(message: CloudToAppMessage): message is DashboardAlwaysOnChanged {\n return message.type === CloudToAppMessageType.DASHBOARD_ALWAYS_ON_CHANGED\n}\n\nexport function isManagedStreamStatus(message: CloudToAppMessage): message is ManagedStreamStatus {\n return message.type === CloudToAppMessageType.MANAGED_STREAM_STATUS\n}\n\nexport function isRtmpStreamStatus(message: CloudToAppMessage): message is RtmpStreamStatus {\n return message.type === GlassesToCloudMessageType.RTMP_STREAM_STATUS\n}\n\nexport function isPhotoResponse(message: CloudToAppMessage): message is PhotoResponse {\n return message.type === GlassesToCloudMessageType.PHOTO_RESPONSE\n}\n\nexport function isRgbLedControlResponse(message: CloudToAppMessage): message is RgbLedControlResponse {\n return message.type === GlassesToCloudMessageType.RGB_LED_CONTROL_RESPONSE\n}\n\nexport function isStreamStatusCheckResponse(message: CloudToAppMessage): message is StreamStatusCheckResponse {\n return message.type === CloudToAppMessageType.STREAM_STATUS_CHECK_RESPONSE\n}\n\nexport function isAudioPlayResponse(message: CloudToAppMessage): message is AudioPlayResponse {\n return message.type === CloudToAppMessageType.AUDIO_PLAY_RESPONSE\n}\n\n// New type guards for App-to-App communication\nexport function isAppMessageReceived(message: CloudToAppMessage): message is AppMessageReceived {\n return message.type === CloudToAppMessageType.APP_MESSAGE_RECEIVED\n}\n\nexport function isAppUserJoined(message: CloudToAppMessage): message is AppUserJoined {\n return message.type === CloudToAppMessageType.APP_USER_JOINED\n}\n\nexport function isAppUserLeft(message: CloudToAppMessage): message is AppUserLeft {\n return message.type === CloudToAppMessageType.APP_USER_LEFT\n}\n\n//===========================================================\n// App-to-App Communication Response Messages\n//===========================================================\n\n/**\n * Message received from another App user\n */\nexport interface AppMessageReceived extends BaseMessage {\n type: CloudToAppMessageType.APP_MESSAGE_RECEIVED\n payload: any\n messageId: string\n senderUserId: string\n senderSessionId: string\n roomId?: string\n}\n\n/**\n * Notification that a user joined the App\n */\nexport interface AppUserJoined extends BaseMessage {\n type: CloudToAppMessageType.APP_USER_JOINED\n userId: string\n sessionId: string\n joinedAt: Date\n userProfile?: any\n roomId?: string\n}\n\n/**\n * Notification that a user left the App\n */\nexport interface AppUserLeft extends BaseMessage {\n type: CloudToAppMessageType.APP_USER_LEFT\n userId: string\n sessionId: string\n leftAt: Date\n roomId?: string\n}\n\n/**\n * Room status update (members, config changes, etc.)\n */\nexport interface AppRoomUpdated extends BaseMessage {\n type: CloudToAppMessageType.APP_ROOM_UPDATED\n roomId: string\n updateType: \"user_joined\" | \"user_left\" | \"config_changed\" | \"room_closed\"\n roomData: {\n memberCount: number\n maxUsers?: number\n isPrivate?: boolean\n metadata?: any\n }\n}\n\n/**\n * Response to a direct message attempt\n */\nexport interface AppDirectMessageResponse extends BaseMessage {\n type: CloudToAppMessageType.APP_DIRECT_MESSAGE_RESPONSE\n messageId: string\n success: boolean\n error?: string\n targetUserId: string\n}\n\n//===========================================================\n// Cloud-to-Sdk Communication Response Messages\n//===========================================================\n\n/**\n * Permission data structures for permission fetch responses\n */\nexport interface Permission {\n type: string // or a union/enum if you want stricter typing\n description: string\n _id: string\n}\n\n/**\n * Package permissions response structure\n */\nexport interface PackagePermissions {\n packageName: string\n permissions: Permission[]\n}\n",
17
- "// src/enums.ts\n\n/**\n * Types of Third-Party Applications (Apps)\n */\nexport enum AppType {\n SYSTEM_DASHBOARD = \"system_dashboard\", // Special UI placement, system functionality\n BACKGROUND = \"background\", // Can temporarily take control of display\n STANDARD = \"standard\", // Regular App (default) only one standard app can run at a time. starting a standard App will close any other standard App that is running.\n}\n\n/**\n * Types of layouts for displaying content\n */\nexport enum LayoutType {\n TEXT_WALL = \"text_wall\",\n DOUBLE_TEXT_WALL = \"double_text_wall\",\n DASHBOARD_CARD = \"dashboard_card\",\n REFERENCE_CARD = \"reference_card\",\n BITMAP_VIEW = \"bitmap_view\",\n BITMAP_ANIMATION = \"bitmap_animation\",\n CLEAR_VIEW = \"clear_view\",\n}\n\n/**\n * Types of views for displaying content\n */\nexport enum ViewType {\n DASHBOARD = \"dashboard\", // Regular dashboard (main/expanded)\n // ALWAYS_ON = \"always_on\", // Persistent overlay dashboard\n MAIN = \"main\", // Regular app content\n}\n\n// Types for AppSettings\nexport enum AppSettingType {\n TOGGLE = \"toggle\",\n TEXT = \"text\",\n SELECT = \"select\",\n SLIDER = \"slider\",\n GROUP = \"group\",\n TEXT_NO_SAVE_BUTTON = \"text_no_save_button\",\n SELECT_WITH_SEARCH = \"select_with_search\",\n MULTISELECT = \"multiselect\",\n TITLE_VALUE = \"titleValue\",\n NUMERIC_INPUT = \"numeric_input\",\n TIME_PICKER = \"time_picker\",\n}\n// | { type: \"toggle\"; key: string; label: string; defaultValue: boolean }\n// | { type: \"text\"; key: string; label: string; defaultValue?: string }\n// | { type: \"select\"; key: string; label: string; options: { label: string; value: string }[]; defaultValue?: string };\n\n/**\n * Types of hardware components that apps can require\n */\nexport enum HardwareType {\n CAMERA = \"CAMERA\",\n DISPLAY = \"DISPLAY\",\n MICROPHONE = \"MICROPHONE\",\n SPEAKER = \"SPEAKER\",\n IMU = \"IMU\",\n BUTTON = \"BUTTON\",\n LIGHT = \"LIGHT\",\n WIFI = \"WIFI\",\n}\n\n/**\n * Levels of hardware requirements\n */\nexport enum HardwareRequirementLevel {\n REQUIRED = \"REQUIRED\", // App cannot function without this hardware\n OPTIONAL = \"OPTIONAL\", // App has enhanced features with this hardware\n}\n",
18
- "// @mentra/sdk\n// packages/sdk/types/src/models.ts - Core models\n\nimport {\n AppSettingType,\n AppType,\n HardwareType,\n HardwareRequirementLevel,\n} from \"./enums\";\n\n// Tool parameter type definition\nexport interface ToolParameterSchema {\n type: \"string\" | \"number\" | \"boolean\";\n description: string;\n enum?: string[];\n required?: boolean;\n}\n\n// Tool schema definition for Apps\nexport interface ToolSchema {\n id: string;\n description: string;\n activationPhrases?: string[];\n parameters?: Record<string, ToolParameterSchema>;\n}\n\n/**\n * Developer profile information\n */\nexport interface DeveloperProfile {\n company?: string;\n website?: string;\n contactEmail?: string;\n description?: string;\n logo?: string;\n}\n\n// Define PermissionType enum with legacy support\nexport enum PermissionType {\n MICROPHONE = \"MICROPHONE\",\n LOCATION = \"LOCATION\",\n BACKGROUND_LOCATION = \"BACKGROUND_LOCATION\",\n CALENDAR = \"CALENDAR\",\n CAMERA = \"CAMERA\",\n\n // Legacy notification permission (backward compatibility)\n NOTIFICATIONS = \"NOTIFICATIONS\",\n\n // New granular notification permissions\n READ_NOTIFICATIONS = \"READ_NOTIFICATIONS\",\n POST_NOTIFICATIONS = \"POST_NOTIFICATIONS\",\n\n ALL = \"ALL\",\n}\n\n// Legacy permission mapping for backward compatibility\nexport const LEGACY_PERMISSION_MAP = new Map<PermissionType, PermissionType[]>([\n [PermissionType.NOTIFICATIONS, [PermissionType.READ_NOTIFICATIONS]],\n]);\n\n// Permission interface\nexport interface Permission {\n type: PermissionType;\n description?: string;\n}\n\n/**\n * Hardware requirement for an app\n */\nexport interface HardwareRequirement {\n type: HardwareType;\n level: HardwareRequirementLevel;\n description?: string; // Why this hardware is needed\n}\n\n/**\n * Base interface for applications\n */\nexport interface AppI {\n packageName: string;\n name: string;\n publicUrl: string; // Base URL of the app server\n isSystemApp?: boolean; // Is this a system app?\n uninstallable?: boolean; // Can the app be uninstalled?\n\n webviewURL?: string; // URL for phone UI\n logoURL: string;\n appType: AppType; // Type of app\n appStoreId?: string; // Which app store registered this app\n\n /**\n * @deprecated Use organizationId instead. Will be removed after migration.\n */\n developerId?: string; // ID of the developer who created the app\n organizationId?: any; // ID of the organization that owns this app\n\n // Auth\n hashedEndpointSecret?: string;\n hashedApiKey?: string;\n\n // App details\n permissions?: Permission[];\n description?: string;\n version?: string;\n settings?: AppSettings;\n tools?: ToolSchema[];\n\n /**\n * Hardware requirements for the app\n * If not specified, app is assumed to work with any hardware\n */\n hardwareRequirements?: HardwareRequirement[];\n\n isPublic?: boolean;\n appStoreStatus?: \"DEVELOPMENT\" | \"SUBMITTED\" | \"REJECTED\" | \"PUBLISHED\";\n}\n\n/**\n * Base interface for all app settings\n */\nexport interface BaseAppSetting {\n key: string;\n label: string;\n value?: any; // User's selected value\n defaultValue?: any; // System default\n}\n\n/**\n * Setting types for applications\n */\nexport type AppSetting =\n | (BaseAppSetting & {\n type: AppSettingType.TOGGLE;\n defaultValue: boolean;\n value?: boolean;\n })\n | (BaseAppSetting & {\n type: AppSettingType.TEXT;\n defaultValue?: string;\n value?: string;\n })\n | (BaseAppSetting & {\n type: AppSettingType.TEXT_NO_SAVE_BUTTON;\n defaultValue?: string;\n value?: string;\n maxLines?: number;\n })\n | (BaseAppSetting & {\n type: AppSettingType.SELECT;\n options: { label: string; value: any }[];\n defaultValue?: any;\n value?: any;\n })\n | (BaseAppSetting & {\n type: AppSettingType.SELECT_WITH_SEARCH;\n options: { label: string; value: any }[];\n defaultValue?: any;\n value?: any;\n })\n | (BaseAppSetting & {\n type: AppSettingType.MULTISELECT;\n options: { label: string; value: any }[];\n defaultValue?: any[];\n value?: any[];\n })\n | (BaseAppSetting & {\n type: AppSettingType.SLIDER;\n min: number;\n max: number;\n defaultValue: number;\n value?: number;\n })\n | (BaseAppSetting & {\n type: AppSettingType.NUMERIC_INPUT;\n min?: number;\n max?: number;\n step?: number;\n placeholder?: string;\n defaultValue?: number;\n value?: number;\n })\n | (BaseAppSetting & {\n type: AppSettingType.TIME_PICKER;\n showSeconds?: boolean;\n defaultValue?: number; // Total seconds\n value?: number; // Total seconds\n })\n | (BaseAppSetting & {\n type: AppSettingType.GROUP;\n title: string;\n })\n | (BaseAppSetting & {\n type: AppSettingType.TITLE_VALUE;\n label: string;\n value: any;\n key?: never; // TITLE_VALUE settings don't need keys since they're display-only\n });\n\nexport type AppSettings = AppSetting[];\n\n/**\n * App configuration file structure\n * Represents the schema in app_config.json\n */\nexport interface AppConfig {\n name: string;\n description: string;\n version: string;\n settings: AppSetting[];\n tools: ToolSchema[];\n}\n\n/**\n * Validate a App configuration object\n * @param config Object to validate\n * @returns True if the config is valid\n */\nexport function validateAppConfig(config: any): config is AppConfig {\n if (!config || typeof config !== \"object\") return false;\n\n // Check required string properties\n if (\n typeof config.name !== \"string\" ||\n typeof config.description !== \"string\" ||\n typeof config.version !== \"string\"\n ) {\n return false;\n }\n\n // Check settings array\n if (!Array.isArray(config.settings)) return false;\n\n // Validate each setting\n return config.settings.every((setting: any) => {\n // Group settings just need a title\n if (setting.type === \"group\") {\n return typeof setting.title === \"string\";\n }\n\n // TITLE_VALUE settings just need label and value\n if (setting.type === \"titleValue\") {\n return typeof setting.label === \"string\" && \"value\" in setting;\n }\n\n // Regular settings need key and label\n if (typeof setting.key !== \"string\" || typeof setting.label !== \"string\") {\n return false;\n }\n\n // Type-specific validation\n switch (setting.type) {\n case AppSettingType.TOGGLE:\n return typeof setting.defaultValue === \"boolean\";\n\n case AppSettingType.TEXT:\n case AppSettingType.TEXT_NO_SAVE_BUTTON:\n return (\n setting.defaultValue === undefined ||\n typeof setting.defaultValue === \"string\"\n );\n\n case AppSettingType.SELECT:\n case AppSettingType.SELECT_WITH_SEARCH:\n return (\n Array.isArray(setting.options) &&\n setting.options.every(\n (opt: any) => typeof opt.label === \"string\" && \"value\" in opt,\n )\n );\n\n case AppSettingType.MULTISELECT:\n return (\n Array.isArray(setting.options) &&\n setting.options.every(\n (opt: any) => typeof opt.label === \"string\" && \"value\" in opt,\n ) &&\n (setting.defaultValue === undefined ||\n Array.isArray(setting.defaultValue))\n );\n\n case AppSettingType.SLIDER:\n return (\n typeof setting.defaultValue === \"number\" &&\n typeof setting.min === \"number\" &&\n typeof setting.max === \"number\" &&\n setting.min <= setting.max\n );\n\n case AppSettingType.NUMERIC_INPUT:\n return (\n (setting.defaultValue === undefined ||\n typeof setting.defaultValue === \"number\") &&\n (setting.min === undefined || typeof setting.min === \"number\") &&\n (setting.max === undefined || typeof setting.max === \"number\") &&\n (setting.step === undefined || typeof setting.step === \"number\") &&\n (setting.placeholder === undefined ||\n typeof setting.placeholder === \"string\")\n );\n\n case AppSettingType.TIME_PICKER:\n return (\n (setting.defaultValue === undefined ||\n typeof setting.defaultValue === \"number\") &&\n (setting.showSeconds === undefined ||\n typeof setting.showSeconds === \"boolean\")\n );\n\n case AppSettingType.GROUP:\n return typeof setting.title === \"string\";\n\n case AppSettingType.TITLE_VALUE:\n return typeof setting.label === \"string\" && \"value\" in setting;\n\n default:\n return false;\n }\n });\n}\n\n/**\n * Transcript segment for speech processing\n */\nexport interface TranscriptSegment {\n speakerId?: string;\n resultId: string;\n text: string;\n timestamp: Date;\n isFinal: boolean;\n}\n\n/**\n * Complete transcript\n */\nexport interface TranscriptI {\n segments: TranscriptSegment[];\n languageSegments?: Map<string, TranscriptSegment[]>; // Language-indexed map for multi-language support\n}\n",
19
- "// src/webhook.ts\n\n/**\n * Types of webhook requests that can be sent to Apps\n */\nexport enum WebhookRequestType {\n /** Request to start a App session */\n SESSION_REQUEST = \"session_request\",\n\n /** Request to stop a App session */\n STOP_REQUEST = \"stop_request\",\n}\n\n/**\n * Base interface for all webhook requests\n */\nexport interface BaseWebhookRequest {\n /** Type of webhook request */\n type: WebhookRequestType;\n\n /** Session ID for the request */\n sessionId: string;\n\n /** User ID associated with the session */\n userId: string;\n\n /** Timestamp of the request */\n timestamp: string;\n}\n\n/**\n * Session request webhook\n *\n * Sent to a App when a user starts the App\n */\nexport interface SessionWebhookRequest extends BaseWebhookRequest {\n type: WebhookRequestType.SESSION_REQUEST;\n mentraOSWebsocketUrl?: string;\n augmentOSWebsocketUrl?: string;\n}\n\n/**\n * Stop request webhook\n *\n * Sent to a App when a user or the system stops the App\n */\nexport interface StopWebhookRequest extends BaseWebhookRequest {\n type: WebhookRequestType.STOP_REQUEST;\n reason: \"user_disabled\" | \"system_stop\" | \"error\";\n}\n\n/**\n * Union type for all webhook requests\n */\nexport type WebhookRequest = SessionWebhookRequest | StopWebhookRequest;\n\n/**\n * Response to a webhook request\n */\nexport interface WebhookResponse {\n status: \"success\" | \"error\";\n message?: string;\n}\n\n/**\n * Type guard to check if a webhook request is a session request\n */\nexport function isSessionWebhookRequest(\n request: WebhookRequest,\n): request is SessionWebhookRequest {\n return request.type === WebhookRequestType.SESSION_REQUEST;\n}\n\n/**\n * Type guard to check if a webhook request is a stop request\n */\nexport function isStopWebhookRequest(\n request: WebhookRequest,\n): request is StopWebhookRequest {\n return request.type === WebhookRequestType.STOP_REQUEST;\n}\n",
20
- "/**\n * 🚀 App Server Module\n *\n * Creates and manages a server for Apps in the MentraOS ecosystem.\n * Handles webhook endpoints, session management, and cleanup.\n */\nimport express, { type Express } from \"express\";\nimport path from \"path\";\nimport fs from \"fs\";\nimport { AppSession } from \"../session/index\";\nimport { createAuthMiddleware } from \"../webview\";\nimport { newSDKUpdate } from \"../../constants/log-messages/updates\";\n\nimport {\n WebhookRequest,\n WebhookResponse,\n SessionWebhookRequest,\n StopWebhookRequest,\n ToolCall,\n WebhookRequestType,\n PhotoData,\n} from \"../../types\";\n\nimport { Logger } from \"pino\";\nimport { logger as rootLogger } from \"../../logging/logger\";\nimport axios from \"axios\";\n\nexport const GIVE_APP_CONTROL_OF_TOOL_RESPONSE: string = \"GIVE_APP_CONTROL_OF_TOOL_RESPONSE\";\n\n/**\n * 🔧 Configuration options for App Server\n *\n * @example\n * ```typescript\n * const config: AppServerConfig = {\n * packageName: 'org.example.myapp',\n * apiKey: 'your_api_key',\n * port: 7010,\n * publicDir: './public'\n * };\n * ```\n */\nexport interface AppServerConfig {\n /** 📦 Unique identifier for your App (e.g., 'org.company.appname') must match what you specified at https://console.mentra.glass */\n packageName: string;\n /** 🔑 API key for authentication with MentraOS Cloud */\n apiKey: string;\n /** 🌐 Port number for the server (default: 7010) */\n port?: number;\n\n /** Cloud API URL (default: 'api.mentra.glass') */\n cloudApiUrl?: string;\n\n /** 🛣️ [DEPRECATED] do not set: The SDK will automatically expose an endpoint at '/webhook' */\n webhookPath?: string;\n /**\n * 📂 Directory for serving static files (e.g., images, logos)\n * Set to false to disable static file serving\n */\n publicDir?: string | false;\n\n /** ❤️ Enable health check endpoint at /health (default: true) */\n healthCheck?: boolean;\n /**\n * 🔐 Secret key used to sign session cookies\n * This must be a strong, unique secret\n */\n cookieSecret?: string;\n /** App instructions string shown to the user */\n appInstructions?: string;\n}\n\n/**\n * 🎯 App Server Implementation\n *\n * Base class for creating App servers. Handles:\n * - 🔄 Session lifecycle management\n * - 📡 Webhook endpoints for MentraOS Cloud\n * - 📂 Static file serving\n * - ❤️ Health checks\n * - 🧹 Cleanup on shutdown\n *\n * @example\n * ```typescript\n * class MyAppServer extends AppServer {\n * protected async onSession(session: AppSession, sessionId: string, userId: string) {\n * // Handle new user sessions here\n * session.events.onTranscription((data) => {\n * session.layouts.showTextWall(data.text);\n * });\n * }\n * }\n *\n * const server = new MyAppServer({\n * packageName: 'org.example.myapp',\n * apiKey: 'your_api_key',\n * publicDir: \"/public\",\n * });\n *\n * await server.start();\n * ```\n */\n/** Registry entry for photo requests that persists across session reconnections */\ninterface PhotoRequestRegistryEntry {\n userId: string;\n createdAt: number;\n resolve: (photoData: PhotoData) => void;\n reject: (reason: string) => void;\n}\n\nexport class AppServer {\n /** Express app instance */\n private app: Express;\n /** Map of active user sessions by sessionId */\n private activeSessions = new Map<string, AppSession>();\n /** Map of active user sessions by userId */\n private activeSessionsByUserId = new Map<string, AppSession>();\n /** Array of cleanup handlers to run on shutdown */\n private cleanupHandlers: Array<() => void> = [];\n /** App instructions string shown to the user */\n private appInstructions: string | null = null;\n /**\n * Registry for photo requests that persists across session lifecycle.\n * This fixes race conditions where sessions reconnect during photo capture.\n */\n private photoRequestRegistry = new Map<string, PhotoRequestRegistryEntry>();\n\n public readonly logger: Logger;\n\n constructor(private config: AppServerConfig) {\n // Set defaults and merge with provided config\n this.config = {\n port: 7010,\n webhookPath: \"/webhook\",\n publicDir: false,\n healthCheck: true,\n ...config,\n };\n\n this.logger = rootLogger.child({\n app: this.config.packageName,\n packageName: this.config.packageName,\n service: \"app-server\",\n });\n\n // Initialize Express app\n this.app = express();\n this.app.use(express.json());\n\n const cookieParser = require(\"cookie-parser\");\n this.app.use(\n cookieParser(this.config.cookieSecret || `AOS_${this.config.packageName}_${this.config.apiKey.substring(0, 8)}`),\n );\n\n // Apply authentication middleware\n this.app.use(\n createAuthMiddleware({\n apiKey: this.config.apiKey,\n packageName: this.config.packageName,\n getAppSessionForUser: (userId: string) => {\n return this.activeSessionsByUserId.get(userId) || null;\n },\n cookieSecret:\n this.config.cookieSecret || `AOS_${this.config.packageName}_${this.config.apiKey.substring(0, 8)}`,\n }) as any,\n );\n\n this.appInstructions = (config as any).appInstructions || null;\n\n // Setup server features\n this.setupWebhook();\n this.setupSettingsEndpoint();\n this.setupHealthCheck();\n this.setupToolCallEndpoint();\n this.setupPhotoUploadEndpoint();\n this.setupMentraAuthRedirect();\n this.setupPublicDir();\n this.setupShutdown();\n }\n\n // Expose Express app for custom routes.\n // This is useful for adding custom API routes or middleware.\n public getExpressApp(): Express {\n return this.app;\n }\n\n /**\n * 👥 Session Handler\n * Override this method to handle new App sessions.\n * This is where you implement your app's core functionality.\n *\n * @param session - App session instance for the user\n * @param sessionId - Unique identifier for this session\n * @param userId - User's identifier\n */\n protected async onSession(session: AppSession, sessionId: string, userId: string): Promise<void> {\n this.logger.info(`🚀 Starting new session handling for session ${sessionId} and user ${userId}`);\n // Core session handling logic (onboarding removed)\n this.logger.info(`✅ Session handling completed for session ${sessionId} and user ${userId}`);\n }\n\n /**\n * 👥 Stop Handler\n * Override this method to handle stop requests.\n * This is where you can clean up resources when a session is stopped.\n *\n * @param sessionId - Unique identifier for this session\n * @param userId - User's identifier\n * @param reason - Reason for stopping\n */\n protected async onStop(sessionId: string, userId: string, reason: string): Promise<void> {\n this.logger.debug(`Session ${sessionId} stopped for user ${userId}. Reason: ${reason}`);\n\n // Default implementation: close the session if it exists\n const session = this.activeSessions.get(sessionId);\n if (session) {\n session.disconnect();\n this.activeSessions.delete(sessionId);\n this.activeSessionsByUserId.delete(userId);\n }\n }\n\n /**\n * 🛠️ Tool Call Handler\n * Override this method to handle tool calls from MentraOS Cloud.\n * This is where you implement your app's tool functionality.\n *\n * @param toolCall - The tool call request containing tool details and parameters\n * @returns Optional string response that will be sent back to MentraOS Cloud\n */\n protected async onToolCall(toolCall: ToolCall): Promise<string | undefined> {\n this.logger.debug(`Tool call received: ${toolCall.toolId}`);\n this.logger.debug(`Parameters: ${JSON.stringify(toolCall.toolParameters)}`);\n return undefined;\n }\n\n /**\n * 🚀 Start the Server\n * Starts listening for incoming connections and webhook calls.\n *\n * @returns Promise that resolves when server is ready\n */\n public start(): Promise<void> {\n return new Promise((resolve) => {\n this.app.listen(this.config.port, async () => {\n this.logger.info(`🎯 App server running at http://localhost:${this.config.port}`);\n if (this.config.publicDir) {\n this.logger.info(`📂 Serving static files from ${this.config.publicDir}`);\n }\n\n // 🔑 Grab SDK version\n try {\n // Look for the actual installed @mentra/sdk package.json in node_modules\n const sdkPkgPath = path.resolve(process.cwd(), \"node_modules/@mentra/sdk/package.json\");\n\n let currentVersion = \"unknown\";\n\n if (fs.existsSync(sdkPkgPath)) {\n const sdkPkg = JSON.parse(fs.readFileSync(sdkPkgPath, \"utf-8\"));\n\n // Get the actual installed version\n currentVersion = sdkPkg.version || \"not-found\"; // located in the node module\n } else {\n this.logger.debug({ sdkPkgPath }, \"No @mentra/sdk package.json found at path\");\n }\n\n // this.logger.debug(`Developer is using SDK version: ${currentVersion}`);\n\n // Fetch latest SDK version from the API endpoint\n let latest: string | null = null;\n try {\n const cloudHost = \"api.mentra.glass\";\n const response = await axios.get(\n `https://${cloudHost}/api/sdk/version`,\n { timeout: 3000 }, // 3 second timeout\n );\n if (response.data && response.data.success && response.data.data) {\n latest = response.data.data.latest;\n }\n } catch {\n this.logger.debug(\n \"Failed to fetch latest SDK version - skipping version check (offline or API unavailable)\",\n );\n }\n\n if (currentVersion === \"not-found\") {\n this.logger.warn(\n `⚠️ @mentra/sdk not found in your project dependencies. Please install it with: npm install @mentra/sdk`,\n );\n } else if (latest && latest !== currentVersion) {\n this.logger.warn(newSDKUpdate(latest));\n }\n } catch (err) {\n this.logger.error(err, \"Version check failed\");\n }\n\n resolve();\n });\n });\n }\n\n /**\n * 🛑 Stop the Server\n * Gracefully shuts down the server and cleans up all sessions.\n */\n public async stop(): Promise<void> {\n this.logger.info(\"\\n🛑 Shutting down...\");\n await this.cleanup();\n process.exit(0);\n }\n\n /**\n * 🔐 Generate a App token for a user\n * This should be called when handling a session webhook request.\n *\n * @param userId - User identifier\n * @param sessionId - Session identifier\n * @param secretKey - Secret key for signing the token\n * @returns JWT token string\n */\n protected generateToken(userId: string, sessionId: string, secretKey: string): string {\n const { createToken } = require(\"../token/utils\");\n return createToken(\n {\n userId,\n packageName: this.config.packageName,\n sessionId,\n },\n { secretKey },\n );\n }\n\n /**\n * 🧹 Add Cleanup Handler\n * Register a function to be called during server shutdown.\n *\n * @param handler - Function to call during cleanup\n */\n protected addCleanupHandler(handler: () => void): void {\n this.cleanupHandlers.push(handler);\n }\n\n /**\n * 🎯 Setup Webhook Endpoint\n * Creates the webhook endpoint that MentraOS Cloud calls to start new sessions.\n */\n private setupWebhook(): void {\n if (!this.config.webhookPath) {\n this.logger.error(\"❌ Webhook path not set\");\n throw new Error(\"Webhook path not set\");\n }\n\n this.app.post(this.config.webhookPath, async (req, res) => {\n try {\n const webhookRequest = req.body as WebhookRequest;\n\n // Handle session request\n if (webhookRequest.type === WebhookRequestType.SESSION_REQUEST) {\n await this.handleSessionRequest(webhookRequest, res);\n }\n // Handle stop request\n else if (webhookRequest.type === WebhookRequestType.STOP_REQUEST) {\n await this.handleStopRequest(webhookRequest, res);\n }\n // Unknown webhook type\n else {\n this.logger.error(\"❌ Unknown webhook request type\");\n res.status(400).json({\n status: \"error\",\n message: \"Unknown webhook request type\",\n } as WebhookResponse);\n }\n } catch (error) {\n this.logger.error(error, \"❌ Error handling webhook: \" + (error as Error).message);\n res.status(500).json({\n status: \"error\",\n message: \"Error handling webhook: \" + (error as Error).message,\n } as WebhookResponse);\n }\n });\n }\n\n /**\n * 🛠️ Setup Tool Call Endpoint\n * Creates a /tool endpoint for handling tool calls from MentraOS Cloud.\n */\n private setupToolCallEndpoint(): void {\n this.app.post(\"/tool\", async (req, res) => {\n try {\n const toolCall = req.body as ToolCall;\n if (this.activeSessionsByUserId.has(toolCall.userId)) {\n toolCall.activeSession = this.activeSessionsByUserId.get(toolCall.userId) || null;\n } else {\n toolCall.activeSession = null;\n }\n this.logger.info({ body: req.body }, `🔧 Received tool call: ${toolCall.toolId}`);\n // Call the onToolCall handler and get the response\n const response = await this.onToolCall(toolCall);\n\n // Send back the response if one was provided\n if (response !== undefined) {\n res.json({ status: \"success\", reply: response });\n } else {\n res.json({ status: \"success\", reply: null });\n }\n } catch (error) {\n this.logger.error(error, \"❌ Error handling tool call:\");\n res.status(500).json({\n status: \"error\",\n message: error instanceof Error ? error.message : \"Unknown error occurred calling tool\",\n });\n }\n });\n this.app.get(\"/tool\", async (req, res) => {\n res.json({ status: \"success\", reply: \"Hello, world!\" });\n });\n }\n\n /**\n * Handle a session request webhook\n */\n private async handleSessionRequest(request: SessionWebhookRequest, res: express.Response): Promise<void> {\n const { sessionId, userId, mentraOSWebsocketUrl, augmentOSWebsocketUrl } = request;\n this.logger.info({ userId }, `🗣️ Received session request for user ${userId}, session ${sessionId}\\n\\n`);\n\n // Check for existing session (user might be switching clouds)\n // If an existing session exists, we need to clean it up properly to avoid:\n // 1. Orphaned sessions with open WebSockets\n // 2. Cleanup handlers that corrupt the new session's map entries\n // See: cloud/issues/018-app-disconnect-resurrection\n const existingSession = this.activeSessions.get(sessionId);\n if (existingSession) {\n this.logger.info(\n { sessionId, userId },\n `🔄 Existing session found for ${sessionId} - sending OWNERSHIP_RELEASE and disconnecting before new connection`,\n );\n\n try {\n // Send OWNERSHIP_RELEASE to tell the old cloud not to resurrect this app\n // The old cloud will mark the app as DORMANT instead of trying to restart it\n await existingSession.releaseOwnership(\"switching_clouds\");\n } catch (error) {\n this.logger.warn(\n { error, sessionId },\n `⚠️ Failed to send OWNERSHIP_RELEASE to old session - continuing anyway`,\n );\n }\n\n try {\n // Disconnect the old session explicitly\n existingSession.disconnect();\n } catch (error) {\n this.logger.warn({ error, sessionId }, `⚠️ Failed to disconnect old session - continuing anyway`);\n }\n\n // Remove from maps immediately (don't wait for cleanup handler)\n this.activeSessions.delete(sessionId);\n this.activeSessionsByUserId.delete(userId);\n\n this.logger.info({ sessionId, userId }, `✅ Old session cleaned up, proceeding with new connection`);\n }\n\n // Create new App session\n const session = new AppSession({\n packageName: this.config.packageName,\n apiKey: this.config.apiKey,\n mentraOSWebsocketUrl: mentraOSWebsocketUrl || augmentOSWebsocketUrl, // The websocket URL for the specific MentraOS server that this userSession is connecting to.\n appServer: this,\n userId,\n });\n\n // Setup session event handlers\n const cleanupDisconnect = session.events.onDisconnected((info) => {\n // Handle different disconnect info formats (string or object)\n if (typeof info === \"string\") {\n this.logger.info(`👋 Session ${sessionId} disconnected: ${info}`);\n } else {\n // It's an object with detailed disconnect information\n this.logger.info(\n `👋 Session ${sessionId} disconnected: ${info.message} (code: ${info.code}, reason: ${info.reason})`,\n );\n\n // Check if this is a user session end event\n // This happens when the UserSession is disposed after 1 minute grace period\n if (info.sessionEnded === true) {\n this.logger.info(`🛑 User session ended for session ${sessionId}, calling onStop`);\n\n // Call onStop with session end reason\n // This allows apps to clean up resources when the user's session ends\n this.onStop(sessionId, userId, \"User session ended\").catch((error) => {\n this.logger.error(error, `❌ Error in onStop handler for session end:`);\n });\n }\n // Check if this is a permanent disconnection after exhausted reconnection attempts\n else if (info.permanent === true) {\n this.logger.info(`🛑 Permanent disconnection detected for session ${sessionId}, calling onStop`);\n\n // Keep track of the original session before removal\n // const session = this.activeSessions.get(sessionId);\n const _session = this.activeSessions.get(sessionId);\n\n // Call onStop with a reconnection failure reason\n this.onStop(sessionId, userId, `Connection permanently lost: ${info.reason}`).catch((error) => {\n this.logger.error(error, `❌ Error in onStop handler for permanent disconnection:`);\n });\n }\n }\n\n // Remove the session from active sessions ONLY if this session is still the active one.\n // This prevents a bug where an old session's cleanup handler deletes a newer session:\n // 1. User switches from Cloud A to Cloud B\n // 2. SDK creates sessionB, overwrites activeSessions[sessionId]\n // 3. sessionA is orphaned but its cleanup handler still references sessionId\n // 4. When Cloud A disposes, sessionA's cleanup fires and would delete sessionB\n // By checking identity (===), we only delete if we're still the current session.\n // See: cloud/issues/018-app-disconnect-resurrection\n if (this.activeSessions.get(sessionId) === session) {\n this.activeSessions.delete(sessionId);\n } else {\n this.logger.debug({ sessionId }, `🔄 Session ${sessionId} cleanup skipped - a newer session has taken over`);\n }\n if (this.activeSessionsByUserId.get(userId) === session) {\n this.activeSessionsByUserId.delete(userId);\n }\n });\n\n const cleanupError = session.events.onError((error) => {\n this.logger.error(error, `❌ [Session ${sessionId}] Error:`);\n });\n\n // Start the session\n try {\n await session.connect(sessionId);\n this.activeSessions.set(sessionId, session);\n this.activeSessionsByUserId.set(userId, session);\n await this.onSession(session, sessionId, userId);\n res.status(200).json({ status: \"success\" } as WebhookResponse);\n } catch (error) {\n this.logger.error(error, \"❌ Failed to connect:\");\n cleanupDisconnect();\n cleanupError();\n res.status(500).json({\n status: \"error\",\n message: \"Failed to connect\",\n } as WebhookResponse);\n }\n }\n\n /**\n * Handle a stop request webhook\n */\n private async handleStopRequest(request: StopWebhookRequest, res: express.Response): Promise<void> {\n const { sessionId, userId, reason } = request;\n this.logger.info(`\\n\\n🛑 Received stop request for user ${userId}, session ${sessionId}, reason: ${reason}\\n\\n`);\n\n try {\n await this.onStop(sessionId, userId, reason);\n res.status(200).json({ status: \"success\" } as WebhookResponse);\n } catch (error) {\n this.logger.error(error, \"❌ Error handling stop request:\");\n res.status(500).json({\n status: \"error\",\n message: \"Failed to process stop request\",\n } as WebhookResponse);\n }\n }\n\n /**\n * ❤️ Setup Health Check Endpoint\n * Creates a /health endpoint for monitoring server status.\n */\n private setupHealthCheck(): void {\n if (this.config.healthCheck) {\n this.app.get(\"/health\", (req, res) => {\n res.json({\n status: \"healthy\",\n app: this.config.packageName,\n activeSessions: this.activeSessions.size,\n });\n });\n }\n }\n\n /**\n * ⚙️ Setup Settings Endpoint\n * Creates a /settings endpoint that the MentraOS Cloud can use to update settings.\n */\n private setupSettingsEndpoint(): void {\n this.app.post(\"/settings\", async (req, res) => {\n try {\n const { userIdForSettings, settings } = req.body;\n\n if (!userIdForSettings || !Array.isArray(settings)) {\n return res.status(400).json({\n status: \"error\",\n message: \"Missing userId or settings array in request body\",\n });\n }\n\n this.logger.info(`⚙️ Received settings update for user ${userIdForSettings}`);\n\n // Find all active sessions for this user\n const userSessions: AppSession[] = [];\n\n // Look through all active sessions\n this.activeSessions.forEach((session, _sessionId) => {\n // Check if the session has this userId (not directly accessible)\n // We're relying on the webhook handler to have already verified this\n if (session.userId === userIdForSettings) {\n userSessions.push(session);\n }\n });\n\n if (userSessions.length === 0) {\n this.logger.warn(`⚠️ No active sessions found for user ${userIdForSettings}`);\n } else {\n this.logger.info(`🔄 Updating settings for ${userSessions.length} active sessions`);\n }\n\n // Update settings for all of the user's sessions\n for (const session of userSessions) {\n session.updateSettingsForTesting(settings);\n }\n\n // Allow subclasses to handle settings updates if they implement the method\n if (typeof (this as any).onSettingsUpdate === \"function\") {\n await (this as any).onSettingsUpdate(userIdForSettings, settings);\n }\n\n res.json({\n status: \"success\",\n message: \"Settings updated successfully\",\n sessionsUpdated: userSessions.length,\n });\n } catch (error) {\n this.logger.error(error, \"❌ Error handling settings update:\");\n res.status(500).json({\n status: \"error\",\n message: \"Internal server error processing settings update\",\n });\n }\n });\n }\n\n /**\n * 📂 Setup Static File Serving\n * Configures Express to serve static files from the specified directory.\n */\n private setupPublicDir(): void {\n if (this.config.publicDir) {\n const publicPath = path.resolve(this.config.publicDir);\n this.app.use(express.static(publicPath));\n this.logger.info(`📂 Serving static files from ${publicPath}`);\n }\n }\n\n /**\n * 🛑 Setup Shutdown Handlers\n * Registers process signal handlers for graceful shutdown.\n */\n private setupShutdown(): void {\n process.on(\"SIGTERM\", () => this.stop());\n process.on(\"SIGINT\", () => this.stop());\n }\n\n /**\n * 🧹 Cleanup\n * Closes all active sessions and runs cleanup handlers.\n * Releases ownership before disconnecting to enable clean handoffs (no resurrection).\n */\n private async cleanup(): Promise<void> {\n // Close all active sessions with ownership release for clean handoff\n for (const [sessionId, session] of this.activeSessions) {\n this.logger.info(`👋 Closing session ${sessionId} with ownership release`);\n try {\n // Release ownership first, then disconnect\n // This tells the cloud not to resurrect this app\n await session.disconnect({\n releaseOwnership: true,\n reason: \"clean_shutdown\",\n });\n } catch (error) {\n this.logger.error(error, `Error during cleanup of session ${sessionId}`);\n // Still try to disconnect even if release fails\n try {\n await session.disconnect();\n } catch {\n // Ignore secondary errors\n }\n }\n }\n this.activeSessions.clear();\n this.activeSessionsByUserId.clear();\n\n // Run cleanup handlers\n this.cleanupHandlers.forEach((handler) => handler());\n }\n\n /**\n * 🎯 Setup Photo Upload Endpoint\n * Creates a /photo-upload endpoint for receiving photos directly from ASG glasses\n */\n private setupPhotoUploadEndpoint(): void {\n const multer = require(\"multer\");\n\n // Configure multer for handling multipart form data\n const upload = multer({\n storage: multer.memoryStorage(),\n limits: {\n fileSize: 10 * 1024 * 1024, // 10MB limit\n },\n fileFilter: (req: any, file: any, cb: any) => {\n // Accept image files only\n if (file.mimetype && file.mimetype.startsWith(\"image/\")) {\n cb(null, true);\n } else {\n cb(new Error(\"Only image files are allowed\"), false);\n }\n },\n });\n\n this.app.post(\"/photo-upload\", upload.single(\"photo\"), async (req: any, res: any) => {\n try {\n const { requestId, type, success, errorCode, errorMessage } = req.body;\n const photoFile = req.file;\n\n console.log(\"Received photo response: \", req.body);\n\n this.logger.info(\n { requestId, type, success, errorCode },\n `📸 Received photo response: ${requestId} (type: ${type})`,\n );\n\n if (!requestId) {\n this.logger.error(\"No requestId in photo response\");\n return res.status(400).json({\n success: false,\n error: \"No requestId provided\",\n });\n }\n\n // First, check the AppServer-level registry (persists across session reconnections)\n const registryEntry = this.photoRequestRegistry.get(requestId);\n\n // Debug: Log registry state\n this.logger.info(\n {\n requestId,\n registrySize: this.photoRequestRegistry.size,\n registryKeys: Array.from(this.photoRequestRegistry.keys()),\n activeSessionsCount: this.activeSessions.size,\n activeSessionsByUserIdCount: this.activeSessionsByUserId.size,\n },\n \"📸 Photo upload received - checking registry and sessions\",\n );\n\n // Find session - try registry first, then fall back to session-level lookup\n let session: AppSession | undefined;\n\n if (registryEntry) {\n // Found in registry - get user's current session (may be different from original)\n session = this.activeSessionsByUserId.get(registryEntry.userId);\n this.logger.info(\n { requestId, userId: registryEntry.userId, hasCurrentSession: !!session },\n \"📸 Found photo request in registry\",\n );\n }\n\n // Fall back to session-level lookup for backwards compatibility\n if (!session) {\n session = this.findSessionByPhotoRequestId(requestId);\n this.logger.info(\n { requestId, foundViaSessionLookup: !!session },\n \"📸 Session lookup via pendingPhotoRequests\",\n );\n }\n\n if (!session) {\n this.logger.warn(\n {\n requestId,\n inRegistry: !!registryEntry,\n registryKeys: Array.from(this.photoRequestRegistry.keys()),\n activeSessionIds: Array.from(this.activeSessions.keys()),\n },\n \"No active session found for photo request\",\n );\n return res.status(404).json({\n success: false,\n error: \"No active session found for this photo request\",\n });\n }\n\n // Handle error response (no photo file, but has error info)\n if (type === \"photo_error\" || success === false) {\n // Create error response object\n const errorResponse = {\n requestId,\n success: false as const,\n error: {\n code: errorCode || \"UNKNOWN_ERROR\",\n message: errorMessage || \"Unknown error occurred\",\n },\n };\n\n // If found via registry, reject the promise and clean up\n if (registryEntry) {\n registryEntry.reject(`${errorResponse.error.code}: ${errorResponse.error.message}`);\n this.photoRequestRegistry.delete(requestId);\n }\n\n // Also notify the session's camera module\n session.camera.handlePhotoError(errorResponse);\n\n // Respond to ASG client\n return res.json({\n success: true,\n requestId,\n message: \"Photo error received successfully\",\n });\n }\n\n // Handle successful photo upload\n if (!photoFile) {\n this.logger.error({ requestId }, \"No photo file in successful upload\");\n return res.status(400).json({\n success: false,\n error: \"No photo file provided for successful upload\",\n });\n }\n\n // Create photo data object\n const photoData: PhotoData = {\n buffer: photoFile.buffer,\n mimeType: photoFile.mimetype,\n filename: photoFile.originalname || \"photo.jpg\",\n requestId,\n size: photoFile.size,\n timestamp: new Date(),\n };\n\n // If found via registry, resolve the promise and clean up\n if (registryEntry) {\n registryEntry.resolve(photoData);\n this.photoRequestRegistry.delete(requestId);\n this.logger.info({ requestId }, \"📸 Photo delivered via AppServer registry\");\n }\n\n // Also notify the session's camera module for consistency\n session.camera.handlePhotoReceived(photoData);\n\n // Respond to ASG client\n res.json({\n success: true,\n requestId,\n message: \"Photo received successfully\",\n });\n } catch (error) {\n this.logger.error(error, \"❌ Error handling photo response\");\n res.status(500).json({\n success: false,\n error: \"Internal server error processing photo response\",\n });\n }\n });\n }\n\n /**\n * 🔐 Setup Mentra Auth Redirect Endpoint\n * Creates a /mentra-auth endpoint that redirects to the MentraOS OAuth flow.\n */\n private setupMentraAuthRedirect(): void {\n this.app.get(\"/mentra-auth\", (req, res) => {\n // Redirect to the account.mentra.glass OAuth flow with the app's package name\n const authUrl = `https://account.mentra.glass/auth?packagename=${encodeURIComponent(this.config.packageName)}`;\n\n this.logger.info(`🔐 Redirecting to MentraOS OAuth flow: ${authUrl}`);\n\n res.redirect(302, authUrl);\n });\n }\n\n /**\n * Find session that has a pending photo request for the given requestId\n */\n private findSessionByPhotoRequestId(requestId: string): AppSession | undefined {\n for (const [_sessionId, session] of this.activeSessions) {\n if (session.camera.hasPhotoPendingRequest(requestId)) {\n return session;\n }\n }\n return undefined;\n }\n\n /**\n * Register a photo request at the AppServer level.\n * This persists the request across session reconnections so photos can be\n * delivered even if the user's session disconnects and reconnects.\n *\n * @param requestId - Unique photo request ID\n * @param userId - User ID making the request\n * @param resolve - Promise resolve function to call with photo data\n * @param reject - Promise reject function to call on error/timeout\n */\n public registerPhotoRequest(\n requestId: string,\n userId: string,\n resolve: (photoData: PhotoData) => void,\n reject: (reason: string) => void,\n ): void {\n this.photoRequestRegistry.set(requestId, {\n userId,\n createdAt: Date.now(),\n resolve,\n reject,\n });\n\n this.logger.info(\n { requestId, userId, registrySize: this.photoRequestRegistry.size },\n \"📸 Photo request registered at AppServer level\",\n );\n\n // Set 60-second timeout to prevent memory leaks\n setTimeout(() => {\n const entry = this.photoRequestRegistry.get(requestId);\n if (entry) {\n this.logger.warn({ requestId }, \"📸 Photo request timed out at AppServer level\");\n entry.reject(\"Photo request timed out\");\n this.photoRequestRegistry.delete(requestId);\n }\n }, 60000);\n }\n\n /**\n * Unregister a photo request (called when request is fulfilled or cancelled)\n */\n public unregisterPhotoRequest(requestId: string): void {\n this.photoRequestRegistry.delete(requestId);\n }\n}\n\n/**\n * @deprecated Use `AppServerConfig` instead. `TpaServerConfig` is deprecated and will be removed in a future version.\n * This is an alias for backward compatibility only.\n *\n * @example\n * ```typescript\n * // ❌ Deprecated - Don't use this\n * const config: TpaServerConfig = { ... };\n *\n * // ✅ Use this instead\n * const config: AppServerConfig = { ... };\n * ```\n */\nexport type TpaServerConfig = AppServerConfig;\n\n/**\n * @deprecated Use `AppServer` instead. `TpaServer` is deprecated and will be removed in a future version.\n * This is an alias for backward compatibility only.\n *\n * @example\n * ```typescript\n * // ❌ Deprecated - Don't use this\n * class MyServer extends TpaServer { ... }\n *\n * // ✅ Use this instead\n * class MyServer extends AppServer { ... }\n * ```\n */\nexport class TpaServer extends AppServer {\n constructor(config: TpaServerConfig) {\n super(config);\n // Emit a deprecation warning to help developers migrate\n console.warn(\n \"⚠️ DEPRECATION WARNING: TpaServer is deprecated and will be removed in a future version. \" +\n \"Please use AppServer instead. \" +\n 'Simply replace \"TpaServer\" with \"AppServer\" in your code.',\n );\n }\n}\n",
21
- "/* eslint-disable import/order */\n \n/**\n * 🎯 App Session Module\n *\n * Manages an active Third Party App session with MentraOS Cloud.\n * Handles real-time communication, event subscriptions, and display management.\n */\n\n// Patch version for tracking Bug 007 fix (subscriptions derived from handlers)\n// v1: Derive subscriptions from handlers (single source of truth)\n// v2: Add 'terminated' flag to prevent reconnection after \"User session ended\"\n// This helps verify the correct SDK version is running in production\nconst SDK_SUBSCRIPTION_PATCH = \"bug007-fix-v2\";\nimport { WebSocket } from \"ws\";\nimport { EventManager, EventData } from \"./events\";\nimport { LayoutManager } from \"./layouts\";\nimport { SettingsManager } from \"./settings\";\nimport { LocationManager } from \"./modules/location\";\nimport { CameraModule } from \"./modules/camera\";\nimport { LedModule } from \"./modules/led\";\nimport { AudioManager } from \"./modules/audio\";\nimport { ResourceTracker } from \"../../utils/resource-tracker\";\nimport {\n // Message types\n AppToCloudMessage,\n CloudToAppMessage,\n AppConnectionInit,\n AppSubscriptionUpdate,\n AudioPlayResponse,\n RequestWifiSetup,\n OwnershipReleaseMessage,\n AppToCloudMessageType,\n CloudToAppMessageType,\n\n // Event data types\n StreamType,\n ExtendedStreamType,\n ButtonPress,\n HeadPosition,\n TouchEvent,\n PhoneNotification,\n PhoneNotificationDismissed,\n TranscriptionData,\n TranslationData,\n createTouchEventStream,\n\n // Type guards\n isAppConnectionAck,\n isAppConnectionError,\n isDataStream,\n isAppStopped,\n isSettingsUpdate,\n isDashboardModeChanged,\n isDashboardAlwaysOnChanged,\n isAudioPlayResponse,\n isCapabilitiesUpdate,\n\n // Other types\n AppSettings,\n AppSetting,\n AppConfig,\n validateAppConfig,\n AudioChunk,\n VpsCoordinates,\n PhotoTaken,\n SubscriptionRequest,\n Capabilities,\n CapabilitiesUpdate,\n} from \"../../types\";\nimport { DashboardAPI } from \"../../types/dashboard\";\nimport { MentraosSettingsUpdate } from \"../../types/messages/cloud-to-app\";\nimport { Logger } from \"pino\";\nimport { AppServer } from \"../server\";\nimport axios from \"axios\";\nimport EventEmitter from \"events\";\n\n// Import the cloud-to-app specific type guards\nimport {\n isPhotoResponse,\n isRgbLedControlResponse,\n isRtmpStreamStatus,\n isManagedStreamStatus,\n isStreamStatusCheckResponse,\n isDeviceStateUpdate,\n} from \"../../types/messages/cloud-to-app\";\nimport { SimpleStorage } from \"./modules/simple-storage\";\nimport { DeviceState } from \"./device-state\";\nimport { readNotificationWarnLog } from \"../../utils/permissions-utils\";\n\n/**\n * ⚙️ Configuration options for App Session\n *\n * @example\n * ```typescript\n * const config: AppSessionConfig = {\n * packageName: 'org.example.myapp',\n * apiKey: 'your_api_key',\n * // Auto-reconnection is enabled by default\n * // autoReconnect: true\n * };\n * ```\n */\nexport interface AppSessionConfig {\n /** 📦 Unique identifier for your App (e.g., 'org.company.appname') */\n packageName: string;\n /** 🔑 API key for authentication with MentraOS Cloud */\n apiKey: string;\n /** 🔌 WebSocket server URL (default: 'ws://localhost:7002/app-ws') */\n mentraOSWebsocketUrl?: string;\n /** 🔄 Automatically attempt to reconnect on disconnect (default: true) */\n autoReconnect?: boolean;\n /** 🔁 Maximum number of reconnection attempts (default: 3) */\n maxReconnectAttempts?: number;\n /** ⏱️ Base delay between reconnection attempts in ms (default: 1000) */\n reconnectDelay?: number;\n\n userId: string; // user ID for tracking sessions (email of the user).\n appServer: AppServer; // Optional App server instance for advanced features\n}\n\n// List of event types that should never be subscribed to as streams\nconst APP_TO_APP_EVENT_TYPES = [\n \"app_message_received\",\n \"app_user_joined\",\n \"app_user_left\",\n \"app_room_updated\",\n \"app_direct_message_response\",\n];\n\n/**\n * 🚀 App Session Implementation\n *\n * Manages a live connection between your App and MentraOS Cloud.\n * Provides interfaces for:\n * - 🎮 Event handling (transcription, head position, etc.)\n * - 📱 Display management in AR view\n * - 🔌 Connection lifecycle\n * - 🔄 Automatic reconnection\n *\n * @example\n * ```typescript\n * const session = new AppSession({\n * packageName: 'org.example.myapp',\n * apiKey: 'your_api_key'\n * });\n *\n * // Handle events\n * session.onTranscription((data) => {\n * session.layouts.showTextWall(data.text);\n * });\n *\n * // Connect to cloud\n * await session.connect('session_123');\n * ```\n */\nexport class AppSession {\n /** WebSocket connection to MentraOS Cloud */\n private ws: WebSocket | null = null;\n /** Current session identifier */\n private sessionId: string | null = null;\n /** Number of reconnection attempts made */\n private reconnectAttempts = 0;\n /** Flag to prevent reconnection after session termination (e.g., \"User session ended\") */\n private terminated = false;\n // REMOVED: private subscriptions = new Set<ExtendedStreamType>()\n // Subscriptions are now derived from EventManager.handlers (single source of truth)\n // This prevents drift between handlers and subscriptions that caused Bug 007\n // See: cloud/issues/006-captions-and-apps-stopping/011-sdk-subscription-architecture-mismatch.md\n /** Map to store rate options for streams */\n private streamRates = new Map<ExtendedStreamType, string>();\n /** Resource tracker for automatic cleanup */\n private resources = new ResourceTracker();\n /** Internal settings storage - use public settings API instead */\n private settingsData: AppSettings = [];\n /** App configuration loaded from app_config.json */\n private appConfig: AppConfig | null = null;\n /** Whether to update subscriptions when settings change */\n private shouldUpdateSubscriptionsOnSettingsChange = false;\n /** Custom subscription handler for settings-based subscriptions */\n private subscriptionSettingsHandler?: (settings: AppSettings) => ExtendedStreamType[];\n /** Settings that should trigger subscription updates when changed */\n private subscriptionUpdateTriggers: string[] = [];\n /** Pending user discovery requests waiting for responses */\n private pendingUserDiscoveryRequests = new Map<\n string,\n {\n resolve: (userList: any) => void;\n reject: (reason: any) => void;\n }\n >();\n /** Pending direct message requests waiting for responses */\n private pendingDirectMessages = new Map<\n string,\n {\n resolve: (success: boolean) => void;\n reject: (reason: any) => void;\n }\n >();\n\n /** 🎮 Event management interface */\n public readonly events: EventManager;\n /** 📱 Layout management interface */\n public readonly layouts: LayoutManager;\n /** ⚙️ Settings management interface */\n public readonly settings: SettingsManager;\n /** 📊 Dashboard management interface */\n public readonly dashboard: DashboardAPI;\n /** 📍 Location management interface */\n public readonly location: LocationManager;\n /** 📷 Camera interface for photos and streaming */\n public readonly camera: CameraModule;\n /** 💡 LED interface for RGB LED control */\n public readonly led: LedModule;\n /** 🔊 Audio interface for audio playback */\n public readonly audio: AudioManager;\n /** 🔐 Simple key-value storage interface */\n public readonly simpleStorage: SimpleStorage;\n /** 📱 Reactive device state (WebSocket-based observables) */\n public readonly device: { state: DeviceState };\n\n public readonly appServer: AppServer;\n public readonly logger: Logger;\n public readonly userId: string;\n\n /** 🔧 Device capabilities available for this session */\n public capabilities: Capabilities | null = null;\n\n /** 📡 Latest glasses connection state (includes WiFi status) */\n private glassesConnectionState: any = null; // Using any for now since GlassesConnectionState is in glasses-to-cloud types\n\n /** Dedicated emitter for App-to-App events */\n private appEvents = new EventEmitter();\n\n constructor(private config: AppSessionConfig) {\n // Set defaults and merge with provided config\n this.config = {\n mentraOSWebsocketUrl: `ws://localhost:8002/app-ws`, // Use localhost as default\n autoReconnect: true, // Enable auto-reconnection by default for better reliability\n maxReconnectAttempts: 3, // Default to 3 reconnection attempts for better resilience\n reconnectDelay: 1000, // Start with 1 second delay (uses exponential backoff)\n ...config,\n };\n\n this.appServer = this.config.appServer;\n this.logger = this.appServer.logger.child({\n userId: this.config.userId,\n service: \"app-session\",\n });\n this.userId = this.config.userId;\n\n // Make sure the URL is correctly formatted to prevent double protocol issues\n if (this.config.mentraOSWebsocketUrl) {\n try {\n const url = new URL(this.config.mentraOSWebsocketUrl);\n if (![\"ws:\", \"wss:\"].includes(url.protocol)) {\n // Fix URLs with incorrect protocol (e.g., 'ws://http://host')\n const fixedUrl = this.config.mentraOSWebsocketUrl.replace(/^ws:\\/\\/http:\\/\\//, \"ws://\");\n this.config.mentraOSWebsocketUrl = fixedUrl;\n this.logger.warn(`⚠️ [${this.config.packageName}] Fixed malformed WebSocket URL: ${fixedUrl}`);\n }\n } catch (error) {\n this.logger.error(\n error,\n `⚠️ [${this.config.packageName}] Invalid WebSocket URL format: ${this.config.mentraOSWebsocketUrl}`,\n );\n }\n }\n\n // Log initialization\n this.logger.debug(`🚀 [${this.config.packageName}] App Session initialized`);\n this.logger.debug(`🚀 [${this.config.packageName}] WebSocket URL: ${this.config.mentraOSWebsocketUrl}`);\n\n // Validate URL format - give early warning for obvious issues\n // Check URL format but handle undefined case\n if (this.config.mentraOSWebsocketUrl) {\n try {\n const url = new URL(this.config.mentraOSWebsocketUrl);\n if (![\"ws:\", \"wss:\"].includes(url.protocol)) {\n this.logger.error(\n { config: this.config },\n `⚠️ [${this.config.packageName}] Invalid WebSocket URL protocol: ${url.protocol}. Should be ws: or wss:`,\n );\n }\n } catch (error) {\n this.logger.error(\n error,\n `⚠️ [${this.config.packageName}] Invalid WebSocket URL format: ${this.config.mentraOSWebsocketUrl}`,\n );\n }\n }\n\n this.events = new EventManager(\n this.subscribe.bind(this),\n this.unsubscribe.bind(this),\n this.config.packageName,\n this.getHttpsServerUrl() || \"\",\n );\n this.layouts = new LayoutManager(config.packageName, this.send.bind(this));\n\n // Initialize settings manager with all necessary parameters, including subscribeFn for MentraOS settings\n this.settings = new SettingsManager(\n this.settingsData,\n this.config.packageName,\n this.config.mentraOSWebsocketUrl,\n this.sessionId ?? undefined,\n async (streams: string[]) => {\n // NOTE: With Bug 007 fix, subscriptions are derived from EventManager.handlers\n // This subscribeFn is called by SettingsManager to auto-subscribe to streams for MentraOS settings\n // The actual subscription intent should be tracked via handlers, not a separate Set\n this.logger.debug({ streams: JSON.stringify(streams) }, `[AppSession] subscribeFn called for streams`);\n\n // Log current handler-based subscriptions for debugging\n const currentHandlerStreams = this.events.getRegisteredStreams();\n this.logger.debug(\n {\n requestedStreams: JSON.stringify(streams),\n currentHandlerStreams: JSON.stringify(currentHandlerStreams),\n },\n `[AppSession] subscribeFn: requested streams vs current handler streams`,\n );\n\n // Send subscription update if connected\n // Note: The actual subscriptions sent are derived from handlers\n if (this.ws?.readyState === 1) {\n this.updateSubscriptions();\n this.logger.debug(`[AppSession] Sent updated subscriptions to cloud (derived from handlers).`);\n } else {\n this.logger.debug(`[AppSession] WebSocket not open, will send subscriptions when connected.`);\n }\n },\n );\n\n // Initialize dashboard API with this session instance\n // Import DashboardManager dynamically to avoid circular dependency\n const { DashboardManager } = require(\"./dashboard\");\n this.dashboard = new DashboardManager(this);\n\n // Initialize camera module with session reference\n this.camera = new CameraModule(\n this,\n this.config.packageName,\n this.sessionId || \"unknown-session-id\",\n this.logger.child({ module: \"camera\" }),\n );\n\n // Initialize LED control module\n this.led = new LedModule(\n this,\n this.config.packageName,\n this.sessionId || \"unknown-session-id\",\n this.logger.child({ module: \"led\" }),\n );\n\n // Initialize audio module with session reference\n this.audio = new AudioManager(\n this,\n this.config.packageName,\n this.sessionId || \"unknown-session-id\",\n this.logger.child({ module: \"audio\" }),\n );\n\n this.simpleStorage = new SimpleStorage(this);\n this.device = { state: new DeviceState(this) };\n\n this.location = new LocationManager(this);\n }\n\n /**\n * Get the current session ID\n * @returns The current session ID or 'unknown-session-id' if not connected\n */\n getSessionId(): string {\n return this.sessionId || \"unknown-session-id\";\n }\n\n /**\n * Get the package name for this App\n * @returns The package name\n */\n getPackageName(): string {\n return this.config.packageName;\n }\n\n // =====================================\n // 🎮 Direct Event Handling Interface\n // =====================================\n\n /**\n * @deprecated Use session.events.onTranscription() instead\n */\n onTranscription(handler: (data: TranscriptionData) => void): () => void {\n return this.events.onTranscription(handler);\n }\n\n /**\n * 🌐 Listen for speech transcription events in a specific language\n * @param language - Language code (e.g., \"en-US\")\n * @param handler - Function to handle transcription data\n * @returns Cleanup function to remove the handler\n * @throws Error if language code is invalid\n * @deprecated Use session.events.onTranscriptionForLanguage() instead\n */\n onTranscriptionForLanguage(\n language: string,\n handler: (data: TranscriptionData) => void,\n disableLanguageIdentification = false,\n ): () => void {\n return this.events.onTranscriptionForLanguage(language, handler, disableLanguageIdentification);\n }\n\n /**\n * 🌐 Listen for speech translation events for a specific language pair\n * @param sourceLanguage - Source language code (e.g., \"es-ES\")\n * @param targetLanguage - Target language code (e.g., \"en-US\")\n * @param handler - Function to handle translation data\n * @returns Cleanup function to remove the handler\n * @throws Error if language codes are invalid\n * @deprecated Use session.events.onTranslationForLanguage() instead\n */\n onTranslationForLanguage(\n sourceLanguage: string,\n targetLanguage: string,\n handler: (data: TranslationData) => void,\n ): () => void {\n return this.events.ontranslationForLanguage(sourceLanguage, targetLanguage, handler);\n }\n\n /**\n * 👤 Listen for head position changes\n * @param handler - Function to handle head position updates\n * @returns Cleanup function to remove the handler\n * @deprecated Use session.events.onHeadPosition() instead\n */\n onHeadPosition(handler: (data: HeadPosition) => void): () => void {\n return this.events.onHeadPosition(handler);\n }\n\n /**\n * 🔘 Listen for hardware button press events\n * @param handler - Function to handle button events\n * @returns Cleanup function to remove the handler\n * @deprecated Use session.events.onButtonPress() instead\n */\n onButtonPress(handler: (data: ButtonPress) => void): () => void {\n return this.events.onButtonPress(handler);\n }\n\n /**\n * 👆 Listen for touch gesture events\n * @param gestureOrHandler - Gesture name or handler function\n * @param handler - Handler function (if first param is gesture name)\n * @returns Cleanup function\n *\n * @example\n * // Subscribe to all touch events\n * session.onTouchEvent((event) => console.log(event.gesture_name));\n *\n * // Subscribe to specific gesture\n * session.onTouchEvent(\"forward_swipe\", (event) => console.log(\"Forward swipe!\"));\n */\n onTouchEvent(\n gestureOrHandler: string | ((data: TouchEvent) => void),\n handler?: (data: TouchEvent) => void,\n ): () => void {\n return this.events.onTouchEvent(gestureOrHandler as any, handler as any);\n }\n\n /**\n * 👆 Subscribe to multiple touch gestures\n * @param gestures - Array of gesture names\n * @returns Cleanup function that unsubscribes from all\n *\n * @example\n * session.subscribeToGestures([\"forward_swipe\", \"backward_swipe\"]);\n */\n subscribeToGestures(gestures: string[]): () => void {\n gestures.forEach((gesture) => {\n const stream = createTouchEventStream(gesture);\n this.subscribe(stream);\n });\n\n return () => {\n gestures.forEach((gesture) => {\n const stream = createTouchEventStream(gesture);\n this.unsubscribe(stream);\n });\n };\n }\n\n /**\n * 📱 Listen for phone notification events\n * @param handler - Function to handle notifications\n * @returns Cleanup function to remove the handler\n * @deprecated Use session.events.onPhoneNotifications() instead\n */\n onPhoneNotifications(handler: (data: PhoneNotification) => void): () => void {\n readNotificationWarnLog(this.getHttpsServerUrl() || \"\", this.getPackageName(), \"onPhoneNotifications\");\n return this.events.onPhoneNotifications(handler);\n }\n\n /**\n * 📱 Listen for phone notification dismissed events\n * @param handler - Function to handle notification dismissal data\n * @returns Cleanup function to remove the handler\n * @deprecated Use session.events.onPhoneNotificationDismissed() instead\n */\n onPhoneNotificationDismissed(handler: (data: PhoneNotificationDismissed) => void): () => void {\n return this.events.onPhoneNotificationDismissed(handler);\n }\n\n /**\n * 📡 Listen for VPS coordinates updates\n * @param handler - Function to handle VPS coordinates\n * @returns Cleanup function to remove the handler\n * @deprecated Use session.events.onVpsCoordinates() instead\n */\n onVpsCoordinates(handler: (data: VpsCoordinates) => void): () => void {\n this.subscribe(StreamType.VPS_COORDINATES);\n return this.events.onVpsCoordinates(handler);\n }\n\n /**\n * 📸 Listen for photo responses\n * @param handler - Function to handle photo response data\n * @returns Cleanup function to remove the handler\n * @deprecated Use session.events.onPhotoTaken() instead\n */\n onPhotoTaken(handler: (data: PhotoTaken) => void): () => void {\n this.subscribe(StreamType.PHOTO_TAKEN);\n return this.events.onPhotoTaken(handler);\n }\n\n // =====================================\n // 📡 Pub/Sub Interface\n // =====================================\n\n /**\n * 📬 Subscribe to a specific event stream\n * @param sub - A string or a rich subscription object\n */\n subscribe(sub: SubscriptionRequest): void {\n let type: ExtendedStreamType;\n let rate: string | undefined;\n\n if (typeof sub === \"string\") {\n type = sub;\n } else {\n // it's a LocationStreamRequest object\n type = sub.stream;\n rate = sub.rate;\n }\n\n if (APP_TO_APP_EVENT_TYPES.includes(type as string)) {\n this.logger.warn(\n `[AppSession] Attempted to subscribe to App-to-App event type '${type}', which is not a valid stream. Use the event handler (e.g., onAppMessage) instead.`,\n );\n return;\n }\n\n // NOTE: We no longer maintain this.subscriptions - subscriptions are derived from handlers\n // This prevents drift between handlers and subscriptions (Bug 007 fix)\n // The EventManager.addHandler() already tracks the subscription intent via handlers\n\n if (rate) {\n this.streamRates.set(type, rate);\n }\n\n if (this.ws?.readyState === 1) {\n this.updateSubscriptions();\n }\n }\n\n /**\n * 📭 Unsubscribe from a specific event stream\n * @param sub - The subscription to remove\n */\n unsubscribe(sub: SubscriptionRequest): void {\n let type: ExtendedStreamType;\n if (typeof sub === \"string\") {\n type = sub;\n } else {\n type = sub.stream;\n }\n\n if (APP_TO_APP_EVENT_TYPES.includes(type as string)) {\n this.logger.warn(\n `[AppSession] Attempted to unsubscribe from App-to-App event type '${type}', which is not a valid stream.`,\n );\n return;\n }\n // NOTE: We no longer maintain this.subscriptions - subscriptions are derived from handlers\n // The EventManager.removeHandler() already tracks the unsubscription intent\n\n this.streamRates.delete(type); // also remove from our rate map\n if (this.ws?.readyState === 1) {\n this.updateSubscriptions();\n }\n }\n\n /**\n * 🎯 Generic event listener (pub/sub style)\n * @param event - Event name to listen for\n * @param handler - Event handler function\n */\n on<T extends ExtendedStreamType>(event: T, handler: (data: EventData<T>) => void): () => void {\n return this.events.on(event, handler);\n }\n\n // =====================================\n // 🔌 Connection Management\n // =====================================\n\n /**\n * 🚀 Connect to MentraOS Cloud\n * @param sessionId - Unique session identifier\n * @returns Promise that resolves when connected\n */\n async connect(sessionId: string): Promise<void> {\n this.sessionId = sessionId;\n\n // Configure settings API client with the WebSocket URL and session ID\n // This allows settings to be fetched from the correct server\n this.settings.configureApiClient(this.config.packageName, this.config.mentraOSWebsocketUrl || \"\", sessionId);\n\n // Update the sessionId in the camera module\n if (this.camera) {\n this.camera.updateSessionId(sessionId);\n }\n\n // Update the sessionId in the audio module\n if (this.audio) {\n this.audio.updateSessionId(sessionId);\n }\n\n return new Promise((resolve, reject) => {\n try {\n // Clear previous resources if reconnecting\n if (this.ws) {\n // Don't call full dispose() as that would clear subscriptions\n if (this.ws.readyState !== 3) {\n // 3 = CLOSED\n this.ws.close();\n }\n this.ws = null;\n }\n\n // Validate WebSocket URL before attempting connection\n if (!this.config.mentraOSWebsocketUrl) {\n this.logger.error(\"WebSocket URL is missing or undefined\");\n reject(new Error(\"WebSocket URL is required\"));\n return;\n }\n\n // Add debug logging for connection attempts\n this.logger.info(\n `🔌🔌🔌 [${this.config.packageName}] Attempting to connect to: ${this.config.mentraOSWebsocketUrl} for session ${this.sessionId}`,\n );\n\n // Create connection with error handling\n this.ws = new WebSocket(this.config.mentraOSWebsocketUrl);\n\n // Track WebSocket for automatic cleanup\n this.resources.track(() => {\n if (this.ws && this.ws.readyState !== 3) {\n // 3 = CLOSED\n this.ws.close();\n }\n });\n\n this.ws.on(\"open\", () => {\n try {\n this.sendConnectionInit();\n } catch (error: unknown) {\n this.logger.error(error, \"Error during connection initialization\");\n const errorMessage = error instanceof Error ? error.message : String(error);\n this.events.emit(\"error\", new Error(`Connection initialization failed: ${errorMessage}`));\n reject(error);\n }\n });\n\n // Message handler with comprehensive error recovery\n const messageHandler = async (data: Buffer | string, isBinary: boolean) => {\n try {\n // Handle binary messages (typically audio data)\n if (isBinary && Buffer.isBuffer(data)) {\n try {\n // Validate buffer before processing\n if (data.length === 0) {\n this.events.emit(\"error\", new Error(\"Received empty binary data\"));\n return;\n }\n\n // Convert Node.js Buffer to ArrayBuffer safely\n const arrayBuf: ArrayBufferLike = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);\n\n // Create AUDIO_CHUNK event message with validation\n const audioChunk: AudioChunk = {\n type: StreamType.AUDIO_CHUNK,\n arrayBuffer: arrayBuf,\n timestamp: new Date(), // Ensure timestamp is present\n };\n\n this.handleMessage(audioChunk);\n return;\n } catch (error: unknown) {\n this.logger.error(error, \"Error processing binary message:\");\n const errorMessage = error instanceof Error ? error.message : String(error);\n this.events.emit(\"error\", new Error(`Failed to process binary message: ${errorMessage}`));\n return;\n }\n }\n\n // Handle ArrayBuffer data type directly\n if (data instanceof ArrayBuffer) {\n return;\n }\n\n // Handle JSON messages with validation\n try {\n // Convert string data to JSON safely\n let jsonData: string;\n if (typeof data === \"string\") {\n jsonData = data;\n } else if (Buffer.isBuffer(data)) {\n jsonData = data.toString(\"utf8\");\n } else {\n throw new Error(\"Unknown message format\");\n }\n\n // Validate JSON before parsing\n if (!jsonData || jsonData.trim() === \"\") {\n this.events.emit(\"error\", new Error(\"Received empty JSON message\"));\n return;\n }\n\n // Parse JSON with error handling\n const message = JSON.parse(jsonData) as CloudToAppMessage;\n\n // Basic schema validation\n if (!message || typeof message !== \"object\" || !(\"type\" in message)) {\n this.events.emit(\"error\", new Error(\"Malformed message: missing type property\"));\n return;\n }\n\n // Process the validated message\n this.handleMessage(message);\n } catch (error: unknown) {\n this.logger.error(error, \"JSON parsing error\");\n const errorMessage = error instanceof Error ? error.message : String(error);\n this.events.emit(\"error\", new Error(`Failed to parse JSON message: ${errorMessage}`));\n }\n } catch (error: unknown) {\n // Final catch - should never reach here if individual handlers work correctly\n this.logger.error({ error }, \"Unhandled message processing error\");\n const errorMessage = error instanceof Error ? error.message : String(error);\n this.events.emit(\"error\", new Error(`Unhandled message error: ${errorMessage}`));\n }\n };\n\n this.ws.on(\"message\", messageHandler);\n\n // Track event handler removal for automatic cleanup\n this.resources.track(() => {\n if (this.ws) {\n this.ws.off(\"message\", messageHandler);\n }\n });\n\n // Connection closure handler\n const closeHandler = (code: number, reason: string) => {\n const reasonStr = reason ? `: ${reason}` : \"\";\n const closeInfo = `Connection closed (code: ${code})${reasonStr}`;\n\n // Emit the disconnected event with structured data for better handling\n this.events.emit(\"disconnected\", {\n message: closeInfo,\n code: code,\n reason: reason || \"\",\n wasClean: code === 1000 || code === 1001,\n });\n\n // Only attempt reconnection for abnormal closures\n // https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent/code\n // 1000 (Normal Closure) and 1001 (Going Away) are normal\n // 1002-1015 are abnormal, and reason \"App stopped\" means intentional closure\n // 1008 usually when the userSession no longer exists on server. i.e user disconnected from cloud.\n const isNormalClosure = code === 1000 || code === 1001 || code === 1008;\n const isManualStop = reason && reason.includes(\"App stopped\");\n const isUserSessionEnded = reason && reason.includes(\"User session ended\");\n\n // Log closure details for diagnostics\n this.logger.debug(`🔌 [${this.config.packageName}] WebSocket closed with code ${code}${reasonStr}`);\n this.logger.debug(\n `🔌 [${this.config.packageName}] isNormalClosure: ${isNormalClosure}, isManualStop: ${isManualStop}, isUserSessionEnded: ${isUserSessionEnded}`,\n );\n\n // If user session ended, mark as terminated to prevent any future reconnection\n if (isUserSessionEnded) {\n this.terminated = true;\n this.logger.info(\n `🛑 [${this.config.packageName}] User session ended - marking as terminated, no reconnection allowed`,\n );\n }\n\n if (!isNormalClosure && !isManualStop && !this.terminated) {\n this.logger.warn(`🔌 [${this.config.packageName}] Abnormal closure detected, attempting reconnection`);\n this.handleReconnection();\n } else {\n this.logger.debug(\n `🔌 [${this.config.packageName}] Normal/terminated closure detected, not attempting reconnection (terminated: ${this.terminated})`,\n );\n }\n\n // if user session ended, then trigger onStop.\n if (isUserSessionEnded) {\n this.logger.info(\n `🛑 [${this.config.packageName}] User session ended - emitting disconnected event with sessionEnded flag`,\n );\n // Emit a disconnected event with a special flag to indicate session end\n // This will be caught by AppServer which will call the onStop callback\n const disconnectInfo = {\n message: \"User session ended\",\n code: 1000, // Normal closure\n reason: \"User session ended\",\n wasClean: true,\n permanent: true, // This is permanent - no reconnection\n sessionEnded: true, // Special flag to indicate session disposal\n };\n this.events.emit(\"disconnected\", disconnectInfo);\n }\n };\n\n this.ws.on(\"close\", closeHandler);\n\n // Track event handler removal\n this.resources.track(() => {\n if (this.ws) {\n this.ws.off(\"close\", closeHandler);\n }\n });\n\n // Connection error handler\n const errorHandler = (error: Error) => {\n this.logger.error(error, \"WebSocket error\");\n this.events.emit(\"error\", error);\n };\n\n // Enhanced error handler with detailed logging\n this.ws.on(\"error\", (error: Error) => {\n this.logger.error(\n error,\n `⛔️⛔️⛔️ [${this.config.packageName}] WebSocket connection error: ${error.message}`,\n );\n\n // Try to provide more context\n const errMsg = error.message || \"\";\n if (errMsg.includes(\"ECONNREFUSED\")) {\n this.logger.error(\n `⛔️⛔️⛔️ [${this.config.packageName}] Connection refused - Check if the server is running at the specified URL`,\n );\n } else if (errMsg.includes(\"ETIMEDOUT\")) {\n this.logger.error(\n `⛔️⛔️⛔️ [${this.config.packageName}] Connection timed out - Check network connectivity and firewall rules`,\n );\n }\n\n errorHandler(error);\n });\n\n // Track event handler removal\n this.resources.track(() => {\n if (this.ws) {\n this.ws.off(\"error\", errorHandler);\n }\n });\n\n // Set up connection success handler\n const connectedCleanup = this.events.onConnected(() => resolve());\n\n // Track event handler removal\n this.resources.track(connectedCleanup);\n\n // Connection timeout with configurable duration\n const timeoutMs = 5000; // 5 seconds default\n const connectionTimeout = this.resources.setTimeout(() => {\n // Use tracked timeout that will be auto-cleared\n this.logger.error(\n {\n config: this.config,\n sessionId: this.sessionId,\n timeoutMs,\n },\n `⏱️⏱️⏱️ [${this.config.packageName}] Connection timeout after ${timeoutMs}ms`,\n );\n\n this.events.emit(\"error\", new Error(`Connection timeout after ${timeoutMs}ms`));\n reject(new Error(\"Connection timeout\"));\n }, timeoutMs);\n\n // Clear timeout on successful connection\n const timeoutCleanup = this.events.onConnected(() => {\n clearTimeout(connectionTimeout);\n resolve();\n });\n\n // Track event handler removal\n this.resources.track(timeoutCleanup);\n } catch (error: unknown) {\n this.logger.error(error, \"Connection setup error\");\n const errorMessage = error instanceof Error ? error.message : String(error);\n reject(new Error(`Failed to setup connection: ${errorMessage}`));\n }\n });\n }\n\n /**\n * 🔄 Release ownership of this session to allow clean handoff\n * Call this before connecting to a different cloud instance or shutting down cleanly.\n * This signals to the cloud that no resurrection is needed.\n *\n * @param reason - Why ownership is being released\n */\n async releaseOwnership(reason: \"switching_clouds\" | \"clean_shutdown\" | \"user_logout\"): Promise<void> {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\n this.logger.debug(`[${this.config.packageName}] Cannot release ownership - WebSocket not open`);\n return;\n }\n\n const message: OwnershipReleaseMessage = {\n type: AppToCloudMessageType.OWNERSHIP_RELEASE,\n packageName: this.config.packageName,\n sessionId: this.sessionId || \"\",\n reason,\n timestamp: new Date(),\n };\n\n this.logger.info(\n { reason, sessionId: this.sessionId },\n `🔄 [${this.config.packageName}] Releasing ownership: ${reason}`,\n );\n\n this.send(message);\n\n // Small delay to ensure message is sent before any subsequent disconnect\n await new Promise((resolve) => setTimeout(resolve, 100));\n }\n\n /**\n * 👋 Disconnect from MentraOS Cloud\n * Flushes any pending SimpleStorage writes before closing\n *\n * @param options - Optional disconnect options\n * @param options.releaseOwnership - If true, send OWNERSHIP_RELEASE before disconnecting (enables clean handoff)\n * @param options.reason - Reason for ownership release (required if releaseOwnership is true)\n */\n async disconnect(options?: {\n releaseOwnership?: boolean;\n reason?: \"switching_clouds\" | \"clean_shutdown\" | \"user_logout\";\n }): Promise<void> {\n // Release ownership if requested (for clean handoffs)\n if (options?.releaseOwnership && options?.reason) {\n await this.releaseOwnership(options.reason);\n }\n\n // Flush any pending SimpleStorage writes before closing\n try {\n await this.simpleStorage.flush();\n console.log(\"SimpleStorage flushed on disconnect\");\n } catch (error) {\n console.error(\"Error flushing SimpleStorage on disconnect:\", error);\n // Continue with disconnect even if flush fails\n }\n\n // Clean up camera module first\n if (this.camera) {\n this.camera.cancelAllRequests();\n }\n\n // Clean up audio module\n if (this.audio) {\n this.audio.cancelAllRequests();\n }\n\n // Use the resource tracker to clean up everything\n this.resources.dispose();\n\n // Clean up additional resources not handled by the tracker\n this.ws = null;\n this.sessionId = null;\n // REMOVED: this.subscriptions.clear()\n // We no longer clear subscriptions here - they are derived from handlers\n // This is the key fix for Bug 007: clearing subscriptions here caused\n // empty subscription updates on reconnect when handlers still existed\n // See: cloud/issues/006-captions-and-apps-stopping/011-sdk-subscription-architecture-mismatch.md\n this.reconnectAttempts = 0;\n }\n\n /**\n * 🛠️ Get all current user settings\n * @returns A copy of the current settings array\n * @deprecated Use session.settings.getAll() instead\n */\n getSettings(): AppSettings {\n return this.settings.getAll();\n }\n\n /**\n * 🔍 Get a specific setting value by key\n * @param key The setting key to look for\n * @returns The setting's value, or undefined if not found\n * @deprecated Use session.settings.get(key) instead\n */\n getSetting<T>(key: string): T | undefined {\n return this.settings.get<T>(key);\n }\n\n /**\n * ⚙️ Configure settings-based subscription updates\n * This allows Apps to automatically update their subscriptions when certain settings change\n * @param options Configuration options for settings-based subscriptions\n */\n setSubscriptionSettings(options: {\n updateOnChange: string[]; // Setting keys that should trigger subscription updates\n handler: (settings: AppSettings) => ExtendedStreamType[]; // Handler that returns new subscriptions\n }): void {\n this.shouldUpdateSubscriptionsOnSettingsChange = true;\n this.subscriptionUpdateTriggers = options.updateOnChange;\n this.subscriptionSettingsHandler = options.handler;\n\n // If we already have settings, update subscriptions immediately\n if (this.settingsData.length > 0) {\n this.updateSubscriptionsFromSettings();\n }\n }\n\n /**\n * 🔄 Update subscriptions based on current settings\n * Called automatically when relevant settings change\n */\n private updateSubscriptionsFromSettings(): void {\n if (!this.subscriptionSettingsHandler) return;\n\n try {\n // Get desired subscriptions from settings handler\n const settingsSubscriptions = this.subscriptionSettingsHandler(this.settingsData);\n\n // NOTE: Settings-based subscriptions work differently from handler-based subscriptions\n // With the Bug 007 fix, subscriptions are now derived from EventManager.handlers\n // Apps using setSubscriptionSettings() should ensure their settings correspond to\n // registered handlers for the subscriptions to take effect.\n //\n // Log if there's a mismatch (for debugging during migration)\n const handlerStreams = this.events.getRegisteredStreams();\n if (settingsSubscriptions.length !== handlerStreams.length) {\n this.logger.warn(\n {\n settingsSubscriptions: JSON.stringify(settingsSubscriptions),\n handlerStreams: JSON.stringify(handlerStreams),\n },\n `[AppSession] Settings-based subscriptions (${settingsSubscriptions.length}) differ from handler-based subscriptions (${handlerStreams.length}). ` +\n `Subscriptions are now derived from handlers. Ensure handlers are registered for desired streams.`,\n );\n }\n\n // Send subscription update to cloud if connected\n // Note: updateSubscriptions() derives from handlers, so settings-based apps\n // should ensure their settings correspond to registered handlers\n if (this.ws && this.ws.readyState === 1) {\n this.updateSubscriptions();\n }\n } catch (error: unknown) {\n this.logger.error(error, \"Error updating subscriptions from settings\");\n const errorMessage = error instanceof Error ? error.message : String(error);\n this.events.emit(\"error\", new Error(`Failed to update subscriptions: ${errorMessage}`));\n }\n }\n\n /**\n * 🧪 For testing: Update settings locally\n * In normal operation, settings come from the cloud\n * @param newSettings The new settings to apply\n */\n updateSettingsForTesting(newSettings: AppSettings): void {\n this.settingsData = newSettings;\n\n // Update the settings manager with the new settings\n this.settings.updateSettings(newSettings);\n\n // Emit update event for backwards compatibility\n this.events.emit(\"settings_update\", this.settingsData);\n\n // Check if we should update subscriptions\n if (this.shouldUpdateSubscriptionsOnSettingsChange) {\n this.updateSubscriptionsFromSettings();\n }\n }\n\n /**\n * 📝 Load configuration from a JSON file\n * @param jsonData JSON string containing App configuration\n * @returns The loaded configuration\n * @throws Error if the configuration is invalid\n */\n loadConfigFromJson(jsonData: string): AppConfig {\n try {\n const parsedConfig = JSON.parse(jsonData);\n\n if (validateAppConfig(parsedConfig)) {\n this.appConfig = parsedConfig;\n return parsedConfig;\n } else {\n throw new Error(\"Invalid App configuration format\");\n }\n } catch (error: unknown) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n throw new Error(`Failed to load App configuration: ${errorMessage}`);\n }\n }\n\n /**\n * 📋 Get the loaded App configuration\n * @returns The current App configuration or null if not loaded\n */\n getConfig(): AppConfig | null {\n return this.appConfig;\n }\n\n /**\n * 🔌 Get the WebSocket server URL for this session\n * @returns The WebSocket server URL used by this session\n */\n getServerUrl(): string | undefined {\n return this.config.mentraOSWebsocketUrl;\n }\n\n public getHttpsServerUrl(): string | undefined {\n if (!this.config.mentraOSWebsocketUrl) {\n return undefined;\n }\n return AppSession.convertToHttps(this.config.mentraOSWebsocketUrl);\n }\n\n private static convertToHttps(rawUrl: string | undefined): string {\n if (!rawUrl) return \"\";\n // Remove ws:// or wss://\n let url = rawUrl.replace(/^wss?:\\/\\//, \"\");\n // Remove trailing /app-ws\n url = url.replace(/\\/app-ws$/, \"\");\n // Prepend https://\n return `https://${url}`;\n }\n\n /**\n * 🔍 Get default settings from the App configuration\n * @returns Array of settings with default values\n * @throws Error if configuration is not loaded\n */\n getDefaultSettings(): AppSettings {\n if (!this.appConfig) {\n throw new Error(\"App configuration not loaded. Call loadConfigFromJson first.\");\n }\n\n return this.appConfig.settings\n .filter((s: AppSetting | { type: \"group\"; title: string }): s is AppSetting => s.type !== \"group\")\n .map((s: AppSetting) => ({\n ...s,\n value: s.defaultValue, // Set value to defaultValue\n }));\n }\n\n /**\n * 🔍 Get setting schema from configuration\n * @param key Setting key to look up\n * @returns The setting schema or undefined if not found\n */\n getSettingSchema(key: string): AppSetting | undefined {\n if (!this.appConfig) return undefined;\n\n const setting = this.appConfig.settings.find(\n (s: AppSetting | { type: \"group\"; title: string }) => s.type !== \"group\" && \"key\" in s && s.key === key,\n );\n\n return setting as AppSetting | undefined;\n }\n\n /**\n * 📡 Get WiFi connection status of glasses\n * @returns WiFi status object or null if glasses don't support WiFi or status not available\n */\n getWifiStatus(): { connected: boolean; ssid?: string | null } | null {\n if (!this.capabilities?.hasWifi) {\n return null;\n }\n return this.glassesConnectionState?.wifi || null;\n }\n\n /**\n * ✅ Check if glasses are connected to WiFi\n * @returns true if connected to WiFi, false otherwise\n */\n isWifiConnected(): boolean {\n return this.getWifiStatus()?.connected === true;\n }\n\n /**\n * 🌐 Request WiFi setup from mobile app\n * Triggers a popup on the mobile app that allows user to set up WiFi on glasses\n * @param reason Optional reason message to display to the user\n */\n requestWifiSetup(reason?: string): void {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\n throw new Error(\"Not connected to MentraOS Cloud\");\n }\n\n const message: RequestWifiSetup = {\n type: AppToCloudMessageType.REQUEST_WIFI_SETUP,\n packageName: this.config.packageName,\n sessionId: this.sessionId || \"\",\n reason,\n timestamp: new Date(),\n };\n\n this.send(message);\n }\n\n /**\n * 👂 Listen for glasses connection state changes (includes WiFi status)\n * @param handler Callback function to handle connection state updates\n * @returns Cleanup function to remove the listener\n */\n onGlassesConnectionState(handler: (state: any) => void): () => void {\n return this.events.on(StreamType.GLASSES_CONNECTION_STATE, handler);\n }\n\n // =====================================\n // 🔧 Private Methods\n // =====================================\n\n /**\n * 📨 Handle incoming messages from cloud\n */\n private handleMessage(message: CloudToAppMessage): void {\n try {\n // Validate message before processing\n if (!this.validateMessage(message)) {\n this.events.emit(\"error\", new Error(\"Invalid message format received\"));\n return;\n }\n\n // Handle binary data (audio or video)\n if (message instanceof ArrayBuffer) {\n this.handleBinaryMessage(message);\n return;\n }\n\n // Using type guards to determine message type and safely handle each case\n try {\n if (isAppConnectionAck(message)) {\n // Get settings from connection acknowledgment\n const receivedSettings = message.settings || [];\n this.settingsData = receivedSettings;\n\n // Store config if provided\n if (message.config && validateAppConfig(message.config)) {\n this.appConfig = message.config;\n }\n\n // Use default settings from config if no settings were provided\n if (receivedSettings.length === 0 && this.appConfig) {\n try {\n this.settingsData = this.getDefaultSettings();\n } catch (error) {\n this.logger.warn(error, \"Failed to load default settings from config:\");\n }\n }\n\n // Update the settings manager with the new settings\n this.settings.updateSettings(this.settingsData);\n\n // Handle MentraOS system settings if provided\n this.logger.debug(\n { mentraosSettings: JSON.stringify(message.mentraosSettings) },\n `[AppSession] CONNECTION_ACK mentraosSettings}`,\n );\n if (message.mentraosSettings) {\n this.logger.info(\n { mentraosSettings: JSON.stringify(message.mentraosSettings) },\n `[AppSession] Calling updatementraosSettings with`,\n );\n this.settings.updateMentraosSettings(message.mentraosSettings);\n } else {\n this.logger.warn(`[AppSession] CONNECTION_ACK message missing mentraosSettings field`);\n }\n\n // Handle device capabilities if provided\n if (message.capabilities) {\n this.capabilities = message.capabilities;\n this.logger.info(`[AppSession] Device capabilities loaded for model: ${message.capabilities.modelName}`);\n } else {\n this.logger.debug(`[AppSession] No capabilities provided in CONNECTION_ACK`);\n }\n\n // Emit connected event with settings\n this.events.emit(\"connected\", this.settingsData);\n\n // Log once to confirm Bug 007 fix is active (subscriptions derived from handlers)\n const handlerCount = this.events.getRegisteredStreams().length;\n this.logger.info(\n { patch: SDK_SUBSCRIPTION_PATCH, handlerCount },\n `[AppSession] 🔧 SDK Patch Active: ${SDK_SUBSCRIPTION_PATCH} - Subscriptions derived from ${handlerCount} handler(s)`,\n );\n\n // Update subscriptions (normal flow)\n this.updateSubscriptions();\n\n // If settings-based subscriptions are enabled, update those too\n if (this.shouldUpdateSubscriptionsOnSettingsChange && this.settingsData.length > 0) {\n this.updateSubscriptionsFromSettings();\n }\n } else if (isAppConnectionError(message) || message.type === \"connection_error\") {\n // Handle both App-specific connection_error and standard connection_error\n const errorMessage = message.message || \"Unknown connection error\";\n this.events.emit(\"error\", new Error(errorMessage));\n } else if (message.type === StreamType.AUDIO_CHUNK) {\n // Check if we have a handler registered for AUDIO_CHUNK (derived from handlers)\n const hasAudioHandler = this.events.getRegisteredStreams().includes(StreamType.AUDIO_CHUNK);\n if (hasAudioHandler) {\n // Only process if we're subscribed to avoid unnecessary processing\n this.events.emit(StreamType.AUDIO_CHUNK, message);\n }\n } else if (isDataStream(message) && message.streamType === StreamType.GLASSES_CONNECTION_STATE) {\n // Store latest glasses connection state (includes WiFi info)\n this.glassesConnectionState = message.data;\n\n // Emit to subscribed listeners (check derived from handlers)\n const hasGlassesStateHandler = this.events\n .getRegisteredStreams()\n .includes(StreamType.GLASSES_CONNECTION_STATE);\n if (hasGlassesStateHandler) {\n const sanitizedData = this.sanitizeEventData(\n StreamType.GLASSES_CONNECTION_STATE,\n message.data,\n ) as EventData<typeof StreamType.GLASSES_CONNECTION_STATE>;\n this.events.emit(StreamType.GLASSES_CONNECTION_STATE, sanitizedData);\n }\n } else if (isDataStream(message)) {\n // Ensure streamType exists before emitting the event\n const messageStreamType = message.streamType as ExtendedStreamType;\n // if (message.streamType === StreamType.TRANSCRIPTION) {\n // const transcriptionData = message.data as TranscriptionData;\n // if (transcriptionData.transcribeLanguage) {\n // messageStreamType = createTranscriptionStream(transcriptionData.transcribeLanguage) as ExtendedStreamType;\n // }\n // } else if (message.streamType === StreamType.TRANSLATION) {\n // const translationData = message.data as TranslationData;\n // if (translationData.transcribeLanguage && translationData.translateLanguage) {\n // messageStreamType = createTranslationStream(translationData.transcribeLanguage, translationData.translateLanguage) as ExtendedStreamType;\n // }\n // }\n\n // Check if we have a handler registered for this stream type (derived from handlers)\n const hasHandler = this.events.getRegisteredStreams().includes(messageStreamType);\n if (messageStreamType && hasHandler) {\n const sanitizedData = this.sanitizeEventData(messageStreamType, message.data) as EventData<\n typeof messageStreamType\n >;\n this.events.emit(messageStreamType, sanitizedData);\n }\n } else if (isRtmpStreamStatus(message)) {\n // Emit as a standard stream event if subscribed (check derived from handlers)\n const hasRtmpHandler = this.events.getRegisteredStreams().includes(StreamType.RTMP_STREAM_STATUS);\n if (hasRtmpHandler) {\n this.events.emit(StreamType.RTMP_STREAM_STATUS, message);\n }\n\n // Update camera module's internal stream state\n this.camera.updateStreamState(message);\n } else if (isManagedStreamStatus(message)) {\n // Emit as a standard stream event if subscribed (check derived from handlers)\n const hasManagedStreamHandler = this.events.getRegisteredStreams().includes(StreamType.MANAGED_STREAM_STATUS);\n if (hasManagedStreamHandler) {\n this.events.emit(StreamType.MANAGED_STREAM_STATUS, message);\n }\n\n // Update camera module's managed stream state\n this.camera.handleManagedStreamStatus(message);\n } else if (isStreamStatusCheckResponse(message)) {\n // Handle stream status check response\n // This is a direct response, not a subscription-based event\n this.camera.handleStreamCheckResponse(message);\n } else if (isSettingsUpdate(message)) {\n // Store previous settings to check for changes\n const _prevSettings = [...this.settingsData];\n\n // Update internal settings storage\n this.settingsData = message.settings || [];\n\n // Update the settings manager with the new settings\n const changes = this.settings.updateSettings(this.settingsData);\n\n // Emit settings update event (for backwards compatibility)\n this.events.emit(\"settings_update\", this.settingsData);\n\n // --- MentraOS settings update logic ---\n // If the message.settings looks like MentraOS settings (object with known keys), update mentraosSettings\n if (message.settings && typeof message.settings === \"object\") {\n this.settings.updateMentraosSettings(message.settings);\n }\n\n // Check if we should update subscriptions\n if (this.shouldUpdateSubscriptionsOnSettingsChange) {\n // Check if any subscription trigger settings changed\n const shouldUpdateSubs = this.subscriptionUpdateTriggers.some((key) => {\n return key in changes;\n });\n\n if (shouldUpdateSubs) {\n this.updateSubscriptionsFromSettings();\n }\n }\n } else if (isCapabilitiesUpdate(message)) {\n // Update device capabilities\n const capabilitiesMessage = message as CapabilitiesUpdate;\n this.capabilities = capabilitiesMessage.capabilities;\n this.logger.info(\n capabilitiesMessage.capabilities,\n `[AppSession] Capabilities updated for model: ${capabilitiesMessage.modelName}`,\n );\n\n // Emit capabilities update event for applications to handle\n this.events.emit(\"capabilities_update\", {\n capabilities: capabilitiesMessage.capabilities,\n modelName: capabilitiesMessage.modelName,\n timestamp: capabilitiesMessage.timestamp,\n });\n } else if (isDeviceStateUpdate(message)) {\n // Update device state observables\n this.device.state.updateFromMessage(message.state);\n\n this.logger.debug(\n {\n changedFields: Object.keys(message.state),\n fullSnapshot: message.fullSnapshot,\n },\n `[AppSession] Device state updated via WebSocket`,\n );\n } else if (isAppStopped(message)) {\n const reason = message.reason || \"unknown\";\n const displayReason = `App stopped: ${reason}`;\n\n // Don't emit disconnected event here - let the WebSocket close handler do it\n // This prevents duplicate disconnected events when the session is disposed\n this.logger.info(`📤 [${this.config.packageName}] Received APP_STOPPED message: ${displayReason}`);\n\n // Clear reconnection state\n this.reconnectAttempts = 0;\n }\n // Handle dashboard mode changes\n else if (isDashboardModeChanged(message)) {\n try {\n // Use proper type\n const mode = message.mode || \"none\";\n\n // Update dashboard state in the API\n if (this.dashboard && \"content\" in this.dashboard) {\n (this.dashboard.content as any).setCurrentMode(mode);\n }\n } catch (error) {\n this.logger.error(error, \"Error handling dashboard mode change\");\n }\n }\n // Handle always-on dashboard state changes\n else if (isDashboardAlwaysOnChanged(message)) {\n try {\n // Use proper type\n const enabled = !!message.enabled;\n\n // Update dashboard state in the API\n if (this.dashboard && \"content\" in this.dashboard) {\n (this.dashboard.content as any).setAlwaysOnEnabled(enabled);\n }\n } catch (error) {\n this.logger.error(error, \"Error handling dashboard always-on change\");\n }\n }\n // Handle custom messages\n else if (message.type === CloudToAppMessageType.CUSTOM_MESSAGE) {\n this.events.emit(\"custom_message\", message);\n return;\n }\n // Handle App-to-App communication messages\n else if ((message as any).type === \"app_message_received\") {\n this.appEvents.emit(\"app_message_received\", message as any);\n } else if ((message as any).type === \"app_user_joined\") {\n this.appEvents.emit(\"app_user_joined\", message as any);\n } else if ((message as any).type === \"app_user_left\") {\n this.appEvents.emit(\"app_user_left\", message as any);\n } else if ((message as any).type === \"app_room_updated\") {\n this.appEvents.emit(\"app_room_updated\", message as any);\n } else if ((message as any).type === \"app_direct_message_response\") {\n const response = message as any;\n if (response.messageId && this.pendingDirectMessages.has(response.messageId)) {\n const { resolve } = this.pendingDirectMessages.get(response.messageId)!;\n resolve(response.success);\n this.pendingDirectMessages.delete(response.messageId);\n }\n } else if (message.type === \"augmentos_settings_update\") {\n const mentraosMsg = message as MentraosSettingsUpdate;\n if (mentraosMsg.settings && typeof mentraosMsg.settings === \"object\") {\n this.settings.updateMentraosSettings(mentraosMsg.settings);\n }\n }\n // Handle 'connection_error' as a specific case if cloud sends this string literal\n else if ((message as any).type === \"connection_error\") {\n // Treat 'connection_error' (string literal) like AppConnectionError\n // This handles cases where the cloud might send the type as a direct string\n // instead of the enum's 'tpa_connection_error' value.\n const errorMessage = (message as any).message || \"Unknown connection error (type: connection_error)\";\n this.logger.warn(\n `Received 'connection_error' type directly. Consider aligning cloud to send 'tpa_connection_error'. Message: ${errorMessage}`,\n );\n this.events.emit(\"error\", new Error(errorMessage));\n } else if (message.type === \"permission_error\") {\n // Handle permission errors from cloud\n this.logger.warn(\n {\n message: message.message,\n details: message.details,\n detailsCount: message.details?.length || 0,\n rejectedStreams: message.details?.map((d) => d.stream) || [],\n },\n \"Permission error received:\",\n );\n\n // Emit permission error event for application handling\n this.events.emit(\"permission_error\", {\n message: message.message,\n details: message.details,\n timestamp: message.timestamp,\n });\n\n // Optionally emit individual permission denied events for each stream\n message.details?.forEach((detail) => {\n this.events.emit(\"permission_denied\", {\n stream: detail.stream,\n requiredPermission: detail.requiredPermission,\n message: detail.message,\n });\n });\n } else if (isAudioPlayResponse(message)) {\n // Delegate audio play response handling to the audio module\n if (this.audio) {\n this.audio.handleAudioPlayResponse(message as AudioPlayResponse);\n }\n } else if (isPhotoResponse(message)) {\n // Legacy photo response handling - now photos come directly via webhook\n // This branch can be removed in the future as all photos now go through /photo-upload\n this.logger.warn(\n { message },\n \"Received legacy photo response - photos should now come via /photo-upload webhook\",\n );\n } else if (isRgbLedControlResponse(message)) {\n // LED control responses are no longer handled - fire-and-forget mode\n this.logger.debug({ message }, \"Received LED control response (ignored - fire-and-forget mode)\");\n }\n // Handle unrecognized message types gracefully\n else {\n this.logger.warn(`Unrecognized message type: ${(message as any).type}`);\n this.events.emit(\"error\", new Error(`Unrecognized message type: ${(message as any).type}`));\n }\n } catch (processingError: unknown) {\n // Catch any errors during message processing to prevent App crashes\n this.logger.error(processingError, \"Error processing message:\");\n const errorMessage = processingError instanceof Error ? processingError.message : String(processingError);\n this.events.emit(\"error\", new Error(`Error processing message: ${errorMessage}`));\n }\n } catch (error: unknown) {\n // Final safety net to ensure the App doesn't crash on any unexpected errors\n this.logger.error(error, \"Unexpected error in message handler\");\n const errorMessage = error instanceof Error ? error.message : String(error);\n this.events.emit(\"error\", new Error(`Unexpected error in message handler: ${errorMessage}`));\n }\n }\n\n /**\n * 🧪 Validate incoming message structure\n * @param message - Message to validate\n * @returns boolean indicating if the message is valid\n */\n private validateMessage(message: CloudToAppMessage): boolean {\n // Handle ArrayBuffer case separately\n if (message instanceof ArrayBuffer) {\n return true; // ArrayBuffers are always considered valid at this level\n }\n\n // Check if message is null or undefined\n if (!message) {\n return false;\n }\n\n // Check if message has a type property\n if (!(\"type\" in message)) {\n return false;\n }\n\n // All other message types should be objects with a type property\n return true;\n }\n\n /**\n * 📦 Handle binary message data (audio or video)\n * @param buffer - Binary data as ArrayBuffer\n */\n private handleBinaryMessage(buffer: ArrayBuffer): void {\n try {\n // Safety check - only process if we have a handler registered (derived from handlers)\n const hasAudioHandler = this.events.getRegisteredStreams().includes(StreamType.AUDIO_CHUNK);\n if (!hasAudioHandler) {\n return;\n }\n\n // Validate buffer has content before processing\n if (!buffer || buffer.byteLength === 0) {\n this.events.emit(\"error\", new Error(\"Received empty binary message\"));\n return;\n }\n\n // Create a safety wrapped audio chunk with proper defaults\n const audioChunk: AudioChunk = {\n type: StreamType.AUDIO_CHUNK,\n timestamp: new Date(),\n arrayBuffer: buffer,\n sampleRate: 16000, // Default sample rate\n };\n\n // Emit to subscribers\n this.events.emit(StreamType.AUDIO_CHUNK, audioChunk);\n } catch (error: unknown) {\n this.logger.error(error, \"Error processing binary message\");\n const errorMessage = error instanceof Error ? error.message : String(error);\n this.events.emit(\"error\", new Error(`Error processing binary message: ${errorMessage}`));\n }\n }\n\n /**\n * 🧹 Sanitize event data to prevent crashes from malformed data\n * @param streamType - The type of stream data\n * @param data - The potentially unsafe data to sanitize\n * @returns Sanitized data safe for processing\n */\n private sanitizeEventData(streamType: ExtendedStreamType, data: unknown): any {\n try {\n // If data is null or undefined, return an empty object to prevent crashes\n if (data === null || data === undefined) {\n return {};\n }\n\n // For specific stream types, perform targeted sanitization\n switch (streamType) {\n case StreamType.TRANSCRIPTION: {\n // Ensure text field exists and is a string\n if (typeof (data as TranscriptionData).text !== \"string\") {\n return {\n text: \"\",\n isFinal: true,\n startTime: Date.now(),\n endTime: Date.now(),\n };\n }\n break;\n }\n\n case StreamType.HEAD_POSITION: {\n // Ensure position data has required numeric fields\n // Handle HeadPosition - Note the property position instead of x,y,z\n const pos = data as any;\n if (typeof pos?.position !== \"string\") {\n return { position: \"up\", timestamp: new Date() };\n }\n break;\n }\n\n case StreamType.BUTTON_PRESS: {\n // Ensure button type is valid\n const btn = data as any;\n if (!btn.buttonId || !btn.pressType) {\n return {\n buttonId: \"unknown\",\n pressType: \"short\",\n timestamp: new Date(),\n };\n }\n break;\n }\n }\n\n return data;\n } catch (error: unknown) {\n this.logger.error(error, `Error sanitizing ${streamType} data`);\n // Return a safe empty object if something goes wrong\n return {};\n }\n }\n\n /**\n * 🔐 Send connection initialization message\n */\n private sendConnectionInit(): void {\n const message: AppConnectionInit = {\n type: AppToCloudMessageType.CONNECTION_INIT,\n sessionId: this.sessionId!,\n packageName: this.config.packageName,\n apiKey: this.config.apiKey,\n timestamp: new Date(),\n };\n this.send(message);\n }\n\n /**\n * 📝 Update subscription list with cloud\n */\n private updateSubscriptions(): void {\n // CRITICAL FIX (Bug 007): Derive subscriptions from EventManager.handlers\n // This ensures subscriptions can NEVER be empty if handlers exist\n // Previously, this.subscriptions could drift out of sync with handlers\n // See: cloud/issues/006-captions-and-apps-stopping/011-sdk-subscription-architecture-mismatch.md\n const derivedSubscriptions = this.events.getRegisteredStreams();\n\n this.logger.info(\n { subscriptions: JSON.stringify(derivedSubscriptions) },\n `[AppSession] updateSubscriptions: sending ${derivedSubscriptions.length} subscriptions to cloud (derived from handlers)`,\n );\n\n // Build the array of SubscriptionRequest objects to send to the cloud\n const subscriptionPayload: SubscriptionRequest[] = derivedSubscriptions.map((stream) => {\n const rate = this.streamRates.get(stream);\n if (rate && stream === StreamType.LOCATION_STREAM) {\n return { stream: \"location_stream\", rate: rate as any };\n }\n return stream;\n });\n\n const message: AppSubscriptionUpdate = {\n type: AppToCloudMessageType.SUBSCRIPTION_UPDATE,\n packageName: this.config.packageName,\n subscriptions: subscriptionPayload,\n sessionId: this.sessionId!,\n timestamp: new Date(),\n };\n this.send(message);\n }\n\n /**\n * 🔄 Handle reconnection with exponential backoff\n */\n private async handleReconnection(): Promise<void> {\n // Check if session was terminated (e.g., \"User session ended\")\n if (this.terminated) {\n this.logger.info(\n `🔄 Reconnection skipped: session was terminated (User session ended). ` +\n `If cloud restarts app, onSession will be called with fresh handlers.`,\n );\n return;\n }\n\n // Check if reconnection is allowed\n if (!this.config.autoReconnect || !this.sessionId) {\n this.logger.debug(\n `🔄 Reconnection skipped: autoReconnect=${this.config.autoReconnect}, sessionId=${\n this.sessionId ? \"valid\" : \"invalid\"\n }`,\n );\n return;\n }\n\n // Check if we've exceeded the maximum attempts\n const maxAttempts = this.config.maxReconnectAttempts || 3;\n if (this.reconnectAttempts >= maxAttempts) {\n this.logger.info(`🔄 Maximum reconnection attempts (${maxAttempts}) reached, giving up`);\n\n // Emit a permanent disconnection event to trigger onStop in the App server\n this.events.emit(\"disconnected\", {\n message: `Connection permanently lost after ${maxAttempts} failed reconnection attempts`,\n code: 4000, // Custom code for max reconnection attempts exhausted\n reason: \"Maximum reconnection attempts exceeded\",\n wasClean: false,\n permanent: true, // Flag this as a permanent disconnection\n });\n\n return;\n }\n\n // Calculate delay with exponential backoff\n const baseDelay = this.config.reconnectDelay || 1000;\n const delay = baseDelay * Math.pow(2, this.reconnectAttempts);\n this.reconnectAttempts++;\n\n this.logger.debug(\n `🔄 [${this.config.packageName}] Reconnection attempt ${this.reconnectAttempts}/${maxAttempts} in ${delay}ms`,\n );\n\n // Use the resource tracker for the timeout\n await new Promise<void>((resolve) => {\n this.resources.setTimeout(() => resolve(), delay);\n });\n\n try {\n this.logger.debug(`🔄 [${this.config.packageName}] Attempting to reconnect...`);\n await this.connect(this.sessionId);\n this.logger.debug(`✅ [${this.config.packageName}] Reconnection successful!`);\n this.reconnectAttempts = 0;\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n this.logger.error(error, `❌ [${this.config.packageName}] Reconnection failed for user ${this.userId}`);\n this.events.emit(\"error\", new Error(`Reconnection failed: ${errorMessage}`));\n\n // Check if this was the last attempt\n if (this.reconnectAttempts >= maxAttempts) {\n this.logger.debug(\n `🔄 [${this.config.packageName}] Final reconnection attempt failed, emitting permanent disconnection`,\n );\n\n // Emit permanent disconnection event after the last failed attempt\n this.events.emit(\"disconnected\", {\n message: `Connection permanently lost after ${maxAttempts} failed reconnection attempts`,\n code: 4000, // Custom code for max reconnection attempts exhausted\n reason: \"Maximum reconnection attempts exceeded\",\n wasClean: false,\n permanent: true, // Flag this as a permanent disconnection\n });\n }\n }\n }\n\n /**\n * 📤 Public API for modules to send messages\n * Always uses current WebSocket connection\n */\n public sendMessage(message: AppToCloudMessage): void {\n return this.send(message);\n }\n\n /**\n * 📤 Send message to cloud with validation and error handling\n * @throws {Error} If WebSocket is not connected\n */\n private send(message: AppToCloudMessage): void {\n try {\n // Verify WebSocket connection is valid\n if (!this.ws) {\n throw new Error(\"WebSocket connection not established\");\n }\n\n if (this.ws.readyState !== 1) {\n const stateMap: Record<number, string> = {\n 0: \"CONNECTING\",\n 1: \"OPEN\",\n 2: \"CLOSING\",\n 3: \"CLOSED\",\n };\n const stateName = stateMap[this.ws.readyState] || \"UNKNOWN\";\n throw new Error(`WebSocket not connected (current state: ${stateName})`);\n }\n\n // Validate message before sending\n if (!message || typeof message !== \"object\") {\n throw new Error(\"Invalid message: must be an object\");\n }\n\n if (!(\"type\" in message)) {\n throw new Error('Invalid message: missing \"type\" property');\n }\n\n // Ensure message format is consistent\n if (!(\"timestamp\" in message) || !(message.timestamp instanceof Date)) {\n message.timestamp = new Date();\n }\n\n // Try to send with error handling\n try {\n const serializedMessage = JSON.stringify(message);\n this.ws.send(serializedMessage);\n } catch (sendError: unknown) {\n const errorMessage = sendError instanceof Error ? sendError.message : String(sendError);\n throw new Error(`Failed to send message: ${errorMessage}`);\n }\n } catch (error: unknown) {\n // Check if this is an expected disconnection error (not a real error)\n const isDisconnectError =\n error instanceof Error &&\n (error.message.includes(\"WebSocket not connected\") ||\n error.message.includes(\"CLOSED\") ||\n error.message.includes(\"CLOSING\"));\n\n if (isDisconnectError) {\n // Don't log as error - this is expected when user disconnects\n // Apps should handle this gracefully by checking session.isConnected\n this.logger.debug(error, \"Message send skipped - session disconnected\");\n } else {\n // This is an actual error that needs attention\n this.logger.error(error, \"Message send error\");\n }\n\n // Ensure we always emit an Error object\n if (error instanceof Error) {\n this.events.emit(\"error\", error);\n } else {\n this.events.emit(\"error\", new Error(String(error)));\n }\n\n // Re-throw to maintain the original function behavior\n throw error;\n }\n }\n\n /**\n * Fetch the onboarding instructions for this session from the backend.\n * @returns Promise resolving to the instructions string or null\n */\n public async getInstructions(): Promise<string | null> {\n try {\n const baseUrl = this.getServerUrl();\n const response = await axios.get(`${baseUrl}/api/instructions`, {\n params: { userId: this.userId },\n });\n return response.data.instructions || null;\n } catch (err) {\n this.logger.error(err, `Error fetching instructions from backend`);\n return null;\n }\n }\n // =====================================\n // 👥 App-to-App Communication Interface\n // =====================================\n\n /**\n * 👥 Discover other users currently using the same App\n * @param includeProfiles - Whether to include user profile information\n * @returns Promise that resolves with list of active users\n */\n async discoverAppUsers(domain: string, includeProfiles = false): Promise<any> {\n // Use the domain argument as the base URL if provided\n if (!domain) {\n throw new Error(\"Domain (API base URL) is required for user discovery\");\n }\n const url = `${domain}/api/app-communication/discover-users`;\n // Use the user's core token for authentication\n const appApiKey = this.config.apiKey; // This may need to be updated if you store the core token elsewhere\n\n if (!appApiKey) {\n throw new Error(\"Core token (apiKey) is required for user discovery\");\n }\n const body = {\n packageName: this.config.packageName,\n userId: this.userId,\n includeUserProfiles: includeProfiles,\n };\n const response = await fetch(url, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${appApiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(body),\n });\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Failed to discover users: ${response.status} ${response.statusText} - ${errorText}`);\n }\n return await response.json();\n }\n\n /**\n * 🔍 Check if a specific user is currently active\n * @param userId - User ID to check for\n * @returns Promise that resolves with boolean indicating if user is active\n */\n async isUserActive(userId: string): Promise<boolean> {\n try {\n const userList = await this.discoverAppUsers(\"\", false);\n return userList.users.some((user: any) => user.userId === userId);\n } catch (error) {\n this.logger.error({ error, userId }, \"Error checking if user is active\");\n return false;\n }\n }\n\n /**\n * 📊 Get user count for this App\n * @returns Promise that resolves with number of active users\n */\n async getUserCount(domain: string): Promise<number> {\n try {\n const userList = await this.discoverAppUsers(domain, false);\n return userList.totalUsers;\n } catch (error) {\n this.logger.error(error, \"Error getting user count\");\n return 0;\n }\n }\n\n /**\n * 📢 Send broadcast message to all users with same App active\n * @param payload - Message payload to send\n * @param roomId - Optional room ID for room-based messaging\n * @returns Promise that resolves when message is sent\n */\n async broadcastToAppUsers(payload: any, _roomId?: string): Promise<void> {\n try {\n const messageId = this.generateMessageId();\n\n const message = {\n type: \"app_broadcast_message\",\n packageName: this.config.packageName,\n sessionId: this.sessionId!,\n payload,\n messageId,\n senderUserId: this.userId,\n timestamp: new Date(),\n };\n\n this.send(message as any);\n } catch (error: unknown) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n throw new Error(`Failed to broadcast message: ${errorMessage}`);\n }\n }\n\n /**\n * 📤 Send direct message to specific user\n * @param targetUserId - User ID to send message to\n * @param payload - Message payload to send\n * @returns Promise that resolves with success status\n */\n async sendDirectMessage(targetUserId: string, payload: any): Promise<boolean> {\n return new Promise((resolve, reject) => {\n try {\n const messageId = this.generateMessageId();\n\n // Store promise resolver\n this.pendingDirectMessages.set(messageId, { resolve, reject });\n\n const message = {\n type: \"app_direct_message\",\n packageName: this.config.packageName,\n sessionId: this.sessionId!,\n targetUserId,\n payload,\n messageId,\n senderUserId: this.userId,\n timestamp: new Date(),\n };\n\n this.send(message as any);\n\n // Set timeout to avoid hanging promises\n const timeoutMs = 15000; // 15 seconds\n this.resources.setTimeout(() => {\n if (this.pendingDirectMessages.has(messageId)) {\n this.pendingDirectMessages.get(messageId)!.reject(new Error(\"Direct message timed out\"));\n this.pendingDirectMessages.delete(messageId);\n }\n }, timeoutMs);\n } catch (error: unknown) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n reject(new Error(`Failed to send direct message: ${errorMessage}`));\n }\n });\n }\n\n /**\n * 🏠 Join a communication room for group messaging\n * @param roomId - Room ID to join\n * @param roomConfig - Optional room configuration\n * @returns Promise that resolves when room is joined\n */\n async joinAppRoom(\n roomId: string,\n roomConfig?: {\n maxUsers?: number;\n isPrivate?: boolean;\n metadata?: any;\n },\n ): Promise<void> {\n try {\n const message = {\n type: \"app_room_join\",\n packageName: this.config.packageName,\n sessionId: this.sessionId!,\n roomId,\n roomConfig,\n timestamp: new Date(),\n };\n\n this.send(message as any);\n } catch (error: unknown) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n throw new Error(`Failed to join room: ${errorMessage}`);\n }\n }\n\n /**\n * 🚪 Leave a communication room\n * @param roomId - Room ID to leave\n * @returns Promise that resolves when room is left\n */\n async leaveAppRoom(roomId: string): Promise<void> {\n try {\n const message = {\n type: \"app_room_leave\",\n packageName: this.config.packageName,\n sessionId: this.sessionId!,\n roomId,\n timestamp: new Date(),\n };\n\n this.send(message as any);\n } catch (error: unknown) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n throw new Error(`Failed to leave room: ${errorMessage}`);\n }\n }\n\n /**\n * 📨 Listen for messages from other App users\n * @param handler - Function to handle incoming messages\n * @returns Cleanup function to remove the handler\n */\n onAppMessage(handler: (message: any) => void): () => void {\n this.appEvents.on(\"app_message_received\", handler);\n return () => this.appEvents.off(\"app_message_received\", handler);\n }\n\n /**\n * 👋 Listen for user join events\n * @param handler - Function to handle user join events\n * @returns Cleanup function to remove the handler\n */\n onAppUserJoined(handler: (data: any) => void): () => void {\n this.appEvents.on(\"app_user_joined\", handler);\n return () => this.appEvents.off(\"app_user_joined\", handler);\n }\n\n /**\n * 🚪 Listen for user leave events\n * @param handler - Function to handle user leave events\n * @returns Cleanup function to remove the handler\n */\n onAppUserLeft(handler: (data: any) => void): () => void {\n this.appEvents.on(\"app_user_left\", handler);\n return () => this.appEvents.off(\"app_user_left\", handler);\n }\n\n /**\n * 🏠 Listen for room update events\n * @param handler - Function to handle room updates\n * @returns Cleanup function to remove the handler\n */\n onAppRoomUpdated(handler: (data: any) => void): () => void {\n this.appEvents.on(\"app_room_updated\", handler);\n return () => this.appEvents.off(\"app_room_updated\", handler);\n }\n\n /**\n * 🔧 Generate unique message ID\n * @returns Unique message identifier\n */\n private generateMessageId(): string {\n return `msg_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;\n }\n}\n\n/**\n * @deprecated Use `AppSessionConfig` instead. `TpaSessionConfig` is deprecated and will be removed in a future version.\n * This is an alias for backward compatibility only.\n *\n * @example\n * ```typescript\n * // ❌ Deprecated - Don't use this\n * const config: TpaSessionConfig = { ... };\n *\n * // ✅ Use this instead\n * const config: AppSessionConfig = { ... };\n * ```\n */\nexport type TpaSessionConfig = AppSessionConfig;\n\n/**\n * @deprecated Use `AppSession` instead. `TpaSession` is deprecated and will be removed in a future version.\n * This is an alias for backward compatibility only.\n *\n * @example\n * ```typescript\n * // ❌ Deprecated - Don't use this\n * const session = new TpaSession(config);\n *\n * // ✅ Use this instead\n * const session = new AppSession(config);\n * ```\n */\nexport class TpaSession extends AppSession {\n constructor(config: TpaSessionConfig) {\n super(config);\n // Emit a deprecation warning to help developers migrate\n console.warn(\n \"⚠️ DEPRECATION WARNING: TpaSession is deprecated and will be removed in a future version. \" +\n \"Please use AppSession instead. \" +\n 'Simply replace \"TpaSession\" with \"AppSession\" in your code.',\n );\n }\n}\n\n// Export module types for developers\nexport { CameraModule, PhotoRequestOptions, RtmpStreamOptions } from \"./modules/camera\";\nexport { LedModule, LedControlOptions } from \"./modules/led\";\nexport { AudioManager, AudioPlayOptions, AudioPlayResult, SpeakOptions } from \"./modules/audio\";\nexport { SimpleStorage } from \"./modules/simple-storage\";\n",
22
- "/**\n * 🎮 Event Manager Module\n */\nimport EventEmitter from \"events\"\nimport {\n StreamType,\n ExtendedStreamType,\n AppSettings,\n WebSocketError,\n // Event data types\n ButtonPress,\n HeadPosition,\n PhoneNotification,\n TranscriptionData,\n TranslationData,\n GlassesBatteryUpdate,\n PhoneBatteryUpdate,\n GlassesConnectionState,\n LocationUpdate,\n Vad,\n AudioChunk,\n CalendarEvent,\n VpsCoordinates,\n // Language stream helpers\n createTranscriptionStream,\n isValidLanguageCode,\n createTranslationStream,\n CustomMessage,\n RtmpStreamStatus,\n PhotoTaken,\n ManagedStreamStatus,\n PhoneNotificationDismissed,\n Capabilities,\n TouchEvent,\n createTouchEventStream,\n} from \"../../types\"\nimport {DashboardMode} from \"../../types/dashboard\"\nimport {PermissionErrorDetail} from \"../../types/messages/cloud-to-app\"\nimport {calendarWarnLog, microPhoneWarnLog} from \"../../utils/permissions-utils\"\n\n/** 🎯 Type-safe event handler function */\ntype Handler<T> = (data: T) => void\n\n/** 🔄 System events not tied to streams */\ninterface SystemEvents {\n connected: AppSettings | undefined\n disconnected:\n | string\n | {\n message: string // Human-readable close message\n code: number // WebSocket close code (1000 = normal)\n reason: string // Reason provided by server\n wasClean: boolean // Whether this was a clean closure\n permanent?: boolean // Whether this is a permanent disconnection (no more reconnection attempts)\n sessionEnded?: boolean // Whether this disconnection is due to user session ending\n }\n error: WebSocketError | Error\n settings_update: AppSettings\n capabilities_update: {\n capabilities: Capabilities | null\n modelName: string | null\n timestamp?: Date\n }\n dashboard_mode_change: {mode: DashboardMode | \"none\"}\n dashboard_always_on_change: {enabled: boolean}\n custom_message: CustomMessage\n permission_error: {\n message: string\n details: PermissionErrorDetail[]\n timestamp?: Date\n }\n permission_denied: {\n stream: string\n requiredPermission: string\n message: string\n }\n}\n\n/** 📡 All possible event types */\ntype EventType = ExtendedStreamType | keyof SystemEvents\n\n/** 📦 Map of stream types to their data types */\nexport interface StreamDataTypes {\n [StreamType.BUTTON_PRESS]: ButtonPress\n [StreamType.HEAD_POSITION]: HeadPosition\n [StreamType.PHONE_NOTIFICATION]: PhoneNotification\n [StreamType.TRANSCRIPTION]: TranscriptionData\n [StreamType.TRANSLATION]: TranslationData\n [StreamType.GLASSES_BATTERY_UPDATE]: GlassesBatteryUpdate\n [StreamType.PHONE_BATTERY_UPDATE]: PhoneBatteryUpdate\n [StreamType.GLASSES_CONNECTION_STATE]: GlassesConnectionState\n [StreamType.LOCATION_UPDATE]: LocationUpdate\n [StreamType.CALENDAR_EVENT]: CalendarEvent\n [StreamType.VAD]: Vad\n [StreamType.PHONE_NOTIFICATION_DISMISSED]: PhoneNotificationDismissed\n [StreamType.AUDIO_CHUNK]: AudioChunk\n [StreamType.VIDEO]: ArrayBuffer\n [StreamType.RTMP_STREAM_STATUS]: RtmpStreamStatus\n [StreamType.MANAGED_STREAM_STATUS]: ManagedStreamStatus\n [StreamType.VPS_COORDINATES]: VpsCoordinates\n [StreamType.PHOTO_TAKEN]: PhotoTaken\n [StreamType.OPEN_DASHBOARD]: never\n [StreamType.START_APP]: never\n [StreamType.STOP_APP]: never\n [StreamType.ALL]: never\n [StreamType.WILDCARD]: never\n}\n\n/** 📦 Data type for an event */\nexport type EventData<T extends EventType> = T extends keyof StreamDataTypes\n ? StreamDataTypes[T]\n : T extends keyof SystemEvents\n ? SystemEvents[T]\n : T extends string\n ? T extends `${StreamType.TRANSCRIPTION}:${string}`\n ? TranscriptionData\n : T extends `${StreamType.TRANSLATION}:${string}`\n ? TranslationData\n : never\n : never\n\nexport class EventManager {\n private emitter: EventEmitter\n private handlers: Map<EventType, Set<Handler<unknown>>>\n private lastLanguageTranscriptioCleanupHandler: () => void\n private lastLanguageTranslationCleanupHandler: () => void\n\n constructor(\n private subscribe: (type: ExtendedStreamType) => void,\n private unsubscribe: (type: ExtendedStreamType) => void,\n private packageName: string,\n private baseUrl: string,\n ) {\n this.emitter = new EventEmitter()\n this.handlers = new Map()\n this.lastLanguageTranscriptioCleanupHandler = () => {}\n this.lastLanguageTranslationCleanupHandler = () => {}\n }\n\n // Convenience handlers for common event types\n\n onTranscription(handler: Handler<TranscriptionData>) {\n // Only make the API call if we have a base URL (server-side environment)\n microPhoneWarnLog(this.baseUrl, this.packageName, this.onTranscription.name)\n\n return this.addHandler(createTranscriptionStream(\"en-US\"), handler)\n }\n\n /**\n * 🎤 Listen for transcription events in a specific language\n * @param language - Language code (e.g., \"en-US\") or \"auto\" for automatic detection\n * @param handler - Function to handle transcription data\n * @param optionsOrBoolean - Optional configuration object or boolean (backward compatible)\n * @param optionsOrBoolean.disableLanguageIdentification - Disable language identification (defaults to false/enabled)\n * @param optionsOrBoolean.hints - Array of language code hints to improve detection (e.g., [\"es\", \"fr\"])\n * @returns Cleanup function to remove the handler\n * @throws Error if language code is invalid\n */\n onTranscriptionForLanguage(\n language: string,\n handler: Handler<TranscriptionData>,\n optionsOrBoolean?:\n | boolean\n | {\n disableLanguageIdentification?: boolean\n hints?: string[]\n },\n ): () => void {\n if (language !== \"auto\" && !isValidLanguageCode(language)) {\n throw new Error(`Invalid language code: ${language}`)\n }\n this.lastLanguageTranscriptioCleanupHandler()\n\n // Handle backward compatibility: boolean or options object\n const options =\n typeof optionsOrBoolean === \"boolean\" ? {disableLanguageIdentification: optionsOrBoolean} : optionsOrBoolean\n\n const streamType = createTranscriptionStream(language, options)\n this.lastLanguageTranscriptioCleanupHandler = this.addHandler(streamType, handler)\n return this.lastLanguageTranscriptioCleanupHandler\n }\n\n /**\n * 🌐 Listen for translation events for a specific language pair\n * @param sourceLanguage - Source language code (e.g., \"es-ES\")\n * @param targetLanguage - Target language code (e.g., \"en-US\")\n * @param handler - Function to handle translation data\n * @returns Cleanup function to remove the handler\n * @throws Error if language codes are invalid\n */\n ontranslationForLanguage(\n sourceLanguage: string,\n targetLanguage: string,\n handler: Handler<TranslationData>,\n ): () => void {\n microPhoneWarnLog(this.baseUrl || \"\", this.packageName, this.ontranslationForLanguage.name)\n if (!isValidLanguageCode(sourceLanguage)) {\n throw new Error(`Invalid source language code: ${sourceLanguage}`)\n }\n if (!isValidLanguageCode(targetLanguage)) {\n throw new Error(`Invalid target language code: ${targetLanguage}`)\n }\n\n this.lastLanguageTranslationCleanupHandler()\n const streamType = createTranslationStream(sourceLanguage, targetLanguage)\n this.lastLanguageTranslationCleanupHandler = this.addHandler(streamType, handler)\n\n return this.lastLanguageTranslationCleanupHandler\n }\n\n onHeadPosition(handler: Handler<HeadPosition>) {\n return this.addHandler(StreamType.HEAD_POSITION, handler)\n }\n\n onButtonPress(handler: Handler<ButtonPress>) {\n return this.addHandler(StreamType.BUTTON_PRESS, handler)\n }\n\n onTouchEvent(gestureOrHandler: string | Handler<TouchEvent>, handler?: Handler<TouchEvent>): () => void {\n // Handle both: onTouchEvent(handler) and onTouchEvent(\"forward_swipe\", handler)\n if (typeof gestureOrHandler === \"function\") {\n // Subscribe to all touch events\n return this.addHandler(StreamType.TOUCH_EVENT, gestureOrHandler)\n } else {\n // Subscribe to specific gesture\n const gestureStream = createTouchEventStream(gestureOrHandler)\n return this.addHandler(gestureStream, handler!)\n }\n }\n\n onPhoneNotifications(handler: Handler<PhoneNotification>) {\n return this.addHandler(StreamType.PHONE_NOTIFICATION, handler)\n }\n\n onPhoneNotificationDismissed(handler: Handler<PhoneNotificationDismissed>) {\n return this.addHandler(StreamType.PHONE_NOTIFICATION_DISMISSED, handler)\n }\n\n onGlassesBattery(handler: Handler<GlassesBatteryUpdate>) {\n return this.addHandler(StreamType.GLASSES_BATTERY_UPDATE, handler)\n }\n\n onPhoneBattery(handler: Handler<PhoneBatteryUpdate>) {\n return this.addHandler(StreamType.PHONE_BATTERY_UPDATE, handler)\n }\n\n onVoiceActivity(handler: Handler<Vad>) {\n microPhoneWarnLog(this.baseUrl || \"\", this.packageName, this.onVoiceActivity.name)\n return this.addHandler(StreamType.VAD, handler)\n }\n\n onLocation(handler: Handler<LocationUpdate>) {\n return this.addHandler(StreamType.LOCATION_UPDATE, handler)\n }\n\n onCalendarEvent(handler: Handler<CalendarEvent>) {\n return this.addHandler(StreamType.CALENDAR_EVENT, handler)\n }\n\n /**\n * 🎤 Listen for audio chunk data\n * @param handler - Function to handle audio chunks\n * @returns Cleanup function to remove the handler\n */\n onAudioChunk(handler: Handler<AudioChunk>) {\n return this.addHandler(StreamType.AUDIO_CHUNK, handler)\n }\n\n // System event handlers\n\n onConnected(handler: Handler<SystemEvents[\"connected\"]>) {\n this.emitter.on(\"connected\", handler)\n return () => this.emitter.off(\"connected\", handler)\n }\n\n onDisconnected(handler: Handler<SystemEvents[\"disconnected\"]>) {\n this.emitter.on(\"disconnected\", handler)\n return () => this.emitter.off(\"disconnected\", handler)\n }\n\n onError(handler: Handler<SystemEvents[\"error\"]>) {\n this.emitter.on(\"error\", handler)\n return () => this.emitter.off(\"error\", handler)\n }\n\n onSettingsUpdate(handler: Handler<SystemEvents[\"settings_update\"]>) {\n this.emitter.on(\"settings_update\", handler)\n return () => this.emitter.off(\"settings_update\", handler)\n }\n\n /**\n * 🔧 Listen for device capabilities updates\n * @param handler - Function to handle capabilities updates\n * @returns Cleanup function to remove the handler\n */\n onCapabilitiesUpdate(handler: Handler<SystemEvents[\"capabilities_update\"]>) {\n this.emitter.on(\"capabilities_update\", handler)\n return () => this.emitter.off(\"capabilities_update\", handler)\n }\n\n /**\n * 🌐 Listen for dashboard mode changes\n * @param handler - Function to handle dashboard mode changes\n * @returns Cleanup function to remove the handler\n */\n onDashboardModeChange(handler: Handler<SystemEvents[\"dashboard_mode_change\"]>) {\n this.emitter.on(\"dashboard_mode_change\", handler)\n return () => this.emitter.off(\"dashboard_mode_change\", handler)\n }\n\n /**\n * 🌐 Listen for dashboard always-on mode changes\n * @param handler - Function to handle dashboard always-on mode changes\n * @returns Cleanup function to remove the handler\n */\n onDashboardAlwaysOnChange(handler: Handler<SystemEvents[\"dashboard_always_on_change\"]>) {\n this.emitter.on(\"dashboard_always_on_change\", handler)\n return () => this.emitter.off(\"dashboard_always_on_change\", handler)\n }\n\n /**\n * 🚫 Listen for permission errors when subscriptions are rejected\n * @param handler - Function to handle permission errors\n * @returns Cleanup function to remove the handler\n */\n onPermissionError(handler: Handler<SystemEvents[\"permission_error\"]>) {\n this.emitter.on(\"permission_error\", handler)\n return () => this.emitter.off(\"permission_error\", handler)\n }\n\n /**\n * 🚫 Listen for individual permission denied events for specific streams\n * @param handler - Function to handle permission denied events\n * @returns Cleanup function to remove the handler\n */\n onPermissionDenied(handler: Handler<SystemEvents[\"permission_denied\"]>) {\n this.emitter.on(\"permission_denied\", handler)\n return () => this.emitter.off(\"permission_denied\", handler)\n }\n\n /**\n * 🔄 Listen for changes to a specific setting\n * @param key - Setting key to monitor\n * @param handler - Function to handle setting value changes\n * @returns Cleanup function to remove the handler\n */\n onSettingChange<T>(key: string, handler: (value: T, previousValue: T | undefined) => void): () => void {\n let previousValue: T | undefined = undefined\n\n const settingsHandler = (settings: AppSettings) => {\n try {\n const setting = settings.find((s) => s.key === key)\n if (setting) {\n // Only call handler if value has changed\n if (setting.value !== previousValue) {\n const newValue = setting.value as T\n handler(newValue, previousValue)\n previousValue = newValue\n }\n }\n } catch (error: unknown) {\n console.error(`Error in onSettingChange handler for key \"${key}\":`, error)\n }\n }\n\n this.emitter.on(\"settings_update\", settingsHandler)\n this.emitter.on(\"connected\", settingsHandler) // Also check when first connected\n\n return () => {\n this.emitter.off(\"settings_update\", settingsHandler)\n this.emitter.off(\"connected\", settingsHandler)\n }\n }\n\n /**\n * 🔄 Generic event handler\n *\n * Use this for stream types without specific handler methods\n */\n on<T extends ExtendedStreamType>(type: T, handler: Handler<EventData<T>>): () => void {\n // Check permissions for specific stream types\n if (type === StreamType.CALENDAR_EVENT) {\n calendarWarnLog(this.baseUrl, this.packageName, \"on\")\n }\n return this.addHandler(type, handler)\n }\n\n /**\n * ➕ Add an event handler and subscribe if needed\n */\n private addHandler<T extends ExtendedStreamType>(type: T, handler: Handler<EventData<T>>): () => void {\n const handlers = this.handlers.get(type) ?? new Set()\n\n if (handlers.size === 0) {\n this.handlers.set(type, handlers)\n this.subscribe(type)\n }\n handlers.add(handler as Handler<unknown>)\n return () => this.removeHandler(type, handler)\n }\n\n /**\n * ➖ Remove an event handler\n */\n private removeHandler<T extends ExtendedStreamType>(type: T, handler: Handler<EventData<T>>): void {\n const handlers = this.handlers.get(type)\n if (!handlers) return\n\n handlers.delete(handler as Handler<unknown>)\n if (handlers.size === 0) {\n this.handlers.delete(type)\n this.unsubscribe(type)\n }\n }\n\n /**\n * 🔍 Get all currently registered stream types\n * Returns the streams that have at least one handler registered.\n * Used to derive subscriptions from handlers (single source of truth).\n *\n * This is the fix for Bug 007: subscriptions are now derived from handlers\n * instead of being stored separately, preventing drift between the two.\n */\n getRegisteredStreams(): ExtendedStreamType[] {\n return Array.from(this.handlers.keys()) as ExtendedStreamType[]\n }\n\n /**\n * 📡 Emit an event to all registered handlers with error isolation\n */\n emit<T extends EventType>(event: T, data: EventData<T>): void {\n try {\n // Emit to EventEmitter handlers (system events)\n // console.log(`#### Emitting to ${event}`);\n this.emitter.emit(event, data)\n\n // Emit to stream handlers if applicable\n const handlers = this.handlers.get(event)\n // console.log(`#### Handlers: ${JSON.stringify(handlers)}`);\n\n if (handlers) {\n // Create array of handlers to prevent modification during iteration\n const handlersArray = Array.from(handlers)\n // console.log(`((())) HandlersArray: ${JSON.stringify(handlersArray)}`);\n\n // Execute each handler in isolated try/catch to prevent one handler\n // from crashing the entire App\n handlersArray.forEach((handler) => {\n try {\n ;(handler as Handler<EventData<T>>)(data)\n } catch (handlerError: unknown) {\n // Log the error but don't let it propagate\n console.error(`Error in handler for event '${String(event)}':`, handlerError)\n\n // Emit an error event for tracking purposes\n if (event !== \"error\") {\n // Prevent infinite recursion\n const errorMessage = handlerError instanceof Error ? handlerError.message : String(handlerError)\n\n this.emitter.emit(\"error\", new Error(`Handler error for event '${String(event)}': ${errorMessage}`))\n }\n }\n })\n }\n } catch (emitError: unknown) {\n // Catch any errors in the emission process itself\n console.error(`Fatal error emitting event '${String(event)}':`, emitError)\n\n // Try to emit an error event if we're not already handling an error\n if (event !== \"error\") {\n try {\n const errorMessage = emitError instanceof Error ? emitError.message : String(emitError)\n\n this.emitter.emit(\"error\", new Error(`Event emission error for '${String(event)}': ${errorMessage}`))\n } catch (nestedError) {\n // If even this fails, just log it - nothing more we can do\n console.error(\"Failed to emit error event:\", nestedError)\n }\n }\n }\n }\n\n /**\n * 📨 Listen for custom messages with a specific action\n * @param action - The action identifier to filter by\n * @param handler - Function to handle the message\n * @returns Cleanup function to remove the handler\n */\n onCustomMessage(action: string, handler: (payload: any) => void): () => void {\n const messageHandler = (message: CustomMessage) => {\n if (message.action === action) {\n handler(message.payload)\n }\n }\n\n this.emitter.on(\"custom_message\", messageHandler)\n return () => this.emitter.off(\"custom_message\", messageHandler)\n }\n\n onVpsCoordinates(handler: Handler<VpsCoordinates>) {\n return this.addHandler(StreamType.VPS_COORDINATES, handler)\n }\n\n /**\n * 📸 Listen for photo responses\n * @param handler - Function to handle photo response data\n * @returns Cleanup function to remove the handler\n */\n onPhotoTaken(handler: Handler<PhotoTaken>) {\n return this.addHandler(StreamType.PHOTO_TAKEN, handler)\n }\n}\n",
23
- "// src/index.ts\n\nexport * from \"./token\"\n\n// Message type enums\nexport * from \"./message-types\"\n\n// Base message type\nexport * from \"./messages/base\"\n\n// Messages by direction - export everything except the conflicting type guards\nexport * from \"./messages/glasses-to-cloud\"\nexport * from \"./messages/cloud-to-glasses\"\n\n// Export from app-to-cloud excluding isPhotoRequest which conflicts with cloud-to-glasses\nexport {\n // Types\n SubscriptionRequest,\n AppConnectionInit,\n AppSubscriptionUpdate,\n PhotoRequest,\n RgbLedControlRequest,\n RtmpStreamRequest,\n RtmpStreamStopRequest,\n AppLocationPollRequest,\n RestreamDestination,\n ManagedStreamRequest,\n ManagedStreamStopRequest,\n StreamStatusCheckRequest,\n AudioPlayRequest,\n AudioStopRequest,\n AppToCloudMessage,\n AppBroadcastMessage,\n AppDirectMessage,\n AppUserDiscovery,\n AppRoomJoin,\n AppRoomLeave,\n RequestWifiSetup,\n OwnershipReleaseMessage,\n // Type guards - all except isPhotoRequest\n isAppConnectionInit,\n isAppSubscriptionUpdate,\n isDisplayRequest,\n isRgbLedControlRequest,\n isAudioPlayRequest,\n isAudioStopRequest,\n isDashboardContentUpdate,\n isDashboardModeChange,\n isDashboardSystemUpdate,\n isManagedStreamRequest,\n isManagedStreamStopRequest,\n isRtmpStreamRequest,\n isRtmpStreamStopRequest,\n isOwnershipRelease,\n // Export with alias to avoid conflict\n isPhotoRequest as isPhotoRequestFromApp,\n} from \"./messages/app-to-cloud\"\n\n// Export cloud-to-app but exclude the conflicting type guards\nexport {\n // Types\n AppConnectionAck,\n AppConnectionError,\n AppStopped,\n SettingsUpdate as AppSettingsUpdate, // Alias to avoid conflict with cloud-to-glasses SettingsUpdate\n CapabilitiesUpdate,\n DataStream,\n CloudToAppMessage,\n AudioPlayResponse,\n TranslationData,\n ToolCall,\n StandardConnectionError,\n CustomMessage,\n ManagedStreamStatus,\n StreamStatusCheckResponse,\n OutputStatus,\n MentraosSettingsUpdate,\n TranscriptionData,\n TranscriptionMetadata,\n SonioxToken,\n AudioChunk,\n PermissionError,\n PermissionErrorDetail,\n // Type guards (excluding isPhotoResponse and isRtmpStreamStatus which conflict)\n isAppConnectionAck,\n isAppConnectionError,\n isAppStopped,\n isSettingsUpdate,\n isCapabilitiesUpdate,\n isDataStream,\n isAudioChunk,\n isAudioPlayResponse,\n isDashboardModeChanged,\n isDashboardAlwaysOnChanged,\n isManagedStreamStatus,\n isStreamStatusCheckResponse,\n // Re-export the cloud-to-app versions of these type guards since they're the ones\n // that should be used when dealing with CloudToAppMessage types\n isPhotoResponse as isPhotoResponseFromCloud,\n isRtmpStreamStatus as isRtmpStreamStatusFromCloud,\n isRgbLedControlResponse as isRgbLedControlResponseFromCloud,\n} from \"./messages/cloud-to-app\"\n\n// Stream types\nexport * from \"./streams\"\n\n// Layout types\nexport * from \"./layouts\"\n\n// Dashboard types\nexport * from \"./dashboard\"\n\n// RTMP streaming types\nexport * from \"./rtmp-stream\"\n\n// Other system enums\nexport * from \"./enums\"\n\n// Core model interfaces\nexport * from \"./models\"\n\n// Webhook interfaces\nexport * from \"./webhooks\"\n\n// Capability Discovery types\nexport * from \"./capabilities\"\n\n// Photo data types\nexport * from \"./photo-data\"\n\n/**\n * WebSocket error information\n */\nexport interface WebSocketError {\n code: string\n message: string\n details?: unknown\n}\n\nimport type {Request} from \"express\"\nimport type {AppSession} from \"../app/session\"\n\nexport interface AuthenticatedRequest extends Request {\n authUserId?: string\n activeSession: AppSession | null\n}\n",
24
- "/**\n * warning.ts\n *\n * This file defines styled warning messages that are displayed when an app\n * attempts to use functionality requiring permissions it hasn't declared.\n *\n * Each function generates a bordered terminal warning box with:\n * - ASCII art logo (left side)\n * - Permission requirement details (right side)\n * - Link to developer portal for adding permissions\n *\n * The warnings use chalk for terminal colors and boxen for bordered output,\n * creating a professional side-by-side layout that alerts developers to\n * missing permissions in their app configuration.\n *\n * These are shown during SDK runtime when permission checks fail, helping\n * developers identify and fix permission issues quickly.\n */\nimport chalk from \"chalk\";\nimport boxen from \"boxen\";\nimport { warnLog } from \"./logos\";\n\nconst createPermissionWarning = (\n permissionName: string,\n funcName?: string,\n packageName?: string,\n): string => {\n // Strip ANSI codes for width calculation\n // eslint-disable-next-line no-control-regex\n const stripAnsi = (str: string) => str.replace(/\\u001b\\[\\d+m/g, \"\");\n\n const title = chalk.bold.yellow(\"⚠️ Permission Required\");\n const message = `${chalk.yellow(funcName || \"This function\")} requires ${chalk.bold(permissionName)} permission.`;\n const instructions = chalk.dim(\n \"Please enable this permission in the developer portal at:\",\n );\n const url = chalk.cyan.underline(\n `https://console.mentra.glass/apps/${packageName}/edit`,\n );\n const hint = chalk.dim(\"under *Required Permissions*.\");\n\n // Split logo into lines (without color for width calculation)\n const logoLines = warnLog.split(\"\\n\");\n const coloredLogoLines = logoLines.map((line) => chalk.yellow(line));\n\n const textContent = [title, \"\", message, \"\", instructions, url, \"\", hint];\n\n // Find actual logo width (max line length without ANSI codes)\n const logoWidth = Math.max(\n ...logoLines.map((line) => stripAnsi(line).length),\n );\n\n // Create side-by-side layout\n const maxLines = Math.max(coloredLogoLines.length, textContent.length);\n const combinedLines: string[] = [];\n\n for (let i = 0; i < maxLines; i++) {\n const rawLogoLine = logoLines[i] || \"\";\n const coloredLogoLine = coloredLogoLines[i] || \"\";\n const actualLogoLength = stripAnsi(rawLogoLine).length;\n const padding = \" \".repeat(Math.max(0, logoWidth - actualLogoLength));\n const textLine = textContent[i] || \"\";\n combinedLines.push(`${coloredLogoLine}${padding} ${textLine}`);\n }\n\n return boxen(combinedLines.join(\"\\n\"), {\n padding: 1,\n margin: 1,\n borderStyle: \"round\",\n borderColor: \"yellow\",\n float: \"left\",\n });\n};\n\nexport const noMicrophoneWarn = (\n funcName?: string,\n packageName?: string,\n): string => {\n return createPermissionWarning(\"microphone\", funcName, packageName);\n};\n\nexport const locationWarn = (\n funcName?: string,\n packageName?: string,\n): string => {\n return createPermissionWarning(\"location\", funcName, packageName);\n};\n\nexport const baackgroundLocationWarn = (\n funcName?: string,\n packageName?: string,\n): string => {\n return createPermissionWarning(\"background location\", funcName, packageName);\n};\n\nexport const calendarWarn = (\n funcName?: string,\n packageName?: string,\n): string => {\n return createPermissionWarning(\"calendar\", funcName, packageName);\n};\n\nexport const readNotficationWarn = (\n funcName?: string,\n packageName?: string,\n): string => {\n return createPermissionWarning(\"read notification\", funcName, packageName);\n};\n\nexport const postNotficationWarn = (\n funcName?: string,\n packageName?: string,\n): string => {\n return createPermissionWarning(\"post notification\", funcName, packageName);\n};\n\nexport const cameraWarn = (funcName?: string, packageName?: string): string => {\n return createPermissionWarning(\"camera\", funcName, packageName);\n};\n",
25
- "export const warnLog = String.raw`\n__/\\\\\\\\____________/\\\\\\\\_ \n _\\/\\\\\\\\\\\\________/\\\\\\\\\\\\_ \n _\\/\\\\\\//\\\\\\____/\\\\\\//\\\\\\_ \n _\\/\\\\\\\\///\\\\\\/\\\\\\/_\\/\\\\\\_ \n _\\/\\\\\\__\\///\\\\\\/___\\/\\\\\\_ \n _\\/\\\\\\____\\///_____\\/\\\\\\_ \n _\\/\\\\\\_____________\\/\\\\\\_ \n _\\/\\\\\\_____________\\/\\\\\\_ \n _\\///______________\\///__`;\n\nexport const mentraLogo_1 = String.raw`\n __ __ ________ __ __ ________ _______ ______ \n| \\ / \\| \\| \\ | \\| \\| \\ / \\ \n| $$\\ / $$| $$$$$$$$| $$\\ | $$ \\$$$$$$$$| $$$$$$$\\| $$$$$$\\\n| $$$\\ / $$$| $$__ | $$$\\| $$ | $$ | $$__| $$| $$__| $$\n| $$$$\\ $$$$| $$ \\ | $$$$\\ $$ | $$ | $$ $$| $$ $$\n| $$\\$$ $$ $$| $$$$$ | $$\\$$ $$ | $$ | $$$$$$$\\| $$$$$$$$\n| $$ \\$$$| $$| $$_____ | $$ \\$$$$ | $$ | $$ | $$| $$ | $$\n| $$ \\$ | $$| $$ \\| $$ \\$$$ | $$ | $$ | $$| $$ | $$\n \\$$ \\$$ \\$$$$$$$$ \\$$ \\$$ \\$$ \\$$ \\$$ \\$$ \\$$\n`;\n\nexport const newUpdateText = `\n┬╔╗╔╔═╗╦ ╦ ╦ ╦╔═╗╔╦╗╔═╗╔╦╗╔═╗┬\n│║║║║╣ ║║║ ║ ║╠═╝ ║║╠═╣ ║ ║╣ │\no╝╚╝╚═╝╚╩╝ ╚═╝╩ ═╩╝╩ ╩ ╩ ╚═╝o\n`;\n// export const newSDKUpdate = (versionNumb: string): string => {\n// return `\n\n// /$$ /$$ /$$$$$$$$ /$$ /$$ /$$$$$$$$ /$$$$$$$ /$$$$$$\n// | $$$ /$$$| $$_____/| $$$ | $$|__ $$__/| $$__ $$ /$$__ $$\n// | $$$$ /$$$$| $$ | $$$$| $$ | $$ | $$ \\ $$| $$ \\ $$\n// | $$ $$/$$ $$| $$$$$ | $$ $$ $$ | $$ | $$$$$$$/| $$$$$$$$\n// | $$ $$$| $$| $$__/ | $$ $$$$ | $$ | $$__ $$| $$__ $$\n// | $$\\ $ | $$| $$ | $$\\ $$$ | $$ | $$ \\ $$| $$ | $$\n// | $$ \\/ | $$| $$$$$$$$| $$ \\ $$ | $$ | $$ | $$| $$ | $$\n// |__/ |__/|________/|__/ \\__/ |__/ |__/ |__/|__/ |__/\n\n// ┬╔╗╔╔═╗╦ ╦ ╦ ╦╔═╗╔╦╗╔═╗╔╦╗╔═╗┬\n// │║║║║╣ ║║║ ║ ║╠═╝ ║║╠═╣ ║ ║╣ │\n// o╝╚╝╚═╝╚╩╝ ╚═╝╩ ═╩╝╩ ╩ ╩ ╚═╝o\n// -------------------------------\n// SDK VERSION V${versionNumb} is out!\n// -------------------------------\n// bun install @mentra/sdk@latest\n// `;\n// };\n",
26
- "/**\n * permissions-utils.ts\n *\n * This file provides runtime permission validation utilities for the MentraOS SDK.\n *\n * Each function queries the public permissions API endpoint to check if an app\n * has declared the required permission for a specific feature. If the permission\n * is missing, a styled warning message is displayed in the terminal.\n *\n * Key features:\n * - Fetches app permissions from /api/public/permissions/:packageName\n * - Gracefully handles offline/unreachable endpoints (silent failure)\n * - Displays professional bordered warnings when permissions are missing\n * - Non-blocking - allows app execution to continue even if checks fail\n *\n * These functions are called automatically by SDK methods that require specific\n * permissions (e.g., microphone access, location tracking, camera, etc.) to help\n * developers identify missing permission declarations during development.\n */\nimport {\n noMicrophoneWarn,\n locationWarn,\n baackgroundLocationWarn,\n calendarWarn,\n readNotficationWarn,\n postNotficationWarn,\n cameraWarn,\n} from \"../constants/log-messages/warning\";\nimport {\n PackagePermissions,\n Permission,\n} from \"../../src/types/messages/cloud-to-app\";\n// Check if app has microphone permission, warn if missing\nexport const microPhoneWarnLog = (\n cloudServerUrl: string,\n packageName: string,\n funcName?: string,\n) => {\n if (!cloudServerUrl) return;\n\n const permissionsUrl = `${cloudServerUrl}/api/public/permissions/${encodeURIComponent(packageName)}`;\n\n // console.log(`Fetching permissions from: ${permissionsUrl}`);\n fetch(permissionsUrl)\n .then(async (res) => {\n const contentType = res.headers.get(\"content-type\");\n if (!res.ok) {\n console.warn(\n `Permission API returned ${res.status}: ${res.statusText}`,\n );\n return null;\n }\n\n if (contentType && contentType.includes(\"application/json\")) {\n return (await res.json()) as PackagePermissions;\n } else {\n const text = await res.text();\n console.warn(`Permission API returned non-JSON response: ${text}`);\n return null;\n }\n })\n .then((data: PackagePermissions | null) => {\n if (data) {\n const hasMic = data.permissions.some(\n (p: Permission) => p.type === \"MICROPHONE\",\n );\n\n if (!hasMic) {\n console.log(noMicrophoneWarn(funcName, packageName));\n }\n }\n })\n .catch((err) => {\n // Silently fail if endpoint is unreachable - don't block execution\n console.debug(\n \"Permission check skipped - endpoint unreachable:\",\n err.message,\n );\n });\n};\n\n// Check if app has location permission, warn if missing\nexport const locationWarnLog = (\n cloudServerUrl: string,\n packageName: string,\n funcName?: string,\n) => {\n if (!cloudServerUrl) return;\n\n const permissionsUrl = `${cloudServerUrl}/api/public/permissions/${encodeURIComponent(packageName)}`;\n\n fetch(permissionsUrl)\n .then(async (res) => {\n const contentType = res.headers.get(\"content-type\");\n if (!res.ok) {\n console.warn(\n `Permission API returned ${res.status}: ${res.statusText}`,\n );\n return null;\n }\n\n if (contentType && contentType.includes(\"application/json\")) {\n return (await res.json()) as PackagePermissions;\n } else {\n const text = await res.text();\n console.warn(`Permission API returned non-JSON response: ${text}`);\n return null;\n }\n })\n .then((data: PackagePermissions | null) => {\n if (data) {\n const hasLocation = data.permissions.some(\n (p: Permission) => p.type === \"LOCATION\",\n );\n\n if (!hasLocation) {\n console.log(locationWarn(funcName, packageName));\n }\n }\n })\n .catch((err) => {\n console.debug(\n \"Permission check skipped - endpoint unreachable:\",\n err.message,\n );\n });\n};\n\n// Check if app has background location permission, warn if missing\nexport const backgroundLocationWarnLog = (\n cloudServerUrl: string,\n packageName: string,\n funcName?: string,\n) => {\n if (!cloudServerUrl) return;\n\n const permissionsUrl = `${cloudServerUrl}/api/public/permissions/${encodeURIComponent(packageName)}`;\n\n fetch(permissionsUrl)\n .then(async (res) => {\n const contentType = res.headers.get(\"content-type\");\n if (!res.ok) {\n console.warn(\n `Permission API returned ${res.status}: ${res.statusText}`,\n );\n return null;\n }\n\n if (contentType && contentType.includes(\"application/json\")) {\n return (await res.json()) as PackagePermissions;\n } else {\n const text = await res.text();\n console.warn(`Permission API returned non-JSON response: ${text}`);\n return null;\n }\n })\n .then((data: PackagePermissions | null) => {\n if (data) {\n const hasBackgroundLocation = data.permissions.some(\n (p: Permission) => p.type === \"BACKGROUND_LOCATION\",\n );\n\n if (!hasBackgroundLocation) {\n console.log(baackgroundLocationWarn(funcName, packageName));\n }\n }\n })\n .catch((err) => {\n console.debug(\n \"Permission check skipped - endpoint unreachable:\",\n err.message,\n );\n });\n};\n\n// Check if app has calendar permission, warn if missing\nexport const calendarWarnLog = (\n cloudServerUrl: string,\n packageName: string,\n funcName?: string,\n) => {\n if (!cloudServerUrl) return;\n\n const permissionsUrl = `${cloudServerUrl}/api/public/permissions/${encodeURIComponent(packageName)}`;\n\n fetch(permissionsUrl)\n .then(async (res) => {\n const contentType = res.headers.get(\"content-type\");\n if (!res.ok) {\n console.warn(\n `Permission API returned ${res.status}: ${res.statusText}`,\n );\n return null;\n }\n\n if (contentType && contentType.includes(\"application/json\")) {\n return (await res.json()) as PackagePermissions;\n } else {\n const text = await res.text();\n console.warn(`Permission API returned non-JSON response: ${text}`);\n return null;\n }\n })\n .then((data: PackagePermissions | null) => {\n if (data) {\n const hasCalendar = data.permissions.some(\n (p: Permission) => p.type === \"CALENDAR\",\n );\n\n if (!hasCalendar) {\n console.log(calendarWarn(funcName, packageName));\n }\n }\n })\n .catch((err) => {\n console.debug(\n \"Permission check skipped - endpoint unreachable:\",\n err.message,\n );\n });\n};\n\n// Check if app has read notifications permission, warn if missing\nexport const readNotificationWarnLog = (\n cloudServerUrl: string,\n packageName: string,\n funcName?: string,\n) => {\n if (!cloudServerUrl) return;\n\n const permissionsUrl = `${cloudServerUrl}/api/public/permissions/${encodeURIComponent(packageName)}`;\n\n fetch(permissionsUrl)\n .then(async (res) => {\n const contentType = res.headers.get(\"content-type\");\n if (!res.ok) {\n console.warn(\n `Permission API returned ${res.status}: ${res.statusText}`,\n );\n return null;\n }\n\n if (contentType && contentType.includes(\"application/json\")) {\n return (await res.json()) as PackagePermissions;\n } else {\n const text = await res.text();\n console.warn(`Permission API returned non-JSON response: ${text}`);\n return null;\n }\n })\n .then((data: PackagePermissions | null) => {\n if (data) {\n const hasReadNotifications = data.permissions.some(\n (p: Permission) => p.type === \"READ_NOTIFICATIONS\",\n );\n\n if (!hasReadNotifications) {\n console.log(readNotficationWarn(funcName, packageName));\n }\n }\n })\n .catch((err) => {\n console.debug(\n \"Permission check skipped - endpoint unreachable:\",\n err.message,\n );\n });\n};\n\n// Check if app has post notifications permission, warn if missing\nexport const postNotificationWarnLog = (\n cloudServerUrl: string,\n packageName: string,\n funcName?: string,\n) => {\n if (!cloudServerUrl) return;\n\n const permissionsUrl = `${cloudServerUrl}/api/public/permissions/${encodeURIComponent(packageName)}`;\n\n fetch(permissionsUrl)\n .then(async (res) => {\n const contentType = res.headers.get(\"content-type\");\n if (!res.ok) {\n console.warn(\n `Permission API returned ${res.status}: ${res.statusText}`,\n );\n return null;\n }\n\n if (contentType && contentType.includes(\"application/json\")) {\n return (await res.json()) as PackagePermissions;\n } else {\n const text = await res.text();\n console.warn(`Permission API returned non-JSON response: ${text}`);\n return null;\n }\n })\n .then((data: PackagePermissions | null) => {\n if (data) {\n const hasPostNotifications = data.permissions.some(\n (p: Permission) => p.type === \"POST_NOTIFICATIONS\",\n );\n\n if (!hasPostNotifications) {\n console.log(postNotficationWarn(funcName, packageName));\n }\n }\n })\n .catch((err) => {\n console.debug(\n \"Permission check skipped - endpoint unreachable:\",\n err.message,\n );\n });\n};\n\n// Check if app has camera permission, warn if missing\nexport const cameraWarnLog = (\n cloudServerUrl: string,\n packageName: string,\n funcName?: string,\n) => {\n if (!cloudServerUrl) return;\n\n const permissionsUrl = `${cloudServerUrl}/api/public/permissions/${encodeURIComponent(packageName)}`;\n\n fetch(permissionsUrl)\n .then(async (res) => {\n const contentType = res.headers.get(\"content-type\");\n if (!res.ok) {\n console.warn(\n `Permission API returned ${res.status}: ${res.statusText}`,\n );\n return null;\n }\n\n if (contentType && contentType.includes(\"application/json\")) {\n return (await res.json()) as PackagePermissions;\n } else {\n const text = await res.text();\n console.warn(`Permission API returned non-JSON response: ${text}`);\n return null;\n }\n })\n .then((data: PackagePermissions | null) => {\n if (data) {\n const hasCamera = data.permissions.some(\n (p: Permission) => p.type === \"CAMERA\",\n );\n\n if (!hasCamera) {\n console.log(cameraWarn(funcName, packageName));\n }\n }\n })\n .catch((err) => {\n console.debug(\n \"Permission check skipped - endpoint unreachable:\",\n err.message,\n );\n });\n};\n",
48
+ "/**\n * App Server Module\n *\n * Creates and manages a server for Apps in the MentraOS ecosystem.\n * Handles webhook endpoints, session management, and cleanup.\n *\n * Now built on Hono + Bun for better performance and developer experience.\n */\nimport fs from \"fs\";\nimport path from \"path\";\n\nimport { Hono } from \"hono\";\nimport type { Context, MiddlewareHandler } from \"hono\";\nimport { serveStatic } from \"hono/bun\";\nimport { Logger } from \"pino\";\n\nimport { getDistTag } from \"../../constants/log-messages/updates\";\nimport { createLogger } from \"../../logging/logger\";\nimport {\n WebhookRequest,\n WebhookResponse,\n SessionWebhookRequest,\n StopWebhookRequest,\n ToolCall,\n WebhookRequestType,\n AuthVariables,\n} from \"../../types\";\n\nimport { AppSession } from \"../session/index\";\nimport { createAuthMiddleware, createMentraAuthRoutes } from \"../webview\";\n\n// Import PhotoData type for pending photo requests\nimport type { PhotoData } from \"../../types/photo-data\";\n\nexport const GIVE_APP_CONTROL_OF_TOOL_RESPONSE: string = \"GIVE_APP_CONTROL_OF_TOOL_RESPONSE\";\nconst SDK_ROUTE_PREFIX = \"/api/_mentraos\";\nconst LEGACY_MENTRA_AUTH_ROUTE_PREFIX = \"/api/mentra/auth\";\n\n/**\n * Pending photo request stored at AppServer level for reconnection resilience.\n * This allows O(1) lookup when photo uploads arrive via HTTP,\n * and survives session reconnections.\n * See: cloud/issues/019-sdk-photo-request-architecture\n */\ninterface PendingPhotoRequest {\n userId: string;\n sessionId: string;\n session: AppSession;\n resolve: (photo: PhotoData) => void;\n reject: (error: Error) => void;\n timestamp: number;\n timeoutId?: ReturnType<typeof setTimeout>;\n}\n\n/**\n * 🔧 Configuration options for App Server\n *\n * @example\n * ```typescript\n * const config: AppServerConfig = {\n * packageName: 'org.example.myapp',\n * apiKey: 'your_api_key',\n * port: 7010,\n * publicDir: './public'\n * };\n * ```\n */\nexport interface AppServerConfig {\n /** 📦 Unique identifier for your App (e.g., 'org.company.appname') must match what you specified at https://console.mentra.glass */\n packageName: string;\n /** 🔑 API key for authentication with MentraOS Cloud */\n apiKey: string;\n /** 🌐 Port number for the server (default: 7010) */\n port?: number;\n\n /** Cloud API URL (default: 'api.mentra.glass') */\n cloudApiUrl?: string;\n\n /** 🛣️ [DEPRECATED] do not set: The SDK will automatically expose an endpoint at '/webhook' */\n webhookPath?: string;\n /**\n * 📂 Directory for serving static files (e.g., images, logos)\n * Set to false to disable static file serving\n */\n publicDir?: string | false;\n\n /** ❤️ Enable health check endpoint at /health (default: true) */\n healthCheck?: boolean;\n /**\n * 🔐 Secret key used to sign session cookies\n * This must be a strong, unique secret\n */\n cookieSecret?: string;\n /** App instructions string shown to the user */\n appInstructions?: string;\n\n /**\n * Enable verbose SDK logging. Shows all internal SDK messages in the terminal\n * (WebSocket lifecycle, subscription updates, message routing, etc.).\n * Useful for debugging connection and streaming issues.\n *\n * Can also be enabled with the MENTRA_VERBOSE=true environment variable.\n * This config option takes priority over the env var.\n */\n verbose?: boolean;\n\n /**\n * SDK log level. Controls which log messages appear in the terminal.\n * - 'error': Only errors\n * - 'warn': Errors and warnings\n * - 'info': Normal operation (default)\n * - 'debug': Everything including SDK internals (implies verbose)\n *\n * Can also be set with the MENTRA_LOG_LEVEL environment variable.\n * This config option takes priority over the env var.\n */\n logLevel?: \"error\" | \"warn\" | \"info\" | \"debug\";\n}\n\n// Type for Hono app with auth variables\ntype AppHono = Hono<{ Variables: AuthVariables }>;\n\n/**\n * 🎯 App Server Implementation\n *\n * Base class for creating App servers, now extending Hono for a modern API.\n * Handles:\n * - 🔄 Session lifecycle management\n * - 📡 Webhook endpoints for MentraOS Cloud\n * - 📂 Static file serving\n * - ❤️ Health checks\n * - 🧹 Cleanup on shutdown\n *\n * @example\n * ```typescript\n * class MyAppServer extends AppServer {\n * constructor(config: AppServerConfig) {\n * super(config)\n *\n * // Add custom API routes (Hono syntax)\n * this.get(\"/api/custom\", (c) => c.json({ message: \"Hello!\" }))\n * }\n *\n * protected async onSession(session: AppSession, sessionId: string, userId: string) {\n * // Handle new user sessions here\n * session.events.onTranscription((data) => {\n * session.layouts.showTextWall(data.text);\n * });\n * }\n * }\n *\n * const server = new MyAppServer({\n * packageName: 'org.example.myapp',\n * apiKey: 'your_api_key',\n * });\n *\n * await server.start();\n * ```\n */\nexport class AppServer extends Hono<{ Variables: AuthVariables }> {\n /** Server configuration */\n protected config: AppServerConfig;\n /** Map of active user sessions by sessionId */\n private activeSessions = new Map<string, AppSession>();\n /** Map of active user sessions by userId */\n private activeSessionsByUserId = new Map<string, AppSession>();\n /** Guards against running cleanup twice (double Ctrl-C, SIGINT+SIGTERM, etc.) */\n private _cleanupPromise: Promise<void> | null = null;\n /**\n * Pending photo requests by requestId - owned by AppServer for HTTP endpoint access.\n * This is the single source of truth for pending photo requests.\n * Stored here (not on CameraModule) because:\n * 1. Photo uploads arrive via HTTP to AppServer, not via WebSocket to session\n * 2. Allows O(1) lookup by requestId instead of iterating all sessions\n * 3. Survives session reconnections (session may be removed from activeSessions temporarily)\n * See: cloud/issues/019-sdk-photo-request-architecture\n */\n private pendingPhotoRequests = new Map<string, PendingPhotoRequest>();\n /** Array of cleanup handlers to run on shutdown */\n private cleanupHandlers: Array<() => void> = [];\n /** App instructions string shown to the user */\n private appInstructions: string | null = null;\n\n public readonly logger: Logger;\n\n constructor(config: AppServerConfig) {\n super(); // Initialize Hono\n\n // Set defaults and merge with provided config\n this.config = {\n port: 7010,\n webhookPath: \"/webhook\",\n publicDir: false,\n healthCheck: true,\n ...config,\n };\n\n // Apply config-level log settings to environment before logger creation.\n // Config takes priority over env vars.\n if (config.verbose !== undefined) {\n process.env.MENTRA_VERBOSE = config.verbose ? \"true\" : \"false\";\n }\n if (config.logLevel !== undefined) {\n process.env.MENTRA_LOG_LEVEL = config.logLevel;\n }\n\n this.logger = createLogger({\n verbose: config.verbose,\n logLevel: config.logLevel as any,\n }).child({\n app: this.config.packageName,\n packageName: this.config.packageName,\n service: \"app-server\",\n });\n\n // Apply authentication middleware\n this.use(\n \"*\",\n createAuthMiddleware({\n apiKey: this.config.apiKey,\n packageName: this.config.packageName,\n getAppSessionForUser: (userId: string) => {\n return this.activeSessionsByUserId.get(userId) || null;\n },\n cookieSecret: this.config.cookieSecret || this.config.apiKey, // Default to apiKey for simplicity\n }),\n );\n\n this.appInstructions = (config as any).appInstructions || null;\n\n // Setup server features\n this.setupWebhook();\n this.setupSettingsEndpoint();\n this.setupHealthCheck();\n this.setupToolCallEndpoint();\n this.setupPhotoUploadEndpoint();\n this.setupMentraWebviewAuth();\n this.setupMentraAuthRedirect();\n this.setupPublicDir();\n this.setupShutdown();\n }\n\n /**\n * @deprecated Use `this.get()`, `this.post()`, etc. directly since AppServer now extends Hono\n * This method is kept for backward compatibility during migration.\n */\n public getExpressApp(): AppHono {\n console.warn(\n \"DEPRECATION: getExpressApp() is deprecated. AppServer now extends Hono - use this.get(), this.post(), etc. directly.\",\n );\n return this as AppHono;\n }\n\n /**\n * Get the Hono app instance (returns this since AppServer extends Hono)\n */\n public getHonoApp(): AppHono {\n return this as AppHono;\n }\n\n /**\n * 👥 Session Handler\n * Override this method to handle new App sessions.\n * This is where you implement your app's core functionality.\n *\n * @param session - App session instance for the user\n * @param sessionId - Unique identifier for this session\n * @param userId - User's identifier\n */\n protected async onSession(session: AppSession, sessionId: string, userId: string): Promise<void> {\n this.logger.debug({ sessionId, userId }, \"Session handler started\");\n }\n\n /**\n * 👥 Stop Handler\n * Override this method to handle stop requests.\n * This is where you can clean up resources when a session is stopped.\n *\n * @param sessionId - Unique identifier for this session\n * @param userId - User's identifier\n * @param reason - Reason for stopping\n */\n protected async onStop(sessionId: string, userId: string, reason: string): Promise<void> {\n this.logger.debug(`Session ${sessionId} stopped for user ${userId}. Reason: ${reason}`);\n\n // Default implementation: close the session if it exists\n const session = this.activeSessions.get(sessionId);\n if (session) {\n session.disconnect();\n this.activeSessions.delete(sessionId);\n this.activeSessionsByUserId.delete(userId);\n }\n }\n\n /**\n * 🛠️ Tool Call Handler\n * Override this method to handle tool calls from MentraOS Cloud.\n * This is where you implement your app's tool functionality.\n *\n * @param toolCall - The tool call request containing tool details and parameters\n * @returns Optional string response that will be sent back to MentraOS Cloud\n */\n protected async onToolCall(toolCall: ToolCall): Promise<string | undefined> {\n this.logger.debug(`Tool call received: ${toolCall.toolId}`);\n this.logger.debug(`Parameters: ${JSON.stringify(toolCall.toolParameters)}`);\n return undefined;\n }\n\n protected getActiveSessionById(sessionId: string): AppSession | null {\n return this.activeSessions.get(sessionId) || null;\n }\n\n protected getActiveSessionForUser(userId: string): AppSession | null {\n return this.activeSessionsByUserId.get(userId) || null;\n }\n\n protected setActiveSession(sessionId: string, userId: string, session: AppSession): void {\n this.activeSessions.set(sessionId, session);\n this.activeSessionsByUserId.set(userId, session);\n }\n\n protected removeActiveSession(sessionId: string, userId: string): void {\n this.activeSessions.delete(sessionId);\n this.activeSessionsByUserId.delete(userId);\n }\n\n /**\n * 🚀 Initialize the App\n * Sets up logging and checks SDK version.\n * After calling this, use Bun.serve() with app.fetch to start the server.\n *\n * @example\n * ```typescript\n * const app = new MyAppServer({ ... })\n * await app.start()\n *\n * Bun.serve({\n * port: 3333,\n * routes: { \"/*\": indexHtml },\n * fetch: app.fetch,\n * })\n * ```\n *\n * @returns Promise that resolves when initialization is complete\n */\n public async start(): Promise<void> {\n this.logger.info(`App server running on port ${this.config.port}`);\n\n // Check for SDK updates (non-blocking)\n await this.checkSDKVersion();\n }\n\n /**\n * Check and log SDK version (dist-tag aware).\n * Hits npm registry directly — no dependency on our backend.\n */\n private async checkSDKVersion(): Promise<void> {\n try {\n const sdkPkgPath = path.resolve(process.cwd(), \"node_modules/@mentra/sdk/package.json\");\n\n let currentVersion = \"unknown\";\n\n if (fs.existsSync(sdkPkgPath)) {\n const sdkPkg = JSON.parse(fs.readFileSync(sdkPkgPath, \"utf-8\"));\n currentVersion = sdkPkg.version || \"not-found\";\n } else {\n this.logger.debug({ sdkPkgPath }, \"No @mentra/sdk package.json found at path\");\n }\n\n // Determine which dist-tag (release track) the dev is on\n const distTag = getDistTag(currentVersion);\n\n // Fetch latest version for this track directly from npm registry\n let latest: string | null = null;\n try {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 3000);\n const response = await fetch(`https://registry.npmjs.org/@mentra/sdk/${distTag}`, {\n signal: controller.signal,\n });\n clearTimeout(timeout);\n if (response.ok) {\n const data = (await response.json()) as { version: string };\n latest = data.version;\n }\n } catch {\n this.logger.debug(\"Failed to check npm for SDK updates — skipping (offline or timeout)\");\n }\n\n if (currentVersion === \"not-found\") {\n this.logger.warn(\n \"@mentra/sdk not found in your project dependencies. Install it with: bun install @mentra/sdk\",\n );\n } else if (latest && latest !== currentVersion) {\n this.logger.warn(`SDK update available: ${currentVersion} → ${latest} — bun install @mentra/sdk@${distTag}`);\n }\n } catch (err) {\n this.logger.debug(err, \"Version check failed\");\n }\n }\n\n /**\n * Stop the Server\n * Gracefully shuts down the server and cleans up all sessions.\n */\n public async stop(): Promise<void> {\n // Prevent double-cleanup from concurrent signals or multiple stop() calls\n if (this._cleanupPromise) return this._cleanupPromise;\n this.logger.info(\"Shutting down...\");\n this._cleanupPromise = this.cleanup();\n return this._cleanupPromise;\n }\n\n /**\n * 🔐 Generate a App token for a user\n * This should be called when handling a session webhook request.\n *\n * @param userId - User identifier\n * @param sessionId - Session identifier\n * @param secretKey - Secret key for signing the token\n * @returns JWT token string\n */\n protected generateToken(userId: string, sessionId: string, secretKey: string): string {\n const { createToken } = require(\"../token/utils\");\n return createToken(\n {\n userId,\n packageName: this.config.packageName,\n sessionId,\n },\n { secretKey },\n );\n }\n\n /**\n * Add Cleanup Handler\n * Register a function to be called during server shutdown.\n *\n * @param handler - Function to call during cleanup\n */\n protected addCleanupHandler(handler: () => void): void {\n this.cleanupHandlers.push(handler);\n }\n\n // =====================================\n // 📸 Photo Request Management APIs\n // =====================================\n\n /**\n * Register a pending photo request.\n * Called by CameraModule when a photo is requested.\n * Stores the request at AppServer level for O(1) lookup when HTTP response arrives.\n *\n * @param requestId - Unique identifier for this photo request\n * @param request - Request details including session, resolve/reject callbacks\n */\n registerPhotoRequest(requestId: string, request: Omit<PendingPhotoRequest, \"timeoutId\">): void {\n // Set timeout at AppServer level (single source of truth)\n const timeoutMs = 30000; // 30 seconds\n const timeoutId = setTimeout(() => {\n const pending = this.pendingPhotoRequests.get(requestId);\n if (pending) {\n pending.reject(new Error(\"Photo request timed out\"));\n this.pendingPhotoRequests.delete(requestId);\n this.logger.warn({ requestId }, \"Photo request timed out\");\n }\n }, timeoutMs);\n\n this.pendingPhotoRequests.set(requestId, {\n ...request,\n timeoutId,\n });\n\n this.logger.debug({ requestId, userId: request.userId, sessionId: request.sessionId }, \"Photo request registered\");\n }\n\n /**\n * Get a pending photo request by ID.\n *\n * @param requestId - The request ID to look up\n * @returns The pending request, or undefined if not found\n */\n getPhotoRequest(requestId: string): PendingPhotoRequest | undefined {\n return this.pendingPhotoRequests.get(requestId);\n }\n\n /**\n * Complete a photo request (success or error).\n * Clears the timeout and removes from the pending map.\n *\n * @param requestId - The request ID to complete\n * @returns The pending request that was completed, or undefined if not found\n */\n completePhotoRequest(requestId: string): PendingPhotoRequest | undefined {\n const pending = this.pendingPhotoRequests.get(requestId);\n if (pending) {\n if (pending.timeoutId) {\n clearTimeout(pending.timeoutId);\n }\n this.pendingPhotoRequests.delete(requestId);\n this.logger.debug({ requestId }, \"Photo request completed\");\n }\n return pending;\n }\n\n /**\n * Clean up all pending photo requests for a session.\n * Called when a session permanently disconnects.\n *\n * @param sessionId - The session ID to clean up requests for\n */\n cleanupPhotoRequestsForSession(sessionId: string): void {\n let cleanedCount = 0;\n for (const [requestId, pending] of this.pendingPhotoRequests) {\n if (pending.sessionId === sessionId) {\n if (pending.timeoutId) {\n clearTimeout(pending.timeoutId);\n }\n pending.reject(new Error(\"Session ended\"));\n this.pendingPhotoRequests.delete(requestId);\n cleanedCount++;\n this.logger.debug({ requestId, sessionId }, \"Photo request cleaned up (session ended)\");\n }\n }\n if (cleanedCount > 0) {\n this.logger.debug({ sessionId, cleanedCount }, \"Cleaned up photo requests for ended session\");\n }\n }\n\n /**\n * Setup Webhook Endpoint\n * Creates the webhook endpoint that MentraOS Cloud calls to start new sessions.\n */\n private setupWebhook(): void {\n const webhookPaths = [this.config.webhookPath || \"/webhook\", `${SDK_ROUTE_PREFIX}/webhook`];\n\n const handler = async (c: Context<{ Variables: AuthVariables }>) => {\n try {\n const webhookRequest = (await c.req.json()) as WebhookRequest;\n\n // Handle session request\n if (webhookRequest.type === WebhookRequestType.SESSION_REQUEST) {\n return this.handleSessionWebhookRequest(webhookRequest as SessionWebhookRequest, c);\n }\n // Handle stop request\n else if (webhookRequest.type === WebhookRequestType.STOP_REQUEST) {\n return this.handleStopWebhookRequest(webhookRequest as StopWebhookRequest, c);\n }\n // Unknown webhook type\n else {\n this.logger.error(\"Unknown webhook request type\");\n return c.json(\n {\n status: \"error\",\n message: \"Unknown webhook request type\",\n } as WebhookResponse,\n 400,\n );\n }\n } catch (error) {\n this.logger.error(error, \"Error handling webhook\");\n return c.json(\n {\n status: \"error\",\n message: \"Error handling webhook: \" + (error as Error).message,\n } as WebhookResponse,\n 500,\n );\n }\n };\n\n for (const path of webhookPaths) {\n this.post(path, handler);\n }\n }\n\n /**\n * Setup Tool Call Endpoint\n * Creates a /tool endpoint for handling tool calls from MentraOS Cloud.\n */\n private setupToolCallEndpoint(): void {\n const postHandler = async (c: Context<{ Variables: AuthVariables }>) => {\n try {\n const toolCall = (await c.req.json()) as ToolCall;\n toolCall.activeSession = this.getActiveSessionForUser(toolCall.userId);\n this.logger.debug({ toolId: toolCall.toolId }, \"Tool call received\");\n\n // Call the onToolCall handler and get the response\n const response = await this.onToolCall(toolCall);\n\n // Send back the response if one was provided\n if (response !== undefined) {\n return c.json({ status: \"success\", reply: response });\n } else {\n return c.json({ status: \"success\", reply: null });\n }\n } catch (error) {\n this.logger.error(error, \"Error handling tool call\");\n return c.json(\n {\n status: \"error\",\n message: error instanceof Error ? error.message : \"Unknown error occurred calling tool\",\n },\n 500,\n );\n }\n };\n\n const getHandler = async (c: Context<{ Variables: AuthVariables }>) => {\n return c.json({ status: \"success\", reply: \"Hello, world!\" });\n };\n\n for (const path of [\"/tool\", `${SDK_ROUTE_PREFIX}/tool`]) {\n this.post(path, postHandler);\n this.get(path, getHandler);\n }\n }\n\n /**\n * Handle a session request webhook\n */\n protected async handleSessionWebhookRequest(\n request: SessionWebhookRequest,\n c: Context<{ Variables: AuthVariables }>,\n ): Promise<Response> {\n const { sessionId, userId, websocketUrl, mentraOSWebsocketUrl, augmentOSWebsocketUrl } = request;\n this.logger.debug({ userId, sessionId }, \"Session request received\");\n\n // Check for existing session (user might be switching clouds)\n // If an existing session exists, we need to clean it up properly to avoid:\n // 1. Orphaned sessions with open WebSockets\n // 2. Cleanup handlers that corrupt the new session's map entries\n // See: cloud/issues/018-app-disconnect-resurrection\n const existingSession = this.getActiveSessionById(sessionId);\n if (existingSession) {\n this.logger.debug({ sessionId, userId }, \"Existing session found — releasing ownership before reconnect\");\n\n try {\n // Send OWNERSHIP_RELEASE to tell the old cloud not to resurrect this app\n // The old cloud will mark the app as DORMANT instead of trying to restart it\n await existingSession.releaseOwnership(\"switching_clouds\");\n } catch (error) {\n this.logger.warn({ error, sessionId }, \"Failed to release ownership on old session — continuing\");\n }\n\n try {\n // Disconnect the old session explicitly\n existingSession.disconnect();\n } catch (error) {\n this.logger.warn({ error, sessionId }, \"Failed to disconnect old session — continuing\");\n }\n\n // Remove from maps immediately (don't wait for cleanup handler)\n this.removeActiveSession(sessionId, userId);\n\n this.logger.debug({ sessionId, userId }, \"Old session cleaned up, proceeding with new connection\");\n }\n\n // Create new App session\n const session = new AppSession({\n packageName: this.config.packageName,\n apiKey: this.config.apiKey,\n mentraOSWebsocketUrl: websocketUrl || mentraOSWebsocketUrl || augmentOSWebsocketUrl,\n appServer: this,\n userId,\n });\n\n // Setup session event handlers\n const cleanupDisconnect = session.events.onDisconnected((info) => {\n // Determine if this is a permanent disconnect\n // Permanent disconnects happen when:\n // 1. User session ends (sessionEnded === true)\n // 2. Reconnection attempts exhausted (permanent === true)\n // 3. Clean WebSocket closure (1000/1001) - no reconnection will be attempted\n // Temporary disconnects (abnormal closures like 1006 that trigger reconnection) should NOT remove session from maps\n // See: cloud/issues/019-sdk-photo-request-architecture\n let isPermanent = false;\n let reason = \"unknown\";\n\n // Handle different disconnect info formats (string or object)\n if (typeof info === \"string\") {\n this.logger.debug({ sessionId }, `Session disconnected: ${info}`);\n reason = info;\n // String-only disconnects are typically temporary (e.g., \"WebSocket closed\")\n isPermanent = false;\n } else {\n this.logger.debug({ sessionId, code: info.code }, `Session disconnected: ${info.message}`);\n reason = info.reason || info.message;\n\n if (info.sessionEnded === true) {\n this.logger.debug({ sessionId }, \"User session ended, calling onStop\");\n isPermanent = true;\n\n this.onStop(sessionId, userId, \"User session ended\").catch((error) => {\n this.logger.error(error, \"Error in onStop handler for session end\");\n });\n }\n // Check if this is a permanent disconnection after exhausted reconnection attempts\n else if (info.permanent === true) {\n this.logger.debug({ sessionId }, \"Permanent disconnection, calling onStop\");\n isPermanent = true;\n\n this.onStop(sessionId, userId, `Connection permanently lost: ${info.reason}`).catch((error) => {\n this.logger.error(error, \"Error in onStop handler for permanent disconnection\");\n });\n }\n // Check if this is a clean WebSocket closure (1000/1001) that won't trigger reconnection\n // These are intentional disconnects (app shutdown, manual stop, etc.)\n // AppSession skips reconnection for these codes, so we must treat them as permanent\n // to avoid zombie sessions in activeSessions map\n else if (info.wasClean === true || info.code === 1000 || info.code === 1001) {\n this.logger.debug({ sessionId, code: info.code }, \"Clean WebSocket closure, treating as permanent\");\n isPermanent = true;\n\n // Call onStop for clean disconnects too\n this.onStop(sessionId, userId, `Clean disconnect: ${reason}`).catch((error) => {\n this.logger.error(error, \"Error in onStop handler for clean disconnect\");\n });\n }\n }\n\n // Only remove session and clean up photo requests on PERMANENT disconnects\n // Temporary disconnects should leave the session in place so:\n // 1. Photo uploads can still find the pending request\n // 2. The session can be reused after reconnection\n // See: cloud/issues/019-sdk-photo-request-architecture\n if (isPermanent) {\n // Remove the session from active sessions ONLY if this session is still the active one.\n // This prevents a bug where an old session's cleanup handler deletes a newer session:\n // 1. User switches from Cloud A to Cloud B\n // 2. SDK creates sessionB, overwrites activeSessions[sessionId]\n // 3. sessionA is orphaned but its cleanup handler still references sessionId\n // 4. When Cloud A disposes, sessionA's cleanup fires and would delete sessionB\n // By checking identity (===), we only delete if we're still the current session.\n // See: cloud/issues/018-app-disconnect-resurrection\n if (this.getActiveSessionById(sessionId) === session) {\n this.removeActiveSession(sessionId, userId);\n } else {\n this.logger.debug({ sessionId }, \"Session cleanup skipped — a newer session has taken over\");\n }\n\n // Clean up any pending photo requests for this session\n this.cleanupPhotoRequestsForSession(sessionId);\n } else {\n // Temporary disconnect - session stays in maps for reconnection\n // Photo requests remain pending and can still be fulfilled\n this.logger.debug({ sessionId, reason }, \"Temporary disconnect, keeping session for reconnection\");\n }\n });\n\n const cleanupError = session.events.onError((error) => {\n this.logger.error(error, \"Session error\");\n });\n\n // Start the session\n try {\n await session.connect(sessionId);\n this.setActiveSession(sessionId, userId, session);\n await this.onSession(session, sessionId, userId);\n return c.json({ status: \"success\" } as WebhookResponse);\n } catch (error) {\n this.logger.error(error, \"Failed to connect session\");\n cleanupDisconnect();\n cleanupError();\n return c.json(\n {\n status: \"error\",\n message: \"Failed to connect\",\n } as WebhookResponse,\n 500,\n );\n }\n }\n\n /**\n * Handle a stop request webhook\n */\n protected async handleStopWebhookRequest(\n request: StopWebhookRequest,\n c: Context<{ Variables: AuthVariables }>,\n ): Promise<Response> {\n const { sessionId, userId, reason } = request;\n this.logger.debug({ sessionId, userId, reason }, \"Stop request received\");\n\n try {\n await this.onStop(sessionId, userId, reason);\n return c.json({ status: \"success\" } as WebhookResponse);\n } catch (error) {\n this.logger.error(error, \"Error handling stop request\");\n return c.json(\n {\n status: \"error\",\n message: \"Failed to process stop request\",\n } as WebhookResponse,\n 500,\n );\n }\n }\n\n /**\n * Setup Health Check Endpoint\n * Creates a /health endpoint for monitoring server status.\n */\n private setupHealthCheck(): void {\n if (this.config.healthCheck) {\n const handler = (c: Context<{ Variables: AuthVariables }>) => {\n return c.json({\n status: \"healthy\",\n app: this.config.packageName,\n activeSessions: this.activeSessions.size,\n });\n };\n\n for (const path of [\"/health\", `${SDK_ROUTE_PREFIX}/health`]) {\n this.get(path, handler);\n }\n }\n }\n\n /**\n * Setup Settings Endpoint\n * Creates a /settings endpoint that the MentraOS Cloud can use to update settings.\n */\n private setupSettingsEndpoint(): void {\n const handler = async (c: Context<{ Variables: AuthVariables }>) => {\n try {\n const { userIdForSettings, settings } = await c.req.json();\n\n if (!userIdForSettings || !Array.isArray(settings)) {\n return c.json(\n {\n status: \"error\",\n message: \"Missing userId or settings array in request body\",\n },\n 400,\n );\n }\n\n this.logger.debug({ userId: userIdForSettings }, \"Settings update received\");\n\n // Find all active sessions for this user\n const userSessions: AppSession[] = [];\n\n this.activeSessions.forEach((session, _sessionId) => {\n if (session.userId === userIdForSettings) {\n userSessions.push(session);\n }\n });\n\n if (userSessions.length === 0) {\n this.logger.debug({ userId: userIdForSettings }, \"No active sessions for settings update\");\n }\n\n // Update settings for all of the user's sessions\n for (const session of userSessions) {\n session.updateSettingsForTesting(settings);\n }\n\n // Allow subclasses to handle settings updates if they implement the method\n if (typeof (this as any).onSettingsUpdate === \"function\") {\n await (this as any).onSettingsUpdate(userIdForSettings, settings);\n }\n\n return c.json({\n status: \"success\",\n message: \"Settings updated successfully\",\n sessionsUpdated: userSessions.length,\n });\n } catch (error) {\n this.logger.error(error, \"Error handling settings update\");\n return c.json(\n {\n status: \"error\",\n message: \"Internal server error processing settings update\",\n },\n 500,\n );\n }\n };\n\n for (const path of [\"/settings\", `${SDK_ROUTE_PREFIX}/settings`]) {\n this.post(path, handler);\n }\n }\n\n /**\n * Setup Static File Serving\n * Configures Hono to serve static files from the specified directory.\n */\n private setupPublicDir(): void {\n if (this.config.publicDir) {\n const publicPath = path.resolve(this.config.publicDir);\n this.use(\"/*\", serveStatic({ root: publicPath }));\n this.logger.debug({ publicPath }, \"Serving static files\");\n }\n }\n\n /**\n * Setup Shutdown Handlers\n *\n * Both SIGINT (Ctrl+C) and SIGTERM (Kubernetes) → fast exit.\n *\n * We never release ownership on shutdown (cloud will resurrect), so\n * there is nothing useful to clean up. The only thing worth doing is\n * a best-effort SimpleStorage flush, which we fire-and-forget.\n *\n * process.exit() does NOT work reliably under `bun --watch` — it\n * intercepts the call. Instead we remove all JS signal handlers and\n * re-raise the signal so the OS default handler terminates us.\n *\n * See: cloud/issues/086-sdk-fast-shutdown\n */\n private setupShutdown(): void {\n const die = (signal: \"SIGINT\" | \"SIGTERM\") => {\n this.logger.info(\"Shutting down...\");\n\n // Best-effort flush of SimpleStorage (fire-and-forget, don't await).\n for (const [, session] of this.activeSessions) {\n try {\n (session as any).simpleStorage?.flush?.()?.catch?.(() => {});\n } catch {\n // ignore\n }\n }\n\n // Remove ALL handlers for BOTH signals so the re-raised signal\n // hits the OS default handler and actually kills the process.\n process.removeAllListeners(\"SIGINT\");\n process.removeAllListeners(\"SIGTERM\");\n\n // Re-raise the original signal. OS kills us immediately.\n process.kill(process.pid, signal);\n };\n\n process.on(\"SIGINT\", () => die(\"SIGINT\"));\n process.on(\"SIGTERM\", () => die(\"SIGTERM\"));\n }\n\n /**\n * 🧹 Cleanup\n * Closes all active sessions and runs cleanup handlers.\n * Does NOT release ownership - we want the cloud to resurrect when we come back up.\n *\n * OWNERSHIP_RELEASE should only be sent for:\n * - switching_clouds: User moved to another cloud, don't compete\n * - user_logout: User explicitly logged out\n *\n * NOT for clean_shutdown, because:\n * - Server is restarting/redeploying\n * - Cloud should resurrect the app (trigger webhook)\n * - User expects their app to keep running\n *\n * See: cloud/issues/023-disposed-appsession-resurrection-bug\n */\n private async cleanup(): Promise<void> {\n this.logger.debug(\"Cleanup started — not releasing ownership (cloud will resurrect)\");\n // Close all active sessions WITHOUT releasing ownership\n // This allows the cloud to resurrect apps when we come back up\n for (const [sessionId, session] of this.activeSessions) {\n this.logger.debug({ sessionId }, \"Closing session\");\n try {\n // Just disconnect, don't release ownership\n // The cloud will enter grace period and then resurrect via webhook\n await session.disconnect({\n releaseOwnership: false,\n });\n } catch (error) {\n this.logger.error(error, `Error during cleanup of session ${sessionId}`);\n // Still try to disconnect even if release fails\n try {\n await session.disconnect();\n } catch {\n // Ignore secondary errors\n }\n }\n }\n this.activeSessions.clear();\n this.activeSessionsByUserId.clear();\n\n // Run cleanup handlers\n this.cleanupHandlers.forEach((handler) => handler());\n }\n\n /**\n * 🎯 Setup Photo Upload Endpoint\n * Creates a /photo-upload endpoint for receiving photos directly from ASG glasses\n */\n private setupPhotoUploadEndpoint(): void {\n const handler = async (c: Context<{ Variables: AuthVariables }>) => {\n try {\n // Parse multipart form data\n const body = await c.req.parseBody();\n const requestId = body.requestId as string;\n const type = body.type as string;\n const errorCode = body.errorCode as string;\n const errorMessage = body.errorMessage as string;\n const photoFile = body.photo as File | undefined;\n\n // Defensive parsing: photo file presence is the primary success indicator\n // The success field may be undefined/missing from some clients\n const hasPhotoFile = !!photoFile;\n const successValue = typeof body.success === \"string\" ? body.success : undefined;\n const isExplicitError = type === \"photo_error\" || successValue === \"false\";\n\n this.logger.debug({ requestId, type, hasPhotoFile, isExplicitError }, \"Photo response received\");\n\n if (!requestId) {\n this.logger.error(\"No requestId in photo response\");\n return c.json({ success: false, error: \"No requestId provided\" }, 400);\n }\n\n // Complete the request (O(1) lookup and cleanup)\n const pending = this.completePhotoRequest(requestId);\n if (!pending) {\n this.logger.debug(\n { requestId, pendingCount: this.pendingPhotoRequests.size },\n \"No pending request found for photo (may have timed out or session ended)\",\n );\n return c.json(\n { success: false, error: \"No pending request found for this photo (may have timed out or session ended)\" },\n 404,\n );\n }\n\n // Handle error response: only if explicitly marked as error AND no photo file\n if (isExplicitError && !hasPhotoFile) {\n this.logger.warn(\n { requestId, errorCode, errorMessage },\n `Photo capture failed: ${errorCode} - ${errorMessage}`,\n );\n pending.reject(new Error(`${errorCode || \"UNKNOWN_ERROR\"}: ${errorMessage || \"Unknown error\"}`));\n\n return c.json({\n success: true,\n requestId,\n message: \"Photo error received successfully\",\n });\n }\n\n // Handle successful photo upload\n if (!photoFile) {\n const errorMsg = \"No photo file in upload (and no explicit error reported)\";\n this.logger.error({ requestId, bodyKeys: Object.keys(body) }, errorMsg);\n pending.reject(new Error(errorMsg));\n return c.json({ success: false, error: errorMsg }, 400);\n }\n\n // Read file buffer\n const buffer = Buffer.from(await photoFile.arrayBuffer());\n\n this.logger.debug({ requestId, size: photoFile.size }, \"Photo received\");\n\n // Deliver photo data to the original requester\n pending.resolve({\n buffer,\n mimeType: photoFile.type,\n filename: photoFile.name || \"photo.jpg\",\n requestId,\n size: photoFile.size,\n timestamp: new Date(),\n });\n\n return c.json({\n success: true,\n requestId,\n message: \"Photo received successfully\",\n });\n } catch (error) {\n this.logger.error(error, \"Error handling photo response\");\n return c.json({ success: false, error: \"Internal server error processing photo response\" }, 500);\n }\n };\n\n for (const path of [\"/photo-upload\", `${SDK_ROUTE_PREFIX}/photo-upload`]) {\n this.post(path, handler);\n }\n }\n\n /**\n * Setup Mentra Auth Redirect Endpoint\n * Creates a /mentra-auth endpoint that redirects to the MentraOS OAuth flow.\n */\n private setupMentraAuthRedirect(): void {\n const handler = (c: Context<{ Variables: AuthVariables }>) => {\n const authUrl = `https://account.mentra.glass/auth?packagename=${encodeURIComponent(this.config.packageName)}`;\n return c.redirect(authUrl, 302);\n };\n\n for (const path of [\"/mentra-auth\", `${SDK_ROUTE_PREFIX}/auth`]) {\n this.get(path, handler);\n }\n }\n\n /**\n * Setup SDK-owned webview auth routes.\n *\n * Canonical v3 path is under the internal SDK namespace. The legacy\n * `/api/mentra/auth` path remains as a compatibility alias during the\n * transition so older Hono examples and early adopters keep working.\n */\n private setupMentraWebviewAuth(): void {\n const authApp = createMentraAuthRoutes({\n apiKey: this.config.apiKey,\n packageName: this.config.packageName,\n cookieSecret: this.config.cookieSecret || this.config.apiKey,\n });\n\n for (const path of [`${SDK_ROUTE_PREFIX}/auth`, LEGACY_MENTRA_AUTH_ROUTE_PREFIX]) {\n this.route(path, authApp);\n }\n }\n}\n\n/**\n * @deprecated Use `AppServerConfig` instead. `TpaServerConfig` is deprecated and will be removed in a future version.\n * This is an alias for backward compatibility only.\n */\nexport type TpaServerConfig = AppServerConfig;\n\n/**\n * @deprecated Use `AppServer` instead. `TpaServer` is deprecated and will be removed in a future version.\n * This is an alias for backward compatibility only.\n */\nexport class TpaServer extends AppServer {\n constructor(config: TpaServerConfig) {\n super(config);\n console.warn(\n \"⚠️ DEPRECATION WARNING: TpaServer is deprecated and will be removed in a future version. \" +\n \"Please use AppServer instead. \" +\n 'Simply replace \"TpaServer\" with \"AppServer\" in your code.',\n );\n }\n}\n",
49
+ "/**\n * updates.ts\n *\n * SDK update notification messages and dist-tag utilities.\n *\n * Previously used boxen-bordered ASCII art banners. Now returns plain\n * single-line strings that the clean transport formats with color/prefix.\n *\n * The clean logger renders this as:\n * MentraOS ⚠ SDK update available: 2.1.29 2.1.30 bun install @mentra/sdk@latest\n * MentraOS ⚠ SDK update available: 3.0.0-hono.4 3.0.0-hono.5 bun install @mentra/sdk@hono\n */\n\n/**\n * Known npm dist-tags for @mentra/sdk.\n * Used to parse the current version's prerelease identifier into a dist-tag,\n * and to validate the tag before sending it to the cloud API.\n */\nconst KNOWN_DIST_TAGS = [\"alpha\", \"beta\", \"hono\", \"rc\", \"canary\", \"next\"] as const;\n\n/**\n * Determine the npm dist-tag (release track) from a semver version string.\n *\n * Parses the prerelease identifier:\n * \"2.1.29\" → \"latest\" (no prerelease)\n * \"2.1.31-beta.5\" \"beta\"\n * \"3.0.0-hono.4\" → \"hono\"\n * \"2.1.2-alpha.0\" \"alpha\"\n * \"4.0.0-rc.1\" \"rc\"\n * \"1.0.0-unknown.1\" \"latest\" (unrecognized prerelease falls back to latest)\n *\n * @param version - The installed SDK version string\n * @returns The dist-tag name (e.g., \"latest\", \"beta\", \"hono\")\n */\nexport function getDistTag(version: string): string {\n const pattern = new RegExp(`-(${KNOWN_DIST_TAGS.join(\"|\")})`);\n const match = version.match(pattern);\n return match ? match[1] : \"latest\";\n}\n\n/**\n * Generate a single-line SDK update notification message.\n *\n * The install command uses the correct dist-tag so developers on non-latest\n * tracks (beta, hono, etc.) aren't told to install @latest which would\n * downgrade/brick their app.\n *\n * @param currentVersion - The currently installed SDK version\n * @param latestVersion - The latest available SDK version for this track\n * @param tag - The npm dist-tag for the install command (default: \"latest\")\n * @returns A plain string suitable for `logger.warn()`\n */\nexport const newSDKUpdate = (currentVersion: string, latestVersion: string, tag: string = \"latest\"): string => {\n return `SDK update available: ${currentVersion} ${latestVersion} bun install @mentra/sdk@${tag}`;\n};\n",
50
+ "/**\n * 🎯 App Session Module\n *\n * Manages an active Third Party App session with MentraOS Cloud.\n * Handles real-time communication, event subscriptions, and display management.\n */\n\n// Patch version for tracking Bug 007 fix (subscriptions derived from handlers)\n// v1: Derive subscriptions from handlers (single source of truth)\n// v2: Add 'terminated' flag to prevent reconnection after \"User session ended\"\n// This helps verify the correct SDK version is running in production\nconst SDK_SUBSCRIPTION_PATCH = \"bug007-fix-v2\";\nimport pino from \"pino\";\nimport { WebSocket } from \"ws\";\nimport { EventManager, EventData } from \"./events\";\nimport { LayoutManager } from \"./layouts\";\nimport { SettingsManager } from \"./settings\";\nimport { LocationManager } from \"./modules/location\";\nimport { CameraModule } from \"./modules/camera\";\nimport { LedModule } from \"./modules/led\";\nimport { AudioManager } from \"./modules/audio\";\nimport { ResourceTracker } from \"../../utils/resource-tracker\";\nimport { MentraAuthError, MentraConnectionError, MentraTimeoutError, MentraError } from \"../../logging/errors\";\nimport { createTelemetryStream } from \"../../logging/telemetry-transport\";\nimport type { TelemetryLogEntry } from \"../../types/messages/app-to-cloud\";\nimport type { RequestWifiSetup, OwnershipReleaseMessage } from \"../../types/messages/app-to-cloud\";\nimport {\n // Message types\n AppToCloudMessage,\n CloudToAppMessage,\n AppConnectionInit,\n AppSubscriptionUpdate,\n AudioPlayResponse,\n AppToCloudMessageType,\n CloudToAppMessageType,\n TelemetryResponse,\n\n // Event data types\n StreamType,\n ExtendedStreamType,\n ButtonPress,\n HeadPosition,\n TouchEvent,\n PhoneNotification,\n PhoneNotificationDismissed,\n TranscriptionData,\n TranslationData,\n createTouchEventStream,\n\n // Type guards\n isAppConnectionAck,\n isAppConnectionError,\n isDataStream,\n isAppStopped,\n isSettingsUpdate,\n isDashboardModeChanged,\n isDashboardAlwaysOnChanged,\n isAudioPlayResponse,\n isCapabilitiesUpdate,\n\n // Other types\n AppSettings,\n AppSetting,\n AppConfig,\n validateAppConfig,\n AudioChunk,\n VpsCoordinates,\n PhotoTaken,\n SubscriptionRequest,\n Capabilities,\n CapabilitiesUpdate,\n} from \"../../types\";\nimport { DashboardAPI } from \"../../types/dashboard\";\nimport { MentraosSettingsUpdate } from \"../../types/messages/cloud-to-app\";\nimport { Logger } from \"pino\";\nimport { AppServer } from \"../server\";\nimport axios from \"axios\";\nimport EventEmitter from \"events\";\n\n// Import the cloud-to-app specific type guards\nimport {\n isPhotoResponse,\n isRgbLedControlResponse,\n isStreamStatus,\n isManagedStreamStatus,\n isStreamStatusCheckResponse,\n isDeviceStateUpdate,\n isRequestTelemetry,\n} from \"../../types/messages/cloud-to-app\";\nimport type { RequestTelemetry } from \"../../types/messages/cloud-to-app\";\nimport type { PhotoResponse } from \"../../types/messages/glasses-to-cloud\";\nimport { SimpleStorage } from \"./modules/simple-storage\";\nimport { DeviceState } from \"./device-state\";\nimport { readNotificationWarnLog } from \"../../utils/permissions-utils\";\n\n/**\n * ⚙️ Configuration options for App Session\n *\n * @example\n * ```typescript\n * const config: AppSessionConfig = {\n * packageName: 'org.example.myapp',\n * apiKey: 'your_api_key',\n * // Auto-reconnection is enabled by default\n * // autoReconnect: true\n * };\n * ```\n */\nexport interface AppSessionConfig {\n /** 📦 Unique identifier for your App (e.g., 'org.company.appname') */\n packageName: string;\n /** 🔑 API key for authentication with MentraOS Cloud */\n apiKey: string;\n /** 🔌 WebSocket server URL (default: 'ws://localhost:7002/app-ws') */\n mentraOSWebsocketUrl?: string;\n /** 🔄 Automatically attempt to reconnect on disconnect (default: true) */\n autoReconnect?: boolean;\n /** 🔁 Maximum number of reconnection attempts (default: 3) */\n maxReconnectAttempts?: number;\n /** ⏱️ Base delay between reconnection attempts in ms (default: 1000) */\n reconnectDelay?: number;\n\n userId: string; // user ID for tracking sessions (email of the user).\n appServer: AppServer; // Optional App server instance for advanced features\n}\n\n// List of event types that should never be subscribed to as streams\nconst APP_TO_APP_EVENT_TYPES = [\n \"app_message_received\",\n \"app_user_joined\",\n \"app_user_left\",\n \"app_room_updated\",\n \"app_direct_message_response\",\n];\n\n/**\n * 🚀 App Session Implementation\n *\n * Manages a live connection between your App and MentraOS Cloud.\n * Provides interfaces for:\n * - 🎮 Event handling (transcription, head position, etc.)\n * - 📱 Display management in AR view\n * - 🔌 Connection lifecycle\n * - 🔄 Automatic reconnection\n *\n * @example\n * ```typescript\n * const session = new AppSession({\n * packageName: 'org.example.myapp',\n * apiKey: 'your_api_key'\n * });\n *\n * // Handle events\n * session.onTranscription((data) => {\n * session.layouts.showTextWall(data.text);\n * });\n *\n * // Connect to cloud\n * await session.connect('session_123');\n * ```\n */\nexport class AppSession {\n /** WebSocket connection to MentraOS Cloud */\n private ws: WebSocket | null = null;\n /** Current session identifier */\n private sessionId: string | null = null;\n /** Number of reconnection attempts made */\n private reconnectAttempts = 0;\n /** Flag to prevent reconnection after session termination (e.g., \"User session ended\") */\n private terminated = false;\n // REMOVED: private subscriptions = new Set<ExtendedStreamType>()\n // Subscriptions are now derived from EventManager.handlers (single source of truth)\n // This prevents drift between handlers and subscriptions that caused Bug 007\n // See: cloud/issues/006-captions-and-apps-stopping/011-sdk-subscription-architecture-mismatch.md\n /** Map to store rate options for streams */\n private streamRates = new Map<ExtendedStreamType, string>();\n /** Resource tracker for automatic cleanup */\n private resources = new ResourceTracker();\n /** Internal settings storage - use public settings API instead */\n private settingsData: AppSettings = [];\n /** App configuration loaded from app_config.json */\n private appConfig: AppConfig | null = null;\n /** Whether to update subscriptions when settings change */\n private shouldUpdateSubscriptionsOnSettingsChange = false;\n /** Custom subscription handler for settings-based subscriptions */\n private subscriptionSettingsHandler?: (settings: AppSettings) => ExtendedStreamType[];\n /** Settings that should trigger subscription updates when changed */\n private subscriptionUpdateTriggers: string[] = [];\n /** Pending user discovery requests waiting for responses */\n private pendingUserDiscoveryRequests = new Map<\n string,\n {\n resolve: (userList: any) => void;\n reject: (reason: any) => void;\n }\n >();\n /** Pending direct message requests waiting for responses */\n private pendingDirectMessages = new Map<\n string,\n {\n resolve: (success: boolean) => void;\n reject: (reason: any) => void;\n }\n >();\n\n /** 🎮 Event management interface */\n public readonly events: EventManager;\n /** 📱 Layout management interface */\n public readonly layouts: LayoutManager;\n /** ⚙️ Settings management interface */\n public readonly settings: SettingsManager;\n /** 📊 Dashboard management interface */\n public readonly dashboard: DashboardAPI;\n /** 📍 Location management interface */\n public readonly location: LocationManager;\n /** 📷 Camera interface for photos and streaming */\n public readonly camera: CameraModule;\n /** 💡 LED interface for RGB LED control */\n public readonly led: LedModule;\n /** 🔊 Audio interface for audio playback */\n public readonly audio: AudioManager;\n /** 🔐 Simple key-value storage interface */\n public readonly simpleStorage: SimpleStorage;\n /** 📱 Reactive device state (WebSocket-based observables) */\n public readonly device: { state: DeviceState };\n\n public readonly appServer: AppServer;\n public readonly logger: Logger;\n public readonly userId: string;\n\n /** 🔧 Device capabilities available for this session */\n public capabilities: Capabilities | null = null;\n\n /** 📡 Latest glasses connection state (includes WiFi status) */\n private glassesConnectionState: any = null; // Using any for now since GlassesConnectionState is in glasses-to-cloud types\n\n /** Dedicated emitter for App-to-App events */\n private appEvents = new EventEmitter();\n\n /**\n * Pending handlers waiting for AUDIO_STREAM_READY responses from the cloud.\n * Key: streamId, Value: handler function that receives the raw message.\n * Used by AudioOutputStream.waitForReady() to resolve the relay URL.\n * @internal\n */\n public _audioStreamReadyHandlers = new Map<string, (msg: any) => void>();\n\n /** Per-session ring buffer of recent log entries for incident telemetry uploads */\n private telemetryBuffer: TelemetryLogEntry[] = [];\n /** Interval that sends app-level pings to keep the app-ws connection alive.\n * Bidirectional traffic (ping → cloud, pong ← cloud) prevents load balancer\n * idle timeouts from killing the WebSocket. See: cloud/issues/046-sdk-app-ws-liveness */\n private pingInterval: ReturnType<typeof setInterval> | null = null;\n private static readonly PING_INTERVAL_MS = 15_000;\n\n constructor(private config: AppSessionConfig) {\n // Set defaults and merge with provided config\n this.config = {\n mentraOSWebsocketUrl: `ws://localhost:8002/app-ws`, // Use localhost as default\n autoReconnect: true, // Enable auto-reconnection by default for better reliability\n maxReconnectAttempts: 3, // Default to 3 reconnection attempts for better resilience\n reconnectDelay: 1000, // Start with 1 second delay (uses exponential backoff)\n ...config,\n };\n\n this.appServer = this.config.appServer;\n this.userId = this.config.userId;\n\n // Build session logger: child of appServer.logger (for BetterStack + console)\n // PLUS a telemetry stream that captures info+ entries into this session's\n // ring buffer. Because pino child loggers share the parent transport, we\n // instead create a fresh pino instance that:\n // 1. Forwards all writes to the parent (preserving BetterStack + console)\n // 2. Also tees into the per-session telemetry buffer\n // This means every session.logger.info/warn/error call — including all\n // module child loggers — is automatically captured for incident debugging.\n const parentLogger = this.appServer.logger.child({\n userId: this.config.userId,\n service: \"app-session\",\n });\n const telemetryStream = createTelemetryStream(this.telemetryBuffer, 500);\n // pino multistream: parent at debug (so BetterStack/console still control their own levels)\n // + telemetry stream at info (debug is too noisy for incident bundles)\n this.logger = pino(\n { level: \"debug\", base: null },\n pino.multistream([\n { stream: (parentLogger as any)[pino.symbols.streamSym] ?? process.stderr, level: \"debug\" },\n { stream: telemetryStream, level: \"info\" },\n ]),\n );\n\n // Make sure the URL is correctly formatted to prevent double protocol issues\n if (this.config.mentraOSWebsocketUrl) {\n try {\n const url = new URL(this.config.mentraOSWebsocketUrl);\n if (![\"ws:\", \"wss:\"].includes(url.protocol)) {\n // Fix URLs with incorrect protocol (e.g., 'ws://http://host')\n const fixedUrl = this.config.mentraOSWebsocketUrl.replace(/^ws:\\/\\/http:\\/\\//, \"ws://\");\n this.config.mentraOSWebsocketUrl = fixedUrl;\n this.logger.warn(`Fixed malformed WebSocket URL: ${fixedUrl}`);\n }\n } catch (error) {\n this.logger.error(error, `Invalid WebSocket URL format: ${this.config.mentraOSWebsocketUrl}`);\n }\n }\n\n this.logger.debug(\"App session initialized\");\n\n // Validate URL format - give early warning for obvious issues\n // Check URL format but handle undefined case\n if (this.config.mentraOSWebsocketUrl) {\n try {\n const url = new URL(this.config.mentraOSWebsocketUrl);\n if (![\"ws:\", \"wss:\"].includes(url.protocol)) {\n this.logger.error(\n { config: this.config },\n `Invalid WebSocket URL protocol: ${url.protocol}. Should be ws: or wss:`,\n );\n }\n } catch (error) {\n this.logger.error(\n error,\n `⚠️ [${this.config.packageName}] Invalid WebSocket URL format: ${this.config.mentraOSWebsocketUrl}`,\n );\n }\n }\n\n this.events = new EventManager(\n this.subscribe.bind(this),\n this.unsubscribe.bind(this),\n this.config.packageName,\n this.getHttpsServerUrl() || \"\",\n this.logger,\n );\n this.layouts = new LayoutManager(config.packageName, this.send.bind(this));\n\n // Initialize settings manager with all necessary parameters, including subscribeFn for MentraOS settings\n this.settings = new SettingsManager(\n this.settingsData,\n this.config.packageName,\n this.config.mentraOSWebsocketUrl,\n this.sessionId ?? undefined,\n async (streams: string[]) => {\n // NOTE: With Bug 007 fix, subscriptions are derived from EventManager.handlers\n // This subscribeFn is called by SettingsManager to auto-subscribe to streams for MentraOS settings\n // The actual subscription intent should be tracked via handlers, not a separate Set\n this.logger.debug({ streams: JSON.stringify(streams) }, `[AppSession] subscribeFn called for streams`);\n\n // Log current handler-based subscriptions for debugging\n const currentHandlerStreams = this.events.getRegisteredStreams();\n this.logger.debug(\n {\n requestedStreams: JSON.stringify(streams),\n currentHandlerStreams: JSON.stringify(currentHandlerStreams),\n },\n `[AppSession] subscribeFn: requested streams vs current handler streams`,\n );\n\n // Send subscription update if connected\n // Note: The actual subscriptions sent are derived from handlers\n if (this.ws?.readyState === 1) {\n this.updateSubscriptions();\n this.logger.debug(`[AppSession] Sent updated subscriptions to cloud (derived from handlers).`);\n } else {\n this.logger.debug(`[AppSession] WebSocket not open, will send subscriptions when connected.`);\n }\n },\n );\n\n // Initialize dashboard API with this session instance\n // Import DashboardManager dynamically to avoid circular dependency\n const { DashboardManager } = require(\"./dashboard\");\n this.dashboard = new DashboardManager(this);\n\n // Initialize camera module with session reference\n this.camera = new CameraModule(\n this,\n this.config.packageName,\n this.sessionId || \"unknown-session-id\",\n this.logger.child({ module: \"camera\" }),\n );\n\n // Initialize LED control module\n this.led = new LedModule(\n this,\n this.config.packageName,\n this.sessionId || \"unknown-session-id\",\n this.logger.child({ module: \"led\" }),\n );\n\n // Initialize audio module with session reference\n this.audio = new AudioManager(\n this,\n this.config.packageName,\n this.sessionId || \"unknown-session-id\",\n this.logger.child({ module: \"audio\" }),\n );\n\n this.simpleStorage = new SimpleStorage(this);\n this.device = { state: new DeviceState(this) };\n\n this.location = new LocationManager(this);\n }\n\n /**\n * Get the current session ID\n * @returns The current session ID or 'unknown-session-id' if not connected\n */\n getSessionId(): string {\n return this.sessionId || \"unknown-session-id\";\n }\n\n /**\n * Get the package name for this App\n * @returns The package name\n */\n getPackageName(): string {\n return this.config.packageName;\n }\n\n // =====================================\n // 🎮 Direct Event Handling Interface\n // =====================================\n\n /**\n * @deprecated Use session.events.onTranscription() instead\n */\n onTranscription(handler: (data: TranscriptionData) => void): () => void {\n return this.events.onTranscription(handler);\n }\n\n /**\n * 🌐 Listen for speech transcription events in a specific language\n * @param language - Language code (e.g., \"en-US\")\n * @param handler - Function to handle transcription data\n * @returns Cleanup function to remove the handler\n * @throws Error if language code is invalid\n * @deprecated Use session.events.onTranscriptionForLanguage() instead\n */\n onTranscriptionForLanguage(\n language: string,\n handler: (data: TranscriptionData) => void,\n disableLanguageIdentification = false,\n ): () => void {\n return this.events.onTranscriptionForLanguage(language, handler, disableLanguageIdentification);\n }\n\n /**\n * 🌐 Listen for speech translation events for a specific language pair\n * @param sourceLanguage - Source language code (e.g., \"es-ES\")\n * @param targetLanguage - Target language code (e.g., \"en-US\")\n * @param handler - Function to handle translation data\n * @returns Cleanup function to remove the handler\n * @throws Error if language codes are invalid\n * @deprecated Use session.events.onTranslationForLanguage() instead\n */\n onTranslationForLanguage(\n sourceLanguage: string,\n targetLanguage: string,\n handler: (data: TranslationData) => void,\n ): () => void {\n return this.events.ontranslationForLanguage(sourceLanguage, targetLanguage, handler);\n }\n\n /**\n * 👤 Listen for head position changes\n * @param handler - Function to handle head position updates\n * @returns Cleanup function to remove the handler\n * @deprecated Use session.events.onHeadPosition() instead\n */\n onHeadPosition(handler: (data: HeadPosition) => void): () => void {\n return this.events.onHeadPosition(handler);\n }\n\n /**\n * 🔘 Listen for hardware button press events\n * @param handler - Function to handle button events\n * @returns Cleanup function to remove the handler\n * @deprecated Use session.events.onButtonPress() instead\n */\n onButtonPress(handler: (data: ButtonPress) => void): () => void {\n return this.events.onButtonPress(handler);\n }\n\n /**\n * 👆 Listen for touch gesture events\n * @param gestureOrHandler - Gesture name or handler function\n * @param handler - Handler function (if first param is gesture name)\n * @returns Cleanup function\n *\n * @example\n * // Subscribe to all touch events\n * session.onTouchEvent((event) => console.log(event.gesture_name));\n *\n * // Subscribe to specific gesture\n * session.onTouchEvent(\"forward_swipe\", (event) => console.log(\"Forward swipe!\"));\n */\n onTouchEvent(\n gestureOrHandler: string | ((data: TouchEvent) => void),\n handler?: (data: TouchEvent) => void,\n ): () => void {\n return this.events.onTouchEvent(gestureOrHandler as any, handler as any);\n }\n\n /**\n * 👆 Subscribe to multiple touch gestures\n * @param gestures - Array of gesture names\n * @returns Cleanup function that unsubscribes from all\n *\n * @example\n * session.subscribeToGestures([\"forward_swipe\", \"backward_swipe\"]);\n */\n subscribeToGestures(gestures: string[]): () => void {\n gestures.forEach((gesture) => {\n const stream = createTouchEventStream(gesture);\n this.subscribe(stream);\n });\n\n return () => {\n gestures.forEach((gesture) => {\n const stream = createTouchEventStream(gesture);\n this.unsubscribe(stream);\n });\n };\n }\n\n /**\n * 📱 Listen for phone notification events\n * @param handler - Function to handle notifications\n * @returns Cleanup function to remove the handler\n * @deprecated Use session.events.onPhoneNotifications() instead\n */\n onPhoneNotifications(handler: (data: PhoneNotification) => void): () => void {\n readNotificationWarnLog(this.getHttpsServerUrl() || \"\", this.getPackageName(), \"onPhoneNotifications\");\n return this.events.onPhoneNotifications(handler);\n }\n\n /**\n * 📱 Listen for phone notification dismissed events\n * @param handler - Function to handle notification dismissal data\n * @returns Cleanup function to remove the handler\n * @deprecated Use session.events.onPhoneNotificationDismissed() instead\n */\n onPhoneNotificationDismissed(handler: (data: PhoneNotificationDismissed) => void): () => void {\n return this.events.onPhoneNotificationDismissed(handler);\n }\n\n /**\n * 📡 Listen for VPS coordinates updates\n * @param handler - Function to handle VPS coordinates\n * @returns Cleanup function to remove the handler\n * @deprecated Use session.events.onVpsCoordinates() instead\n */\n onVpsCoordinates(handler: (data: VpsCoordinates) => void): () => void {\n this.subscribe(StreamType.VPS_COORDINATES);\n return this.events.onVpsCoordinates(handler);\n }\n\n /**\n * 📸 Listen for photo responses\n * @param handler - Function to handle photo response data\n * @returns Cleanup function to remove the handler\n * @deprecated Use session.events.onPhotoTaken() instead\n */\n onPhotoTaken(handler: (data: PhotoTaken) => void): () => void {\n this.subscribe(StreamType.PHOTO_TAKEN);\n return this.events.onPhotoTaken(handler);\n }\n\n // =====================================\n // 📡 Pub/Sub Interface\n // =====================================\n\n /**\n * 📬 Subscribe to a specific event stream\n * @param sub - A string or a rich subscription object\n */\n subscribe(sub: SubscriptionRequest): void {\n let type: ExtendedStreamType;\n let rate: string | undefined;\n\n if (typeof sub === \"string\") {\n type = sub;\n } else {\n // it's a LocationStreamRequest object\n type = sub.stream;\n rate = sub.rate;\n }\n\n if (APP_TO_APP_EVENT_TYPES.includes(type as string)) {\n this.logger.warn(\n `[AppSession] Attempted to subscribe to App-to-App event type '${type}', which is not a valid stream. Use the event handler (e.g., onAppMessage) instead.`,\n );\n return;\n }\n\n // NOTE: We no longer maintain this.subscriptions - subscriptions are derived from handlers\n // This prevents drift between handlers and subscriptions (Bug 007 fix)\n // The EventManager.addHandler() already tracks the subscription intent via handlers\n\n if (rate) {\n this.streamRates.set(type, rate);\n }\n\n if (this.ws?.readyState === 1) {\n this.updateSubscriptions();\n }\n }\n\n /**\n * 📭 Unsubscribe from a specific event stream\n * @param sub - The subscription to remove\n */\n unsubscribe(sub: SubscriptionRequest): void {\n let type: ExtendedStreamType;\n if (typeof sub === \"string\") {\n type = sub;\n } else {\n type = sub.stream;\n }\n\n if (APP_TO_APP_EVENT_TYPES.includes(type as string)) {\n this.logger.warn(\n `[AppSession] Attempted to unsubscribe from App-to-App event type '${type}', which is not a valid stream.`,\n );\n return;\n }\n // NOTE: We no longer maintain this.subscriptions - subscriptions are derived from handlers\n // The EventManager.removeHandler() already tracks the unsubscription intent\n\n this.streamRates.delete(type); // also remove from our rate map\n if (this.ws?.readyState === 1) {\n this.updateSubscriptions();\n }\n }\n\n /**\n * 🎯 Generic event listener (pub/sub style)\n * @param event - Event name to listen for\n * @param handler - Event handler function\n */\n on<T extends ExtendedStreamType>(event: T, handler: (data: EventData<T>) => void): () => void {\n return this.events.on(event, handler);\n }\n\n // =====================================\n // 🔌 Connection Management\n // =====================================\n\n /**\n * 🚀 Connect to MentraOS Cloud\n * @param sessionId - Unique session identifier\n * @returns Promise that resolves when connected\n */\n async connect(sessionId: string): Promise<void> {\n this.sessionId = sessionId;\n\n // Configure settings API client with the WebSocket URL and session ID\n // This allows settings to be fetched from the correct server\n this.settings.configureApiClient(this.config.packageName, this.config.mentraOSWebsocketUrl || \"\", sessionId);\n\n // Update the sessionId in the camera module\n if (this.camera) {\n this.camera.updateSessionId(sessionId);\n }\n\n // Update the sessionId in the audio module\n if (this.audio) {\n this.audio.updateSessionId(sessionId);\n }\n\n return new Promise((resolve, reject) => {\n try {\n // Stop ping interval before replacing the WebSocket.\n // This prevents a stale interval from a previous connection running\n // during the reconnect window. startPingInterval() also calls this,\n // but clearing here means no pings fire on a dead socket at all.\n this.stopPingInterval();\n\n // Clear previous resources if reconnecting\n if (this.ws) {\n // Don't call full dispose() as that would clear subscriptions\n if (this.ws.readyState !== 3) {\n // 3 = CLOSED\n this.ws.close();\n }\n this.ws = null;\n }\n\n // Validate WebSocket URL before attempting connection\n if (!this.config.mentraOSWebsocketUrl) {\n this.logger.error(\"WebSocket URL is missing or undefined\");\n reject(new MentraConnectionError(\"WebSocket URL is required\"));\n return;\n }\n\n this.logger.debug(`Connecting to ${this.config.mentraOSWebsocketUrl}`);\n\n // Create connection with error handling\n this.ws = new WebSocket(this.config.mentraOSWebsocketUrl);\n\n // Track WebSocket for automatic cleanup\n this.resources.track(() => {\n if (this.ws && this.ws.readyState !== 3) {\n // 3 = CLOSED\n this.ws.close();\n }\n });\n\n this.ws.on(\"open\", () => {\n try {\n this.sendConnectionInit();\n } catch (error: unknown) {\n this.logger.error(error, \"Error during connection initialization\");\n const errorMessage = error instanceof Error ? error.message : String(error);\n this.events.emit(\"error\", new MentraConnectionError(`Connection initialization failed: ${errorMessage}`));\n reject(error instanceof Error ? error : new MentraConnectionError(String(error)));\n }\n });\n\n // Message handler with comprehensive error recovery\n const messageHandler = async (data: Buffer | string, isBinary: boolean) => {\n try {\n // Handle binary messages (typically audio data)\n if (isBinary && Buffer.isBuffer(data)) {\n try {\n // Validate buffer before processing\n if (data.length === 0) {\n this.events.emit(\"error\", new MentraError(\"Received empty binary data\", \"EMPTY_DATA\"));\n return;\n }\n\n // Convert Node.js Buffer to ArrayBuffer safely\n const arrayBuf: ArrayBufferLike = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);\n\n // Create AUDIO_CHUNK event message with validation\n const audioChunk: AudioChunk = {\n type: StreamType.AUDIO_CHUNK,\n arrayBuffer: arrayBuf,\n timestamp: new Date(), // Ensure timestamp is present\n };\n\n this.handleMessage(audioChunk);\n return;\n } catch (error: unknown) {\n this.logger.error(error, \"Error processing binary message:\");\n const errorMessage = error instanceof Error ? error.message : String(error);\n this.events.emit(\n \"error\",\n new MentraError(`Failed to process binary message: ${errorMessage}`, \"PARSE_ERROR\"),\n );\n return;\n }\n }\n\n // Handle ArrayBuffer data type directly\n if (data instanceof ArrayBuffer) {\n return;\n }\n\n // Handle JSON messages with validation\n try {\n // Convert string data to JSON safely\n let jsonData: string;\n if (typeof data === \"string\") {\n jsonData = data;\n } else if (Buffer.isBuffer(data)) {\n jsonData = data.toString(\"utf8\");\n } else {\n throw new Error(\"Unknown message format\");\n }\n\n // Validate JSON before parsing\n if (!jsonData || jsonData.trim() === \"\") {\n this.events.emit(\"error\", new MentraError(\"Received empty JSON message\", \"PARSE_ERROR\"));\n return;\n }\n\n // Parse JSON with error handling\n const message = JSON.parse(jsonData) as CloudToAppMessage;\n\n // Basic schema validation\n if (!message || typeof message !== \"object\" || !(\"type\" in message)) {\n this.events.emit(\"error\", new MentraError(\"Malformed message: missing type property\", \"PARSE_ERROR\"));\n return;\n }\n\n // Process the validated message\n this.handleMessage(message);\n } catch (error: unknown) {\n this.logger.error(error, \"JSON parsing error\");\n const errorMessage = error instanceof Error ? error.message : String(error);\n this.events.emit(\"error\", new MentraError(`Failed to parse message: ${errorMessage}`, \"PARSE_ERROR\"));\n }\n } catch (error: unknown) {\n // Final catch - should never reach here if individual handlers work correctly\n this.logger.error({ error }, \"Unhandled message processing error\");\n const errorMessage = error instanceof Error ? error.message : String(error);\n this.events.emit(\"error\", new MentraError(`Unhandled message error: ${errorMessage}`, \"INTERNAL_ERROR\"));\n }\n };\n\n this.ws.on(\"message\", messageHandler);\n\n // Track event handler removal for automatic cleanup\n this.resources.track(() => {\n if (this.ws) {\n this.ws.off(\"message\", messageHandler);\n }\n });\n\n // Connection closure handler\n const closeHandler = (code: number, reason: string) => {\n const reasonStr = reason ? `: ${reason}` : \"\";\n const closeInfo = `Connection closed (code: ${code})${reasonStr}`;\n\n // Emit the disconnected event with structured data for better handling\n this.events.emit(\"disconnected\", {\n message: closeInfo,\n code: code,\n reason: reason || \"\",\n wasClean: code === 1000 || code === 1001,\n });\n\n // Only attempt reconnection for abnormal closures\n // https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent/code\n // 1000 (Normal Closure) and 1001 (Going Away) are normal\n // 1002-1015 are abnormal, and reason \"App stopped\" means intentional closure\n // 1008 usually when the userSession no longer exists on server. i.e user disconnected from cloud.\n const isNormalClosure = code === 1000 || code === 1001 || code === 1008;\n const isManualStop = reason && reason.includes(\"App stopped\");\n const isUserSessionEnded = reason && reason.includes(\"User session ended\");\n\n this.logger.debug(`WebSocket closed (code: ${code}${reasonStr})`);\n\n // Stop ping interval immediately — the socket is closed so pings are pointless.\n // connect() will restart it on reconnect; disconnect() would also clear it,\n // but stopping here prevents an orphaned interval if neither is called.\n this.stopPingInterval();\n\n // If user session ended, mark as terminated to prevent any future reconnection\n if (isUserSessionEnded) {\n this.terminated = true;\n this.logger.debug(\"User session ended — marked as terminated, no reconnection\");\n }\n\n if (!isNormalClosure && !isManualStop && !this.terminated) {\n this.logger.debug(\"Abnormal closure detected, attempting reconnection\");\n this.handleReconnection();\n } else {\n this.logger.debug(\"Normal closure, not reconnecting\");\n }\n\n // if user session ended, then trigger onStop.\n if (isUserSessionEnded) {\n this.logger.debug(\"User session ended — emitting disconnected event\");\n // Emit a disconnected event with a special flag to indicate session end\n // This will be caught by AppServer which will call the onStop callback\n const disconnectInfo = {\n message: \"User session ended\",\n code: 1000, // Normal closure\n reason: \"User session ended\",\n wasClean: true,\n permanent: true, // This is permanent - no reconnection\n sessionEnded: true, // Special flag to indicate session disposal\n };\n this.events.emit(\"disconnected\", disconnectInfo);\n }\n };\n\n this.ws.on(\"close\", closeHandler);\n\n // Track event handler removal\n this.resources.track(() => {\n if (this.ws) {\n this.ws.off(\"close\", closeHandler);\n }\n });\n\n // Connection error handler\n const errorHandler = (error: Error) => {\n this.logger.error(error, \"WebSocket error\");\n this.events.emit(\"error\", new MentraConnectionError(error.message));\n };\n\n this.ws.on(\"error\", (error: Error) => {\n errorHandler(error);\n });\n\n // Track event handler removal\n this.resources.track(() => {\n if (this.ws) {\n this.ws.off(\"error\", errorHandler);\n }\n });\n\n // Set up connection success handler\n const connectedCleanup = this.events.onConnected(() => resolve());\n\n // Track event handler removal\n this.resources.track(connectedCleanup);\n\n // Connection timeout with configurable duration\n const timeoutMs = 5000; // 5 seconds default\n const connectionTimeout = this.resources.setTimeout(() => {\n // Use tracked timeout that will be auto-cleared\n this.logger.error(\n {\n config: this.config,\n sessionId: this.sessionId,\n timeoutMs,\n },\n `Connection timeout after ${timeoutMs}ms`,\n );\n\n const err = new MentraTimeoutError(`Connection timeout after ${timeoutMs}ms`);\n this.events.emit(\"error\", err);\n reject(err);\n }, timeoutMs);\n\n // Clear timeout on successful connection\n const timeoutCleanup = this.events.onConnected(() => {\n clearTimeout(connectionTimeout);\n resolve();\n });\n\n // Track event handler removal\n this.resources.track(timeoutCleanup);\n } catch (error: unknown) {\n this.logger.error(error, \"Connection setup error\");\n const errorMessage = error instanceof Error ? error.message : String(error);\n reject(new MentraConnectionError(`Failed to setup connection: ${errorMessage}`));\n }\n });\n }\n\n /**\n * 🔄 Release ownership of this session to allow clean handoff\n * Call this before connecting to a different cloud instance or shutting down cleanly.\n * This signals to the cloud that no resurrection is needed.\n *\n * @param reason - Why ownership is being released\n */\n async releaseOwnership(reason: \"switching_clouds\" | \"clean_shutdown\" | \"user_logout\"): Promise<void> {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\n this.logger.debug(`[${this.config.packageName}] Cannot release ownership - WebSocket not open`);\n return;\n }\n\n const message: OwnershipReleaseMessage = {\n type: AppToCloudMessageType.OWNERSHIP_RELEASE,\n packageName: this.config.packageName,\n sessionId: this.sessionId || \"\",\n reason,\n timestamp: new Date(),\n };\n\n this.logger.info(\n { reason, sessionId: this.sessionId },\n `🔄 [${this.config.packageName}] Releasing ownership: ${reason}`,\n );\n\n this.send(message);\n\n // Small delay to ensure message is sent before any subsequent disconnect\n await new Promise((resolve) => setTimeout(resolve, 100));\n }\n\n /**\n * 👋 Disconnect from MentraOS Cloud\n * Flushes any pending SimpleStorage writes before closing\n *\n * @param options - Optional disconnect options\n * @param options.releaseOwnership - If true, send OWNERSHIP_RELEASE before disconnecting (enables clean handoff)\n * @param options.reason - Reason for ownership release (required if releaseOwnership is true)\n */\n async disconnect(options?: {\n releaseOwnership?: boolean;\n reason?: \"switching_clouds\" | \"clean_shutdown\" | \"user_logout\";\n }): Promise<void> {\n // Release ownership if requested (for clean handoffs)\n if (options?.releaseOwnership && options?.reason) {\n await this.releaseOwnership(options.reason);\n }\n\n // Flush any pending SimpleStorage writes before closing\n try {\n await this.simpleStorage.flush();\n console.log(\"SimpleStorage flushed on disconnect\");\n } catch (error) {\n console.error(\"Error flushing SimpleStorage on disconnect:\", error);\n // Continue with disconnect even if flush fails\n }\n\n // Clean up camera module first\n if (this.camera) {\n this.camera.cancelAllRequests();\n }\n\n // Clean up audio module\n if (this.audio) {\n this.audio.cancelAllRequests();\n }\n\n // Stop ping interval before disposing resources\n this.stopPingInterval();\n\n // Use the resource tracker to clean up everything\n this.resources.dispose();\n\n // Clean up additional resources not handled by the tracker\n this.ws = null;\n this.sessionId = null;\n // REMOVED: this.subscriptions.clear()\n // We no longer clear subscriptions here - they are derived from handlers\n // This is the key fix for Bug 007: clearing subscriptions here caused\n // empty subscription updates on reconnect when handlers still existed\n // See: cloud/issues/006-captions-and-apps-stopping/011-sdk-subscription-architecture-mismatch.md\n this.reconnectAttempts = 0;\n }\n\n /**\n * 🛠️ Get all current user settings\n * @returns A copy of the current settings array\n * @deprecated Use session.settings.getAll() instead\n */\n getSettings(): AppSettings {\n return this.settings.getAll();\n }\n\n /**\n * 🔍 Get a specific setting value by key\n * @param key The setting key to look for\n * @returns The setting's value, or undefined if not found\n * @deprecated Use session.settings.get(key) instead\n */\n getSetting<T>(key: string): T | undefined {\n return this.settings.get<T>(key);\n }\n\n /**\n * ⚙️ Configure settings-based subscription updates\n * This allows Apps to automatically update their subscriptions when certain settings change\n * @param options Configuration options for settings-based subscriptions\n */\n setSubscriptionSettings(options: {\n updateOnChange: string[]; // Setting keys that should trigger subscription updates\n handler: (settings: AppSettings) => ExtendedStreamType[]; // Handler that returns new subscriptions\n }): void {\n this.shouldUpdateSubscriptionsOnSettingsChange = true;\n this.subscriptionUpdateTriggers = options.updateOnChange;\n this.subscriptionSettingsHandler = options.handler;\n\n // If we already have settings, update subscriptions immediately\n if (this.settingsData.length > 0) {\n this.updateSubscriptionsFromSettings();\n }\n }\n\n /**\n * 🔄 Update subscriptions based on current settings\n * Called automatically when relevant settings change\n */\n private updateSubscriptionsFromSettings(): void {\n if (!this.subscriptionSettingsHandler) return;\n\n try {\n // Get desired subscriptions from settings handler\n const settingsSubscriptions = this.subscriptionSettingsHandler(this.settingsData);\n\n // NOTE: Settings-based subscriptions work differently from handler-based subscriptions\n // With the Bug 007 fix, subscriptions are now derived from EventManager.handlers\n // Apps using setSubscriptionSettings() should ensure their settings correspond to\n // registered handlers for the subscriptions to take effect.\n //\n // Log if there's a mismatch (for debugging during migration)\n const handlerStreams = this.events.getRegisteredStreams();\n if (settingsSubscriptions.length !== handlerStreams.length) {\n this.logger.warn(\n {\n settingsSubscriptions: JSON.stringify(settingsSubscriptions),\n handlerStreams: JSON.stringify(handlerStreams),\n },\n `[AppSession] Settings-based subscriptions (${settingsSubscriptions.length}) differ from handler-based subscriptions (${handlerStreams.length}). ` +\n `Subscriptions are now derived from handlers. Ensure handlers are registered for desired streams.`,\n );\n }\n\n // Send subscription update to cloud if connected\n // Note: updateSubscriptions() derives from handlers, so settings-based apps\n // should ensure their settings correspond to registered handlers\n if (this.ws && this.ws.readyState === 1) {\n this.updateSubscriptions();\n }\n } catch (error: unknown) {\n this.logger.error(error, \"Error updating subscriptions from settings\");\n const errorMessage = error instanceof Error ? error.message : String(error);\n this.events.emit(\n \"error\",\n new MentraError(`Failed to update subscriptions: ${errorMessage}`, \"SUBSCRIPTION_ERROR\"),\n );\n }\n }\n\n /**\n * 🧪 For testing: Update settings locally\n * In normal operation, settings come from the cloud\n * @param newSettings The new settings to apply\n */\n updateSettingsForTesting(newSettings: AppSettings): void {\n this.settingsData = newSettings;\n\n // Update the settings manager with the new settings\n this.settings.updateSettings(newSettings);\n\n // Emit update event for backwards compatibility\n this.events.emit(\"settings_update\", this.settingsData);\n\n // Check if we should update subscriptions\n if (this.shouldUpdateSubscriptionsOnSettingsChange) {\n this.updateSubscriptionsFromSettings();\n }\n }\n\n /**\n * 📝 Load configuration from a JSON file\n * @param jsonData JSON string containing App configuration\n * @returns The loaded configuration\n * @throws Error if the configuration is invalid\n */\n loadConfigFromJson(jsonData: string): AppConfig {\n try {\n const parsedConfig = JSON.parse(jsonData);\n\n if (validateAppConfig(parsedConfig)) {\n this.appConfig = parsedConfig;\n return parsedConfig;\n } else {\n throw new Error(\"Invalid App configuration format\");\n }\n } catch (error: unknown) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n throw new Error(`Failed to load App configuration: ${errorMessage}`);\n }\n }\n\n /**\n * 📋 Get the loaded App configuration\n * @returns The current App configuration or null if not loaded\n */\n getConfig(): AppConfig | null {\n return this.appConfig;\n }\n\n /**\n * 🔌 Get the WebSocket server URL for this session\n * @returns The WebSocket server URL used by this session\n */\n getServerUrl(): string | undefined {\n return this.config.mentraOSWebsocketUrl;\n }\n\n public getHttpsServerUrl(): string | undefined {\n if (!this.config.mentraOSWebsocketUrl) {\n return undefined;\n }\n return AppSession.convertToHttps(this.config.mentraOSWebsocketUrl);\n }\n\n private static convertToHttps(rawUrl: string | undefined): string {\n if (!rawUrl) return \"\";\n // Remove ws:// or wss://\n let url = rawUrl.replace(/^wss?:\\/\\//, \"\");\n // Remove trailing /app-ws\n url = url.replace(/\\/app-ws$/, \"\");\n // Prepend https://\n return `https://${url}`;\n }\n\n /**\n * 🔍 Get default settings from the App configuration\n * @returns Array of settings with default values\n * @throws Error if configuration is not loaded\n */\n getDefaultSettings(): AppSettings {\n if (!this.appConfig) {\n throw new Error(\"App configuration not loaded. Call loadConfigFromJson first.\");\n }\n\n return this.appConfig.settings\n .filter((s: AppSetting | { type: \"group\"; title: string }): s is AppSetting => s.type !== \"group\")\n .map((s: AppSetting) => ({\n ...s,\n value: s.defaultValue, // Set value to defaultValue\n }));\n }\n\n /**\n * 🔍 Get setting schema from configuration\n * @param key Setting key to look up\n * @returns The setting schema or undefined if not found\n */\n getSettingSchema(key: string): AppSetting | undefined {\n if (!this.appConfig) return undefined;\n\n const setting = this.appConfig.settings.find(\n (s: AppSetting | { type: \"group\"; title: string }) => s.type !== \"group\" && \"key\" in s && s.key === key,\n );\n\n return setting as AppSetting | undefined;\n }\n\n /**\n * 📡 Get WiFi connection status of glasses\n * @returns WiFi status object or null if glasses don't support WiFi or status not available\n */\n getWifiStatus(): { connected: boolean; ssid?: string | null } | null {\n if (!this.capabilities?.hasWifi) {\n return null;\n }\n return this.glassesConnectionState?.wifi || null;\n }\n\n /**\n * ✅ Check if glasses are connected to WiFi\n * @returns true if connected to WiFi, false otherwise\n */\n isWifiConnected(): boolean {\n return this.getWifiStatus()?.connected === true;\n }\n\n /**\n * 🌐 Request WiFi setup from mobile app\n * Triggers a popup on the mobile app that allows user to set up WiFi on glasses\n * @param reason Optional reason message to display to the user\n */\n requestWifiSetup(reason?: string): void {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\n throw new Error(\"Not connected to MentraOS Cloud\");\n }\n\n const message: RequestWifiSetup = {\n type: AppToCloudMessageType.REQUEST_WIFI_SETUP,\n packageName: this.config.packageName,\n sessionId: this.sessionId || \"\",\n reason,\n timestamp: new Date(),\n };\n\n this.send(message);\n }\n\n /**\n * 👂 Listen for glasses connection state changes (includes WiFi status)\n * @param handler Callback function to handle connection state updates\n * @returns Cleanup function to remove the listener\n */\n onGlassesConnectionState(handler: (state: any) => void): () => void {\n return this.events.on(StreamType.GLASSES_CONNECTION_STATE, handler);\n }\n\n // =====================================\n // 🔧 Private Methods\n // =====================================\n\n /**\n * 📨 Handle incoming messages from cloud\n */\n private handleMessage(message: CloudToAppMessage): void {\n try {\n // Validate message before processing\n if (!this.validateMessage(message)) {\n this.events.emit(\"error\", new MentraError(\"Invalid message format received\", \"PARSE_ERROR\"));\n return;\n }\n\n // Handle binary data (audio or video)\n if (message instanceof ArrayBuffer) {\n this.handleBinaryMessage(message);\n return;\n }\n\n // Using type guards to determine message type and safely handle each case\n try {\n if (isAppConnectionAck(message)) {\n // Get settings from connection acknowledgment\n const receivedSettings = message.settings || [];\n this.settingsData = receivedSettings;\n\n // Store config if provided\n if (message.config && validateAppConfig(message.config)) {\n this.appConfig = message.config;\n }\n\n // Use default settings from config if no settings were provided\n if (receivedSettings.length === 0 && this.appConfig) {\n try {\n this.settingsData = this.getDefaultSettings();\n } catch (error) {\n this.logger.warn(error, \"Failed to load default settings from config:\");\n }\n }\n\n // Update the settings manager with the new settings\n this.settings.updateSettings(this.settingsData);\n\n // Handle MentraOS system settings if provided\n this.logger.debug(\n { mentraosSettings: JSON.stringify(message.mentraosSettings) },\n `[AppSession] CONNECTION_ACK mentraosSettings}`,\n );\n if (message.mentraosSettings) {\n this.logger.info(\n { mentraosSettings: JSON.stringify(message.mentraosSettings) },\n `[AppSession] Calling updatementraosSettings with`,\n );\n this.settings.updateMentraosSettings(message.mentraosSettings);\n } else {\n this.logger.warn(`[AppSession] CONNECTION_ACK message missing mentraosSettings field`);\n }\n\n // Handle device capabilities if provided\n if (message.capabilities) {\n this.capabilities = message.capabilities;\n this.logger.info(`[AppSession] Device capabilities loaded for model: ${message.capabilities.modelName}`);\n } else {\n this.logger.debug(`[AppSession] No capabilities provided in CONNECTION_ACK`);\n }\n\n // Emit connected event with settings\n this.events.emit(\"connected\", this.settingsData);\n\n // Start app-level ping interval to keep the app-ws connection alive.\n // The SDK sends {type:\"ping\"} every 15s; the cloud responds {type:\"pong\"}.\n // Both directions produce traffic, satisfying infra bidirectional-traffic\n // requirements that prevent load balancer idle timeouts.\n // Only new-SDK apps send pings — old 2.x apps are unaffected.\n // See: cloud/issues/046-sdk-app-ws-liveness\n this.startPingInterval();\n\n // Log once to confirm Bug 007 fix is active (subscriptions derived from handlers)\n const handlerCount = this.events.getRegisteredStreams().length;\n this.logger.info(\n { patch: SDK_SUBSCRIPTION_PATCH, handlerCount },\n `[AppSession] 🔧 SDK Patch Active: ${SDK_SUBSCRIPTION_PATCH} - Subscriptions derived from ${handlerCount} handler(s)`,\n );\n\n // Update subscriptions (normal flow)\n this.updateSubscriptions();\n\n // If settings-based subscriptions are enabled, update those too\n if (this.shouldUpdateSubscriptionsOnSettingsChange && this.settingsData.length > 0) {\n this.updateSubscriptionsFromSettings();\n }\n } else if (isAppConnectionError(message) || message.type === \"connection_error\") {\n // Handle both App-specific connection_error and standard connection_error\n const errorMessage = message.message || \"Unknown connection error\";\n this.events.emit(\"error\", new MentraAuthError(errorMessage));\n } else if (message.type === StreamType.AUDIO_CHUNK) {\n // Check if we have a handler registered for AUDIO_CHUNK (derived from handlers)\n const hasAudioHandler = this.events.getRegisteredStreams().includes(StreamType.AUDIO_CHUNK);\n if (hasAudioHandler) {\n // Only process if we're subscribed to avoid unnecessary processing\n this.events.emit(StreamType.AUDIO_CHUNK, message);\n }\n } else if (isDataStream(message) && message.streamType === StreamType.GLASSES_CONNECTION_STATE) {\n // Store latest glasses connection state (includes WiFi info)\n this.glassesConnectionState = message.data;\n\n // Emit to subscribed listeners (check derived from handlers)\n const hasGlassesStateHandler = this.events\n .getRegisteredStreams()\n .includes(StreamType.GLASSES_CONNECTION_STATE);\n if (hasGlassesStateHandler) {\n const sanitizedData = this.sanitizeEventData(\n StreamType.GLASSES_CONNECTION_STATE,\n message.data,\n ) as EventData<typeof StreamType.GLASSES_CONNECTION_STATE>;\n this.events.emit(StreamType.GLASSES_CONNECTION_STATE, sanitizedData);\n }\n } else if (isDataStream(message)) {\n // Ensure streamType exists before emitting the event\n const messageStreamType = message.streamType as ExtendedStreamType;\n // if (message.streamType === StreamType.TRANSCRIPTION) {\n // const transcriptionData = message.data as TranscriptionData;\n // if (transcriptionData.transcribeLanguage) {\n // messageStreamType = createTranscriptionStream(transcriptionData.transcribeLanguage) as ExtendedStreamType;\n // }\n // } else if (message.streamType === StreamType.TRANSLATION) {\n // const translationData = message.data as TranslationData;\n // if (translationData.transcribeLanguage && translationData.translateLanguage) {\n // messageStreamType = createTranslationStream(translationData.transcribeLanguage, translationData.translateLanguage) as ExtendedStreamType;\n // }\n // }\n\n // Check if we have a handler registered for this stream type (derived from handlers)\n const hasHandler = this.events.getRegisteredStreams().includes(messageStreamType);\n if (messageStreamType && hasHandler) {\n const sanitizedData = this.sanitizeEventData(messageStreamType, message.data) as EventData<\n typeof messageStreamType\n >;\n this.events.emit(messageStreamType, sanitizedData);\n }\n } else if (isStreamStatus(message)) {\n // Emit as a standard stream event if subscribed (check derived from handlers)\n const hasStreamHandler = this.events.getRegisteredStreams().includes(StreamType.STREAM_STATUS);\n if (hasStreamHandler) {\n this.events.emit(StreamType.STREAM_STATUS, message);\n }\n\n // Update camera module's internal stream state\n this.camera.updateStreamState(message);\n } else if (isManagedStreamStatus(message)) {\n // Emit as a standard stream event if subscribed (check derived from handlers)\n const hasManagedStreamHandler = this.events.getRegisteredStreams().includes(StreamType.MANAGED_STREAM_STATUS);\n if (hasManagedStreamHandler) {\n this.events.emit(StreamType.MANAGED_STREAM_STATUS, message);\n }\n\n // Update camera module's managed stream state\n this.camera.handleManagedStreamStatus(message);\n } else if (isStreamStatusCheckResponse(message)) {\n // Handle stream status check response\n // This is a direct response, not a subscription-based event\n this.camera.handleStreamCheckResponse(message);\n } else if (isSettingsUpdate(message)) {\n // Store previous settings to check for changes\n const _prevSettings = [...this.settingsData];\n\n // Update internal settings storage\n this.settingsData = message.settings || [];\n\n // Update the settings manager with the new settings\n const changes = this.settings.updateSettings(this.settingsData);\n\n // Emit settings update event (for backwards compatibility)\n this.events.emit(\"settings_update\", this.settingsData);\n\n // --- MentraOS settings update logic ---\n // If the message.settings looks like MentraOS settings (object with known keys), update mentraosSettings\n if (message.settings && typeof message.settings === \"object\") {\n this.settings.updateMentraosSettings(message.settings);\n }\n\n // Check if we should update subscriptions\n if (this.shouldUpdateSubscriptionsOnSettingsChange) {\n // Check if any subscription trigger settings changed\n const shouldUpdateSubs = this.subscriptionUpdateTriggers.some((key) => {\n return key in changes;\n });\n\n if (shouldUpdateSubs) {\n this.updateSubscriptionsFromSettings();\n }\n }\n } else if (isCapabilitiesUpdate(message)) {\n // Update device capabilities\n const capabilitiesMessage = message as CapabilitiesUpdate;\n this.capabilities = capabilitiesMessage.capabilities;\n this.logger.info(\n capabilitiesMessage.capabilities,\n `[AppSession] Capabilities updated for model: ${capabilitiesMessage.modelName}`,\n );\n\n // Emit capabilities update event for applications to handle\n this.events.emit(\"capabilities_update\", {\n capabilities: capabilitiesMessage.capabilities,\n modelName: capabilitiesMessage.modelName,\n timestamp: capabilitiesMessage.timestamp,\n });\n } else if (isDeviceStateUpdate(message)) {\n // Update device state observables\n this.device.state.updateFromMessage(message.state);\n\n this.logger.debug(\n {\n changedFields: Object.keys(message.state),\n fullSnapshot: message.fullSnapshot,\n },\n `[AppSession] Device state updated via WebSocket`,\n );\n } else if (isAppStopped(message)) {\n const reason = message.reason || \"unknown\";\n const displayReason = `App stopped: ${reason}`;\n\n // Don't emit disconnected event here - let the WebSocket close handler do it\n // This prevents duplicate disconnected events when the session is disposed\n this.logger.info(`📤 [${this.config.packageName}] Received APP_STOPPED message: ${displayReason}`);\n\n // Clear reconnection state\n this.reconnectAttempts = 0;\n }\n // Handle dashboard mode changes\n else if (isDashboardModeChanged(message)) {\n try {\n // Use proper type\n const mode = message.mode || \"none\";\n\n // Update dashboard state in the API\n if (this.dashboard && \"content\" in this.dashboard) {\n (this.dashboard.content as any).setCurrentMode(mode);\n }\n } catch (error) {\n this.logger.error(error, \"Error handling dashboard mode change\");\n }\n }\n // Handle always-on dashboard state changes\n else if (isDashboardAlwaysOnChanged(message)) {\n try {\n // Use proper type\n const enabled = !!message.enabled;\n\n // Update dashboard state in the API\n if (this.dashboard && \"content\" in this.dashboard) {\n (this.dashboard.content as any).setAlwaysOnEnabled(enabled);\n }\n } catch (error) {\n this.logger.error(error, \"Error handling dashboard always-on change\");\n }\n }\n // Handle custom messages\n else if (message.type === CloudToAppMessageType.CUSTOM_MESSAGE) {\n this.events.emit(\"custom_message\", message);\n return;\n }\n // Handle App-to-App communication messages\n else if ((message as any).type === \"app_message_received\") {\n this.appEvents.emit(\"app_message_received\", message as any);\n } else if ((message as any).type === \"app_user_joined\") {\n this.appEvents.emit(\"app_user_joined\", message as any);\n } else if ((message as any).type === \"app_user_left\") {\n this.appEvents.emit(\"app_user_left\", message as any);\n } else if ((message as any).type === \"app_room_updated\") {\n this.appEvents.emit(\"app_room_updated\", message as any);\n } else if ((message as any).type === \"app_direct_message_response\") {\n const response = message as any;\n if (response.messageId && this.pendingDirectMessages.has(response.messageId)) {\n const { resolve } = this.pendingDirectMessages.get(response.messageId)!;\n resolve(response.success);\n this.pendingDirectMessages.delete(response.messageId);\n }\n } else if (message.type === \"augmentos_settings_update\") {\n const mentraosMsg = message as MentraosSettingsUpdate;\n if (mentraosMsg.settings && typeof mentraosMsg.settings === \"object\") {\n this.settings.updateMentraosSettings(mentraosMsg.settings);\n }\n }\n // Handle 'connection_error' as a specific case if cloud sends this string literal\n else if ((message as any).type === \"connection_error\") {\n // Treat 'connection_error' (string literal) like AppConnectionError\n // This handles cases where the cloud might send the type as a direct string\n // instead of the enum's 'tpa_connection_error' value.\n const errorMessage = (message as any).message || \"Unknown connection error (type: connection_error)\";\n this.logger.warn(\n `Received 'connection_error' type directly. Consider aligning cloud to send 'tpa_connection_error'. Message: ${errorMessage}`,\n );\n this.events.emit(\"error\", new MentraConnectionError(errorMessage));\n } else if (message.type === \"permission_error\") {\n // Handle permission errors from cloud\n this.logger.warn(\n {\n message: message.message,\n details: message.details,\n detailsCount: message.details?.length || 0,\n rejectedStreams: message.details?.map((d) => d.stream) || [],\n },\n \"Permission error received:\",\n );\n\n // Emit permission error event for application handling\n this.events.emit(\"permission_error\", {\n message: message.message,\n details: message.details,\n timestamp: message.timestamp,\n });\n\n // Optionally emit individual permission denied events for each stream\n message.details?.forEach((detail) => {\n this.events.emit(\"permission_denied\", {\n stream: detail.stream,\n requiredPermission: detail.requiredPermission,\n message: detail.message,\n });\n });\n } else if (message.type === \"audio_stream_ready\") {\n // Route AUDIO_STREAM_READY to the pending handler for this streamId\n const streamId = (message as any).streamId;\n const handler = this._audioStreamReadyHandlers.get(streamId);\n if (handler) {\n handler(message);\n this._audioStreamReadyHandlers.delete(streamId);\n } else {\n this.logger.debug({ streamId }, \"Received audio_stream_ready with no pending handler\");\n }\n } else if (isAudioPlayResponse(message)) {\n // Delegate audio play response handling to the audio module\n if (this.audio) {\n this.audio.handleAudioPlayResponse(message as AudioPlayResponse);\n }\n } else if (isPhotoResponse(message)) {\n // Photo responses can arrive via WebSocket when the cloud forwards error/success\n // from the phone's REST endpoint (POST /api/client/photo/response).\n // Success photos normally arrive via HTTP to /photo-upload, but errors always\n // come through WebSocket because the phone reports them to cloud, not directly\n // to the SDK. We MUST handle errors here to reject the pending promise immediately\n // instead of letting the developer wait 30s for a generic timeout.\n // See: OS-947, OS-951\n const photoResponse = message as PhotoResponse;\n const { requestId, success } = photoResponse;\n\n if (requestId && this.appServer) {\n const pending = this.appServer.completePhotoRequest(requestId);\n if (pending) {\n if (success) {\n // Success via WebSocket (legacy path — normally comes via /photo-upload HTTP)\n this.logger.info({ requestId }, \"📸 Photo success received via WebSocket (legacy path)\");\n pending.resolve({\n buffer: Buffer.from([]),\n mimeType: \"image/jpeg\",\n filename: \"photo.jpg\",\n requestId,\n size: 0,\n timestamp: new Date(),\n photoUrl: photoResponse.photoUrl,\n } as any);\n } else {\n // Error response — this is the critical path for OS-947/OS-951\n const errorCode = photoResponse.error?.code || \"UNKNOWN_ERROR\";\n const errorMessage = photoResponse.error?.message || \"Photo capture failed\";\n this.logger.warn(\n { requestId, errorCode, errorMessage },\n `📸 Photo error received via WebSocket: ${errorCode} - ${errorMessage}`,\n );\n pending.reject(new Error(`${errorCode}: ${errorMessage}`));\n }\n } else {\n this.logger.debug(\n { requestId, success },\n \"Photo response received via WebSocket but no pending request found (may have timed out or been completed via /photo-upload)\",\n );\n }\n } else {\n this.logger.warn(\n { requestId, hasAppServer: !!this.appServer },\n \"Photo response received via WebSocket but missing requestId or appServer\",\n );\n }\n } else if (isRgbLedControlResponse(message)) {\n // LED control responses are no longer handled - fire-and-forget mode\n this.logger.debug({ message }, \"Received LED control response (ignored - fire-and-forget mode)\");\n } else if (isRequestTelemetry(message)) {\n // Cloud is asking us to upload recent telemetry logs for incident debugging\n this.handleTelemetryRequest(message as RequestTelemetry).catch((err) => {\n this.logger.warn(err, \"handleTelemetryRequest failed\");\n });\n } else if ((message as any).type === \"pong\") {\n // Cloud responded to our app-level ping — bidirectional traffic maintained.\n // No action needed: the response itself satisfies the egress requirement.\n // See: cloud/issues/046-sdk-app-ws-liveness\n }\n // Handle unrecognized message types gracefully\n else {\n this.logger.warn(`Unrecognized message type: ${(message as any).type}`);\n this.events.emit(\n \"error\",\n new MentraError(`Unrecognized message type: ${(message as any).type}`, \"UNKNOWN_TYPE\"),\n );\n }\n } catch (processingError: unknown) {\n // Catch any errors during message processing to prevent App crashes\n this.logger.error(processingError, \"Error processing message:\");\n const errorMessage = processingError instanceof Error ? processingError.message : String(processingError);\n this.events.emit(\"error\", new MentraError(`Error processing message: ${errorMessage}`, \"INTERNAL_ERROR\"));\n }\n } catch (error: unknown) {\n // Final safety net to ensure the App doesn't crash on any unexpected errors\n this.logger.error(error, \"Unexpected error in message handler\");\n const errorMessage = error instanceof Error ? error.message : String(error);\n this.events.emit(\"error\", new Error(`Unexpected error in message handler: ${errorMessage}`));\n }\n }\n\n /**\n * 🏓 Start the app-level ping interval.\n * Clears any existing interval first so reconnects don't double-up.\n */\n private startPingInterval(): void {\n this.stopPingInterval();\n this.pingInterval = setInterval(() => {\n if (!this.ws || this.ws.readyState !== 1) return;\n try {\n this.ws.send(JSON.stringify({ type: \"ping\" }));\n } catch {\n // If send fails the WebSocket is already dead; the close handler\n // will fire and trigger reconnection — nothing to do here.\n }\n }, AppSession.PING_INTERVAL_MS);\n }\n\n /**\n * 🏓 Stop the app-level ping interval.\n */\n private stopPingInterval(): void {\n if (this.pingInterval !== null) {\n clearInterval(this.pingInterval);\n this.pingInterval = null;\n }\n }\n\n /**\n * 📤 Handle a REQUEST_TELEMETRY message from cloud.\n * Collects recent log entries from the AppServer's telemetry buffer and\n * POSTs them back to the cloud's incident endpoint so the support team\n * can diagnose production issues.\n *\n * @param request - The REQUEST_TELEMETRY message from cloud\n */\n private async handleTelemetryRequest(request: RequestTelemetry): Promise<void> {\n const { incidentId, uploadToken, windowMs } = request;\n\n // Read from this session's own buffer — already scoped to this user\n const cutoff = windowMs ? Date.now() - windowMs : 0;\n const logs = cutoff > 0 ? this.telemetryBuffer.filter((e) => e.timestamp >= cutoff) : [...this.telemetryBuffer];\n\n this.logger.debug({ incidentId, logCount: logs.length, windowMs }, \"Collecting telemetry for incident upload\");\n\n const payload: TelemetryResponse = {\n type: AppToCloudMessageType.TELEMETRY_RESPONSE,\n incidentId,\n packageName: this.config.packageName,\n logs,\n metadata: {\n bufferSize: logs.length,\n oldestEntryMs: logs.length > 0 ? logs[0].timestamp : undefined,\n newestEntryMs: logs.length > 0 ? logs[logs.length - 1].timestamp : undefined,\n },\n };\n\n const baseUrl = this.getHttpsServerUrl();\n if (!baseUrl) {\n this.logger.warn({ incidentId }, \"Cannot upload telemetry — no server URL available\");\n return;\n }\n\n try {\n // Strip any path suffix (e.g. /app-ws) to get the cloud root URL\n const cloudBase = baseUrl.replace(/\\/app-ws.*$/, \"\");\n const url = `${cloudBase}/api/incidents/${incidentId}/logs`;\n\n await axios.post(url, payload, {\n headers: {\n \"Authorization\": `Bearer ${uploadToken}`,\n \"Content-Type\": \"application/json\",\n },\n timeout: 10_000,\n });\n\n this.logger.info({ incidentId, logCount: logs.length }, \"Telemetry uploaded successfully\");\n } catch (err) {\n this.logger.error(err, `Failed to upload telemetry for incident ${incidentId}`);\n }\n }\n\n /**\n * 🧪 Validate incoming message structure\n * @param message - Message to validate\n * @returns boolean indicating if the message is valid\n */\n private validateMessage(message: CloudToAppMessage): boolean {\n // Handle ArrayBuffer case separately\n if (message instanceof ArrayBuffer) {\n return true; // ArrayBuffers are always considered valid at this level\n }\n\n // Check if message is null or undefined\n if (!message) {\n return false;\n }\n\n // Check if message has a type property\n if (!(\"type\" in message)) {\n return false;\n }\n\n // All other message types should be objects with a type property\n return true;\n }\n\n /**\n * 📦 Handle binary message data (audio or video)\n * @param buffer - Binary data as ArrayBuffer\n */\n private handleBinaryMessage(buffer: ArrayBuffer): void {\n try {\n // Safety check - only process if we have a handler registered (derived from handlers)\n const hasAudioHandler = this.events.getRegisteredStreams().includes(StreamType.AUDIO_CHUNK);\n if (!hasAudioHandler) {\n return;\n }\n\n // Validate buffer has content before processing\n if (!buffer || buffer.byteLength === 0) {\n this.events.emit(\"error\", new Error(\"Received empty binary message\"));\n return;\n }\n\n // Create a safety wrapped audio chunk with proper defaults\n const audioChunk: AudioChunk = {\n type: StreamType.AUDIO_CHUNK,\n timestamp: new Date(),\n arrayBuffer: buffer,\n sampleRate: 16000, // Default sample rate\n };\n\n // Emit to subscribers\n this.events.emit(StreamType.AUDIO_CHUNK, audioChunk);\n } catch (error: unknown) {\n this.logger.error(error, \"Error processing binary message\");\n const errorMessage = error instanceof Error ? error.message : String(error);\n this.events.emit(\"error\", new Error(`Error processing binary message: ${errorMessage}`));\n }\n }\n\n /**\n * 🧹 Sanitize event data to prevent crashes from malformed data\n * @param streamType - The type of stream data\n * @param data - The potentially unsafe data to sanitize\n * @returns Sanitized data safe for processing\n */\n private sanitizeEventData(streamType: ExtendedStreamType, data: unknown): any {\n try {\n // If data is null or undefined, return an empty object to prevent crashes\n if (data === null || data === undefined) {\n return {};\n }\n\n // For specific stream types, perform targeted sanitization\n switch (streamType) {\n case StreamType.TRANSCRIPTION: {\n // Ensure text field exists and is a string\n if (typeof (data as TranscriptionData).text !== \"string\") {\n return {\n text: \"\",\n isFinal: true,\n startTime: Date.now(),\n endTime: Date.now(),\n };\n }\n break;\n }\n\n case StreamType.HEAD_POSITION: {\n // Ensure position data has required numeric fields\n // Handle HeadPosition - Note the property position instead of x,y,z\n const pos = data as any;\n if (typeof pos?.position !== \"string\") {\n return { position: \"up\", timestamp: new Date() };\n }\n break;\n }\n\n case StreamType.BUTTON_PRESS: {\n // Ensure button type is valid\n const btn = data as any;\n if (!btn.buttonId || !btn.pressType) {\n return {\n buttonId: \"unknown\",\n pressType: \"short\",\n timestamp: new Date(),\n };\n }\n break;\n }\n }\n\n return data;\n } catch (error: unknown) {\n this.logger.error(error, `Error sanitizing ${streamType} data`);\n // Return a safe empty object if something goes wrong\n return {};\n }\n }\n\n /**\n * 🔐 Send connection initialization message\n */\n private sendConnectionInit(): void {\n const message: AppConnectionInit = {\n type: AppToCloudMessageType.CONNECTION_INIT,\n sessionId: this.sessionId!,\n packageName: this.config.packageName,\n apiKey: this.config.apiKey,\n timestamp: new Date(),\n };\n this.send(message);\n }\n\n /**\n * 📝 Update subscription list with cloud\n */\n private updateSubscriptions(): void {\n // CRITICAL FIX (Bug 007): Derive subscriptions from EventManager.handlers\n // This ensures subscriptions can NEVER be empty if handlers exist\n // Previously, this.subscriptions could drift out of sync with handlers\n // See: cloud/issues/006-captions-and-apps-stopping/011-sdk-subscription-architecture-mismatch.md\n const derivedSubscriptions = this.events.getRegisteredStreams();\n\n this.logger.info(\n { subscriptions: JSON.stringify(derivedSubscriptions) },\n `[AppSession] updateSubscriptions: sending ${derivedSubscriptions.length} subscriptions to cloud (derived from handlers)`,\n );\n\n // Build the array of SubscriptionRequest objects to send to the cloud\n const subscriptionPayload: SubscriptionRequest[] = derivedSubscriptions.map((stream) => {\n const rate = this.streamRates.get(stream);\n if (rate && stream === StreamType.LOCATION_STREAM) {\n return { stream: \"location_stream\", rate: rate as any };\n }\n return stream;\n });\n\n const message: AppSubscriptionUpdate = {\n type: AppToCloudMessageType.SUBSCRIPTION_UPDATE,\n packageName: this.config.packageName,\n subscriptions: subscriptionPayload,\n sessionId: this.sessionId!,\n timestamp: new Date(),\n };\n this.send(message);\n }\n\n /**\n * 🔄 Handle reconnection with exponential backoff\n */\n private async handleReconnection(): Promise<void> {\n // Check if session was terminated (e.g., \"User session ended\")\n if (this.terminated) {\n this.logger.info(\n `🔄 Reconnection skipped: session was terminated (User session ended). ` +\n `If cloud restarts app, onSession will be called with fresh handlers.`,\n );\n return;\n }\n\n // Check if reconnection is allowed\n if (!this.config.autoReconnect || !this.sessionId) {\n this.logger.debug(\n `🔄 Reconnection skipped: autoReconnect=${this.config.autoReconnect}, sessionId=${\n this.sessionId ? \"valid\" : \"invalid\"\n }`,\n );\n return;\n }\n\n // Check if we've exceeded the maximum attempts\n const maxAttempts = this.config.maxReconnectAttempts || 3;\n if (this.reconnectAttempts >= maxAttempts) {\n this.logger.warn(`Reconnection failed after ${maxAttempts} attempts, giving up`);\n\n // Emit a permanent disconnection event to trigger onStop in the App server\n this.events.emit(\"disconnected\", {\n message: `Connection permanently lost after ${maxAttempts} failed reconnection attempts`,\n code: 4000, // Custom code for max reconnection attempts exhausted\n reason: \"Maximum reconnection attempts exceeded\",\n wasClean: false,\n permanent: true, // Flag this as a permanent disconnection\n });\n\n return;\n }\n\n // Calculate delay with exponential backoff\n const baseDelay = this.config.reconnectDelay || 1000;\n const delay = baseDelay * Math.pow(2, this.reconnectAttempts);\n this.reconnectAttempts++;\n\n this.logger.warn(`Connection lost, reconnecting (${this.reconnectAttempts}/${maxAttempts})...`);\n\n // Use the resource tracker for the timeout\n await new Promise<void>((resolve) => {\n this.resources.setTimeout(() => resolve(), delay);\n });\n\n try {\n await this.connect(this.sessionId);\n this.logger.info(\"Reconnected successfully\");\n this.reconnectAttempts = 0;\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n this.logger.debug(error, \"Reconnection attempt failed\");\n this.events.emit(\"error\", new Error(`Reconnection failed: ${errorMessage}`));\n\n // Check if this was the last attempt\n if (this.reconnectAttempts >= maxAttempts) {\n this.logger.debug(\"Final reconnection attempt failed, emitting permanent disconnection\");\n\n // Emit permanent disconnection event after the last failed attempt\n this.events.emit(\"disconnected\", {\n message: `Connection permanently lost after ${maxAttempts} failed reconnection attempts`,\n code: 4000, // Custom code for max reconnection attempts exhausted\n reason: \"Maximum reconnection attempts exceeded\",\n wasClean: false,\n permanent: true, // Flag this as a permanent disconnection\n });\n }\n }\n }\n\n /**\n * 📤 Public API for modules to send messages\n * Always uses current WebSocket connection\n */\n public sendMessage(message: AppToCloudMessage): void {\n return this.send(message);\n }\n\n /**\n * 📤 Send raw binary data over the WebSocket.\n * Used by AudioOutputStream to push audio frames to the cloud relay.\n *\n * Binary frame format for audio streaming:\n * [36 bytes: streamId UUID as ASCII] [N bytes: audio data]\n *\n * @param data - Binary data to send\n * @throws {Error} If WebSocket is not connected\n * @internal\n */\n public sendBinary(data: Uint8Array | Buffer): void {\n if (!this.ws) {\n throw new Error(\"WebSocket connection not established\");\n }\n if (this.ws.readyState !== 1) {\n throw new Error(\"WebSocket not connected\");\n }\n try {\n this.ws.send(data);\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : String(err);\n this.logger.debug({ err }, \"Failed to send binary frame\");\n throw new Error(`Failed to send binary data: ${errorMessage}`);\n }\n }\n\n /**\n * 📤 Send message to cloud with validation and error handling\n * @throws {Error} If WebSocket is not connected\n */\n private send(message: AppToCloudMessage): void {\n try {\n // Verify WebSocket connection is valid\n if (!this.ws) {\n throw new Error(\"WebSocket connection not established\");\n }\n\n if (this.ws.readyState !== 1) {\n const stateMap: Record<number, string> = {\n 0: \"CONNECTING\",\n 1: \"OPEN\",\n 2: \"CLOSING\",\n 3: \"CLOSED\",\n };\n const stateName = stateMap[this.ws.readyState] || \"UNKNOWN\";\n throw new Error(`WebSocket not connected (current state: ${stateName})`);\n }\n\n // Validate message before sending\n if (!message || typeof message !== \"object\") {\n throw new Error(\"Invalid message: must be an object\");\n }\n\n if (!(\"type\" in message)) {\n throw new Error('Invalid message: missing \"type\" property');\n }\n\n // Ensure message format is consistent\n if (!(\"timestamp\" in message) || !(message.timestamp instanceof Date)) {\n message.timestamp = new Date();\n }\n\n // Try to send with error handling\n try {\n const serializedMessage = JSON.stringify(message);\n this.ws.send(serializedMessage);\n } catch (sendError: unknown) {\n const errorMessage = sendError instanceof Error ? sendError.message : String(sendError);\n throw new Error(`Failed to send message: ${errorMessage}`);\n }\n } catch (error: unknown) {\n // Check if this is an expected disconnection error (not a real error)\n const isDisconnectError =\n error instanceof Error &&\n (error.message.includes(\"WebSocket not connected\") ||\n error.message.includes(\"CLOSED\") ||\n error.message.includes(\"CLOSING\"));\n\n if (isDisconnectError) {\n // Don't log as error - this is expected when user disconnects\n // Apps should handle this gracefully by checking session.isConnected\n this.logger.debug(error, \"Message send skipped - session disconnected\");\n } else {\n // This is an actual error that needs attention\n this.logger.error(error, \"Message send error\");\n }\n\n // Ensure we always emit an Error object\n if (error instanceof Error) {\n this.events.emit(\"error\", error);\n } else {\n this.events.emit(\"error\", new Error(String(error)));\n }\n\n // Re-throw to maintain the original function behavior\n throw error;\n }\n }\n\n /**\n * Fetch the onboarding instructions for this session from the backend.\n * @returns Promise resolving to the instructions string or null\n */\n public async getInstructions(): Promise<string | null> {\n try {\n const baseUrl = this.getServerUrl();\n const response = await axios.get(`${baseUrl}/api/instructions`, {\n params: { userId: this.userId },\n });\n return response.data.instructions || null;\n } catch (err) {\n this.logger.error(err, `Error fetching instructions from backend`);\n return null;\n }\n }\n // =====================================\n // 👥 App-to-App Communication Interface\n // =====================================\n\n /**\n * 👥 Discover other users currently using the same App\n * @param includeProfiles - Whether to include user profile information\n * @returns Promise that resolves with list of active users\n */\n async discoverAppUsers(domain: string, includeProfiles = false): Promise<any> {\n // Use the domain argument as the base URL if provided\n if (!domain) {\n throw new Error(\"Domain (API base URL) is required for user discovery\");\n }\n const url = `${domain}/api/app-communication/discover-users`;\n // Use the user's core token for authentication\n const appApiKey = this.config.apiKey; // This may need to be updated if you store the core token elsewhere\n\n if (!appApiKey) {\n throw new Error(\"Core token (apiKey) is required for user discovery\");\n }\n const body = {\n packageName: this.config.packageName,\n userId: this.userId,\n includeUserProfiles: includeProfiles,\n };\n const response = await fetch(url, {\n method: \"POST\",\n headers: {\n \"Authorization\": `Bearer ${appApiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(body),\n });\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Failed to discover users: ${response.status} ${response.statusText} - ${errorText}`);\n }\n return await response.json();\n }\n\n /**\n * 🔍 Check if a specific user is currently active\n * @param userId - User ID to check for\n * @returns Promise that resolves with boolean indicating if user is active\n */\n async isUserActive(userId: string): Promise<boolean> {\n try {\n const userList = await this.discoverAppUsers(\"\", false);\n return userList.users.some((user: any) => user.userId === userId);\n } catch (error) {\n this.logger.error({ error, userId }, \"Error checking if user is active\");\n return false;\n }\n }\n\n /**\n * 📊 Get user count for this App\n * @returns Promise that resolves with number of active users\n */\n async getUserCount(domain: string): Promise<number> {\n try {\n const userList = await this.discoverAppUsers(domain, false);\n return userList.totalUsers;\n } catch (error) {\n this.logger.error(error, \"Error getting user count\");\n return 0;\n }\n }\n\n /**\n * 📢 Send broadcast message to all users with same App active\n * @param payload - Message payload to send\n * @param roomId - Optional room ID for room-based messaging\n * @returns Promise that resolves when message is sent\n */\n async broadcastToAppUsers(payload: any, _roomId?: string): Promise<void> {\n try {\n const messageId = this.generateMessageId();\n\n const message = {\n type: \"app_broadcast_message\",\n packageName: this.config.packageName,\n sessionId: this.sessionId!,\n payload,\n messageId,\n senderUserId: this.userId,\n timestamp: new Date(),\n };\n\n this.send(message as any);\n } catch (error: unknown) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n throw new Error(`Failed to broadcast message: ${errorMessage}`);\n }\n }\n\n /**\n * 📤 Send direct message to specific user\n * @param targetUserId - User ID to send message to\n * @param payload - Message payload to send\n * @returns Promise that resolves with success status\n */\n async sendDirectMessage(targetUserId: string, payload: any): Promise<boolean> {\n return new Promise((resolve, reject) => {\n try {\n const messageId = this.generateMessageId();\n\n // Store promise resolver\n this.pendingDirectMessages.set(messageId, { resolve, reject });\n\n const message = {\n type: \"app_direct_message\",\n packageName: this.config.packageName,\n sessionId: this.sessionId!,\n targetUserId,\n payload,\n messageId,\n senderUserId: this.userId,\n timestamp: new Date(),\n };\n\n this.send(message as any);\n\n // Set timeout to avoid hanging promises\n const timeoutMs = 15000; // 15 seconds\n this.resources.setTimeout(() => {\n if (this.pendingDirectMessages.has(messageId)) {\n this.pendingDirectMessages.get(messageId)!.reject(new Error(\"Direct message timed out\"));\n this.pendingDirectMessages.delete(messageId);\n }\n }, timeoutMs);\n } catch (error: unknown) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n reject(new Error(`Failed to send direct message: ${errorMessage}`));\n }\n });\n }\n\n /**\n * 🏠 Join a communication room for group messaging\n * @param roomId - Room ID to join\n * @param roomConfig - Optional room configuration\n * @returns Promise that resolves when room is joined\n */\n async joinAppRoom(\n roomId: string,\n roomConfig?: {\n maxUsers?: number;\n isPrivate?: boolean;\n metadata?: any;\n },\n ): Promise<void> {\n try {\n const message = {\n type: \"app_room_join\",\n packageName: this.config.packageName,\n sessionId: this.sessionId!,\n roomId,\n roomConfig,\n timestamp: new Date(),\n };\n\n this.send(message as any);\n } catch (error: unknown) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n throw new Error(`Failed to join room: ${errorMessage}`);\n }\n }\n\n /**\n * 🚪 Leave a communication room\n * @param roomId - Room ID to leave\n * @returns Promise that resolves when room is left\n */\n async leaveAppRoom(roomId: string): Promise<void> {\n try {\n const message = {\n type: \"app_room_leave\",\n packageName: this.config.packageName,\n sessionId: this.sessionId!,\n roomId,\n timestamp: new Date(),\n };\n\n this.send(message as any);\n } catch (error: unknown) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n throw new Error(`Failed to leave room: ${errorMessage}`);\n }\n }\n\n /**\n * 📨 Listen for messages from other App users\n * @param handler - Function to handle incoming messages\n * @returns Cleanup function to remove the handler\n */\n onAppMessage(handler: (message: any) => void): () => void {\n this.appEvents.on(\"app_message_received\", handler);\n return () => this.appEvents.off(\"app_message_received\", handler);\n }\n\n /**\n * 👋 Listen for user join events\n * @param handler - Function to handle user join events\n * @returns Cleanup function to remove the handler\n */\n onAppUserJoined(handler: (data: any) => void): () => void {\n this.appEvents.on(\"app_user_joined\", handler);\n return () => this.appEvents.off(\"app_user_joined\", handler);\n }\n\n /**\n * 🚪 Listen for user leave events\n * @param handler - Function to handle user leave events\n * @returns Cleanup function to remove the handler\n */\n onAppUserLeft(handler: (data: any) => void): () => void {\n this.appEvents.on(\"app_user_left\", handler);\n return () => this.appEvents.off(\"app_user_left\", handler);\n }\n\n /**\n * 🏠 Listen for room update events\n * @param handler - Function to handle room updates\n * @returns Cleanup function to remove the handler\n */\n onAppRoomUpdated(handler: (data: any) => void): () => void {\n this.appEvents.on(\"app_room_updated\", handler);\n return () => this.appEvents.off(\"app_room_updated\", handler);\n }\n\n /**\n * 🔧 Generate unique message ID\n * @returns Unique message identifier\n */\n private generateMessageId(): string {\n return `msg_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;\n }\n}\n\n/**\n * @deprecated Use `AppSessionConfig` instead. `TpaSessionConfig` is deprecated and will be removed in a future version.\n * This is an alias for backward compatibility only.\n *\n * @example\n * ```typescript\n * // ❌ Deprecated - Don't use this\n * const config: TpaSessionConfig = { ... };\n *\n * // ✅ Use this instead\n * const config: AppSessionConfig = { ... };\n * ```\n */\nexport type TpaSessionConfig = AppSessionConfig;\n\n/**\n * @deprecated Use `AppSession` instead. `TpaSession` is deprecated and will be removed in a future version.\n * This is an alias for backward compatibility only.\n *\n * @example\n * ```typescript\n * // ❌ Deprecated - Don't use this\n * const session = new TpaSession(config);\n *\n * // ✅ Use this instead\n * const session = new AppSession(config);\n * ```\n */\nexport class TpaSession extends AppSession {\n constructor(config: TpaSessionConfig) {\n super(config);\n // Emit a deprecation warning to help developers migrate\n console.warn(\n \"⚠️ DEPRECATION WARNING: TpaSession is deprecated and will be removed in a future version. \" +\n \"Please use AppSession instead. \" +\n 'Simply replace \"TpaSession\" with \"AppSession\" in your code.',\n );\n }\n}\n\n// Export module classes for developers\nexport { CameraModule } from \"./modules/camera\";\nexport { LedModule } from \"./modules/led\";\nexport { AudioManager } from \"./modules/audio\";\nexport { SimpleStorage } from \"./modules/simple-storage\";\n\n// Export module types for developers\nexport type { PhotoRequestOptions, StreamOptions } from \"./modules/camera\";\nexport type { LedControlOptions } from \"./modules/led\";\nexport type { AudioPlayOptions, AudioPlayResult, SpeakOptions } from \"./modules/audio\";\n",
27
51
  "/**\n * 🎨 Layout Manager Module\n *\n * Manages AR display layouts for Apps. This class provides an easy-to-use interface\n * for showing different types of content in the user's AR view.\n *\n * @example\n * ```typescript\n * const layouts = new LayoutManager('org.example.myapp', sendMessage);\n *\n * // Show a simple message\n * layouts.showTextWall('Hello AR World!');\n *\n * // Show a card with title\n * layouts.showReferenceCard('Weather', 'Sunny and 75°F');\n * ```\n */\nimport { BitmapUtils } from \"../../utils/bitmap-utils\";\nimport {\n DisplayRequest,\n Layout,\n TextWall,\n DoubleTextWall,\n ReferenceCard,\n DashboardCard,\n LayoutType,\n ViewType,\n AppToCloudMessageType,\n BitmapView,\n BitmapAnimation,\n ClearView,\n} from \"../../types\";\n\nexport class LayoutManager {\n /**\n * 🎯 Creates a new LayoutManager instance\n *\n * @param packageName - App package identifier\n * @param sendMessage - Function to send display requests to MentraOS\n */\n constructor(\n private packageName: string,\n private sendMessage: (message: DisplayRequest) => void,\n ) {}\n\n /**\n * 📦 Creates a display event request with validation\n *\n * @param layout - Layout configuration to display\n * @param view - View type (main or dashboard)\n * @param durationMs - How long to show the layout (optional)\n * @returns Formatted display request\n * @throws Error if layout is invalid\n */\n private createDisplayEvent(\n layout: Layout,\n view: ViewType = ViewType.MAIN,\n durationMs?: number,\n ): DisplayRequest {\n try {\n // Validate layout data before sending\n if (!layout) {\n throw new Error(\"Layout cannot be null or undefined\");\n }\n\n if (!layout.layoutType) {\n throw new Error(\"Layout must have a layoutType property\");\n }\n\n // Layout-specific validations\n switch (layout.layoutType) {\n case LayoutType.TEXT_WALL:\n if (typeof (layout as TextWall).text !== \"string\") {\n throw new Error(\"TextWall layout must have a text property\");\n }\n // Ensure text is not too long (prevent performance issues)\n if ((layout as TextWall).text.length > 1000) {\n console.warn(\n \"TextWall text is very long, this may cause performance issues\",\n );\n }\n break;\n\n case LayoutType.DOUBLE_TEXT_WALL:\n const doubleText = layout as DoubleTextWall;\n if (typeof doubleText.topText !== \"string\") {\n throw new Error(\n \"DoubleTextWall layout must have a topText property\",\n );\n }\n if (typeof doubleText.bottomText !== \"string\") {\n throw new Error(\n \"DoubleTextWall layout must have a bottomText property\",\n );\n }\n break;\n\n case LayoutType.REFERENCE_CARD:\n const refCard = layout as ReferenceCard;\n if (typeof refCard.title !== \"string\") {\n throw new Error(\"ReferenceCard layout must have a title property\");\n }\n if (typeof refCard.text !== \"string\") {\n throw new Error(\"ReferenceCard layout must have a text property\");\n }\n break;\n\n case LayoutType.DASHBOARD_CARD:\n const dashCard = layout as DashboardCard;\n if (typeof dashCard.leftText !== \"string\") {\n throw new Error(\n \"DashboardCard layout must have a leftText property\",\n );\n }\n if (typeof dashCard.rightText !== \"string\") {\n throw new Error(\n \"DashboardCard layout must have a rightText property\",\n );\n }\n break;\n\n case LayoutType.BITMAP_VIEW:\n const bitmapView = layout as BitmapView;\n if (typeof bitmapView.data !== \"string\") {\n throw new Error(\"BitmapView layout must have a data property\");\n }\n // Check if data is too large (prevent OOM errors)\n if (bitmapView.data.length > 1000000) {\n // 1MB limit\n throw new Error(\n \"Bitmap data is too large (>1MB), please reduce size\",\n );\n }\n break;\n\n case LayoutType.CLEAR_VIEW:\n // ClearView has no additional validation needed\n break;\n }\n\n // Validate view type\n if (view !== ViewType.MAIN && view !== ViewType.DASHBOARD) {\n console.warn(`Invalid view type: ${view}, defaulting to MAIN`);\n view = ViewType.MAIN;\n }\n\n // Validate duration if provided\n if (durationMs !== undefined) {\n if (typeof durationMs !== \"number\" || durationMs < 0) {\n console.warn(`Invalid duration: ${durationMs}, ignoring`);\n durationMs = undefined;\n }\n }\n\n // Create the display request with validated data\n return {\n timestamp: new Date(),\n sessionId: \"\", // Will be filled by session\n type: AppToCloudMessageType.DISPLAY_REQUEST,\n packageName: this.packageName,\n view,\n layout,\n durationMs,\n };\n } catch (error) {\n console.error(\"Error creating display event:\", error);\n throw error; // Re-throw to notify caller\n }\n }\n\n /**\n * 📝 Shows a single block of text\n *\n * Best for:\n * - Simple messages\n * - Status updates\n * - Notifications\n *\n * @param text - Text content to display\n * @param options - Optional parameters (view, duration, priority)\n * - priority: If true, this display will not be overridden by other requests (default: false)\n *\n * @example\n * ```typescript\n * layouts.showTextWall('Connected to server');\n * layouts.showTextWall('Onboarding!', { priority: true });\n * ```\n */\n showTextWall(\n text: string,\n options?: { view?: ViewType; durationMs?: number },\n ) {\n try {\n // Validate input before processing\n if (text === undefined || text === null) {\n text = \"\"; // Default to empty string instead of crashing\n console.warn(\"showTextWall called with null/undefined text\");\n }\n\n // Ensure text is a string\n if (typeof text !== \"string\") {\n text = String(text); // Convert to string\n console.warn(\"showTextWall: Non-string input converted to string\");\n }\n\n // Create layout with validated text\n const layout: TextWall = {\n layoutType: LayoutType.TEXT_WALL,\n text,\n };\n\n // Create and send display event with error handling\n try {\n const displayEvent = this.createDisplayEvent(\n layout,\n options?.view,\n options?.durationMs,\n );\n this.sendMessage(displayEvent);\n } catch (error) {\n console.error(\"Failed to display text wall:\", error);\n // Don't re-throw - prevent app crashes\n }\n } catch (error) {\n console.error(\"Error in showTextWall:\", error);\n // Don't crash the App - fail gracefully\n }\n }\n\n /**\n * ↕️ Shows two sections of text, one above the other\n *\n * Best for:\n * - Before/After content\n * - Question/Answer displays\n * - Two-part messages\n * - Comparisons\n *\n * @param topText - Text to show in top section\n * @param bottomText - Text to show in bottom section\n * @param options - Optional parameters (view, duration)\n *\n * @example\n * ```typescript\n * layouts.showDoubleTextWall(\n * 'Original: Hello',\n * 'Translated: Bonjour'\n * );\n * ```\n */\n showDoubleTextWall(\n topText: string,\n bottomText: string,\n options?: { view?: ViewType; durationMs?: number },\n ) {\n const layout: DoubleTextWall = {\n layoutType: LayoutType.DOUBLE_TEXT_WALL,\n topText,\n bottomText,\n };\n this.sendMessage(\n this.createDisplayEvent(layout, options?.view, options?.durationMs),\n );\n }\n\n /**\n * 📇 Shows a card with a title and content\n *\n * Best for:\n * - Titled content\n * - Important information\n * - Structured data\n * - Notifications with context\n *\n * @param title - Card title\n * @param text - Main content text\n * @param options - Optional parameters (view, duration)\n *\n * @example\n * ```typescript\n * layouts.showReferenceCard(\n * 'Meeting Reminder',\n * 'Team standup in 5 minutes'\n * );\n * ```\n */\n showReferenceCard(\n title: string,\n text: string,\n options?: { view?: ViewType; durationMs?: number },\n ) {\n const layout: ReferenceCard = {\n layoutType: LayoutType.REFERENCE_CARD,\n title,\n text,\n };\n this.sendMessage(\n this.createDisplayEvent(layout, options?.view, options?.durationMs),\n );\n }\n\n /**\n * 📇 Shows a bitmap\n *\n * Uses the proven animation system internally for proper L/R eye synchronization.\n * This ensures single bitmap displays work consistently with the same\n * hardware-optimized timing as animations.\n *\n * @param data - hex or base64 encoded bitmap data\n * @param options - Optional parameters (view, duration)\n *\n * @example\n * ```typescript\n * layouts.showBitmapView(\n * yourHexOrBase64EncodedBitmapDataString\n * );\n * ```\n */\n async showBitmapView(\n base64Bitmap: string,\n options?: { view?: ViewType; padding?: { left: number; top: number } },\n ) {\n const padding = options?.padding ?? { left: 50, top: 35 };\n const bitmapFrame = await BitmapUtils.padBase64Bitmap(\n base64Bitmap,\n padding,\n );\n const validation = BitmapUtils.validateBase64Bitmap(bitmapFrame);\n if (!validation.isValid) {\n throw new Error(\n `❌ Frame validation failed: ${validation.errors.join(\", \")}`,\n );\n }\n const layout: BitmapView = {\n layoutType: LayoutType.BITMAP_VIEW,\n data: bitmapFrame,\n };\n this.sendMessage(this.createDisplayEvent(layout, options?.view));\n }\n\n /**\n * 📊 Shows a dashboard card with left and right text\n *\n * Best for:\n * - Key-value pairs\n * - Dashboard displays\n * - Metrics\n *\n * @param leftText - Left side text (typically label/key)\n * @param rightText - Right side text (typically value)\n * @param options - Optional parameters (view, duration)\n *\n * @example\n * ```typescript\n * layouts.showDashboardCard('Weather', '72°F');\n * ```\n */\n showDashboardCard(\n leftText: string,\n rightText: string,\n options?: { view?: ViewType; durationMs?: number },\n ) {\n const layout: DashboardCard = {\n layoutType: LayoutType.DASHBOARD_CARD,\n leftText,\n rightText,\n };\n this.sendMessage(\n this.createDisplayEvent(\n layout,\n options?.view || ViewType.DASHBOARD,\n options?.durationMs,\n ),\n );\n }\n\n /**\n * 🧹 Clears the display\n *\n * Best for:\n * - Clearing previous content\n * - Resetting display state\n * - Starting fresh\n *\n * @param options - Optional parameters (view)\n *\n * @example\n * ```typescript\n * layouts.clearView();\n * layouts.clearView({ view: ViewType.DASHBOARD });\n * ```\n */\n clearView(options?: { view?: ViewType }) {\n const layout: ClearView = {\n layoutType: LayoutType.CLEAR_VIEW,\n };\n this.sendMessage(this.createDisplayEvent(layout, options?.view));\n }\n\n /**\n * 🎬 Shows an animated sequence of bitmap images (iOS-controlled timing)\n *\n * Sends complete animation package to iOS for device-controlled timing.\n * This provides superior performance and synchronization by letting\n * the device control the display timing directly.\n *\n * Best for:\n * - Smooth high-performance animations\n * - Precise timing control\n * - Synchronized left/right display\n * - EvenDemo-quality performance\n *\n * @param bitmapDataArray - Array of bitmap data strings (hex or base64 encoded)\n * @param intervalMs - Time between frames in milliseconds (default: 1650ms)\n * @param repeat - Whether to loop the animation continuously (default: false)\n * @param options - Optional parameters (view)\n *\n * @example\n * ```typescript\n * // Device-controlled animation (recommended)\n * const frames = ['hexdata1', 'hexdata2', 'hexdata3'];\n * layouts.showBitmapAnimation(frames, 1650, true);\n *\n * // Single-cycle animation\n * layouts.showBitmapAnimation(loadingFrames, 1650, false);\n * ```\n *\n * @returns Animation controller object with stop() method\n */\n showBitmapAnimation(\n bitmapDataArray: string[],\n intervalMs: number = 1650,\n repeat: boolean = false,\n options?: { view?: ViewType },\n ): { stop: () => void } {\n // Validation\n if (!Array.isArray(bitmapDataArray) || bitmapDataArray.length === 0) {\n throw new Error(\n \"showBitmapAnimation requires a non-empty array of bitmap data\",\n );\n }\n\n // Send complete animation package to iOS for device-controlled timing\n const layout: BitmapAnimation = {\n layoutType: LayoutType.BITMAP_ANIMATION,\n frames: bitmapDataArray,\n interval: intervalMs,\n repeat: repeat,\n };\n\n this.sendMessage(this.createDisplayEvent(layout, options?.view));\n\n console.log(\n `🎬 Sent batched animation to iOS: ${\n bitmapDataArray.length\n } frames at ${intervalMs}ms${repeat ? \" (repeating)\" : \"\"}`,\n );\n\n // Return controller for compatibility\n return {\n stop: () => {\n // Send stop command to iOS\n this.clearView();\n console.log(\"🛑 Animation stop requested\");\n },\n };\n }\n}\n",
28
- "/**\n * 🔧 Settings Manager Module\n *\n * Manages App settings with automatic synchronization and change notifications.\n * Provides type-safe access to settings with default values.\n */\nimport EventEmitter from \"events\";\nimport { AppSetting, AppSettings } from \"../../types\";\nimport { ApiClient } from \"./api-client\";\nimport { logger } from \"../../logging/logger\"; // Adjust import path as needed\n// Note(Isaiah): Let's not import @mentra/utils in the SDK to avoid circular dependencies. Also i'm deprecating it in favor of the new logging system.\n\n/**\n * Change information for a single setting\n */\nexport interface SettingChange {\n oldValue: any;\n newValue: any;\n}\n\n/**\n * Map of setting keys to their change information\n */\nexport type SettingsChangeMap = Record<string, SettingChange>;\n\n/**\n * Callback for when any setting changes\n */\nexport type SettingsChangeHandler = (changes: SettingsChangeMap) => void;\n\n/**\n * Callback for when a specific setting changes\n */\nexport type SettingValueChangeHandler<T = any> = (\n newValue: T,\n oldValue: T,\n) => void;\n\n/**\n * Internal event names\n */\nenum SettingsEvents {\n CHANGE = \"settings:change\",\n VALUE_CHANGE = \"settings:value:\",\n}\n\n/**\n * 🔧 Settings Manager\n *\n * Provides a type-safe interface for accessing and reacting to App settings.\n * Automatically synchronizes with MentraOS Cloud.\n */\nexport class SettingsManager {\n // Current settings values\n private settings: AppSettings = [];\n\n // Event emitter for change notifications\n private emitter = new EventEmitter();\n\n // API client for fetching settings\n private apiClient?: ApiClient;\n\n // --- MentraOS settings event system ---\n private mentraosSettings: Record<string, any> = {};\n private mentraosEmitter = new EventEmitter();\n private subscribeFn?: (streams: string[]) => Promise<void>; // Added for auto-subscriptions\n\n /**\n * Create a new settings manager\n *\n * @param initialSettings Initial settings values (if available)\n * @param packageName Package name for the App\n * @param wsUrl WebSocket URL (for deriving HTTP API URL)\n * @param userId User ID (for authenticated requests)\n * @param subscribeFn Optional function to call to subscribe to streams\n */\n constructor(\n initialSettings: AppSettings = [],\n packageName?: string,\n wsUrl?: string,\n userId?: string,\n subscribeFn?: (streams: string[]) => Promise<void>, // Added parameter\n ) {\n this.settings = [...initialSettings];\n this.subscribeFn = subscribeFn; // Store the subscribe function\n\n // Create API client if we have enough information\n if (packageName) {\n this.apiClient = new ApiClient(packageName, wsUrl, userId);\n }\n }\n\n /**\n * Configure the API client\n *\n * @param packageName Package name for the App\n * @param wsUrl WebSocket URL\n * @param userId User ID\n */\n configureApiClient(packageName: string, wsUrl: string, userId: string): void {\n if (!this.apiClient) {\n this.apiClient = new ApiClient(packageName, wsUrl, userId);\n } else {\n this.apiClient.setWebSocketUrl(wsUrl);\n this.apiClient.setUserId(userId);\n }\n }\n\n /**\n * Update the current settings\n * This is called internally when settings are loaded or changed\n *\n * @param newSettings New settings values\n * @returns Map of changed settings\n */\n updateSettings(newSettings: AppSettings): SettingsChangeMap {\n const changes: SettingsChangeMap = {};\n\n // Copy the new settings\n const updatedSettings = [...newSettings];\n\n // Detect changes comparing old and new settings\n for (const newSetting of updatedSettings) {\n const oldSetting = this.settings.find((s) => s.key === newSetting.key);\n\n // Skip if value hasn't changed\n if (oldSetting && this.areEqual(oldSetting.value, newSetting.value)) {\n continue;\n }\n\n // Record change\n changes[newSetting.key] = {\n oldValue: oldSetting?.value,\n newValue: newSetting.value,\n };\n }\n\n // Check for removed settings\n for (const oldSetting of this.settings) {\n const stillExists = updatedSettings.some((s) => s.key === oldSetting.key);\n\n if (!stillExists) {\n changes[oldSetting.key] = {\n oldValue: oldSetting.value,\n newValue: undefined,\n };\n }\n }\n\n // If there are changes, update the settings and emit events\n if (Object.keys(changes).length > 0) {\n this.settings = updatedSettings;\n this.emitChanges(changes);\n }\n\n return changes;\n }\n\n /**\n * Check if two setting values are equal\n *\n * @param a First value\n * @param b Second value\n * @returns True if the values are equal\n */\n private areEqual(a: any, b: any): boolean {\n // Simple equality check - for objects, this won't do a deep equality check\n // but for most setting values (strings, numbers, booleans) it works\n return a === b;\n }\n\n /**\n * Emit change events for updated settings\n *\n * @param changes Map of changed settings\n */\n private emitChanges(changes: SettingsChangeMap): void {\n // Emit the general change event\n this.emitter.emit(SettingsEvents.CHANGE, changes);\n\n // Emit individual value change events\n for (const [key, change] of Object.entries(changes)) {\n this.emitter.emit(\n `${SettingsEvents.VALUE_CHANGE}${key}`,\n change.newValue,\n change.oldValue,\n );\n }\n }\n\n /**\n * 🔄 Listen for changes to any setting\n *\n * @param handler Function to call when settings change\n * @returns Function to remove the listener\n *\n * @example\n * ```typescript\n * settings.onChange((changes) => {\n * console.log('Settings changed:', changes);\n * });\n * ```\n */\n onChange(handler: SettingsChangeHandler): () => void {\n this.emitter.on(SettingsEvents.CHANGE, handler);\n return () => this.emitter.off(SettingsEvents.CHANGE, handler);\n }\n\n /**\n * 🔄 Listen for changes to a specific setting\n *\n * @param key Setting key to monitor\n * @param handler Function to call when the setting changes\n * @returns Function to remove the listener\n *\n * @example\n * ```typescript\n * settings.onValueChange('transcribe_language', (newValue, oldValue) => {\n * console.log(`Language changed from ${oldValue} to ${newValue}`);\n * });\n * ```\n */\n onValueChange<T = any>(\n key: string,\n handler: SettingValueChangeHandler<T>,\n ): () => void {\n const eventName = `${SettingsEvents.VALUE_CHANGE}${key}`;\n this.emitter.on(eventName, handler);\n return () => this.emitter.off(eventName, handler);\n }\n\n /**\n * 🔍 Check if a setting exists\n *\n * @param key Setting key to check\n * @returns True if the setting exists\n */\n has(key: string): boolean {\n return this.settings.some((s) => s.key === key);\n }\n\n /**\n * 🔍 Get all settings\n *\n * @returns Copy of all settings\n */\n getAll(): AppSettings {\n return [...this.settings];\n }\n\n /**\n * 🔍 Get a setting value with type safety\n *\n * @param key Setting key to get\n * @param defaultValue Default value if setting doesn't exist or is undefined\n * @returns Setting value or default value\n *\n * @example\n * ```typescript\n * const lineWidth = settings.get<number>('line_width', 30);\n * const language = settings.get<string>('transcribe_language', 'English');\n * ```\n */\n get<T = any>(key: string, defaultValue?: T): T {\n const setting = this.settings.find((s) => s.key === key);\n\n if (setting && setting.value !== undefined) {\n return setting.value as T;\n }\n\n return defaultValue as T;\n }\n\n /**\n * 🎛️ Get an MentraOS system setting value with optional default\n *\n * @param key MentraOS setting key (e.g., 'metricSystemEnabled', 'brightness')\n * @param defaultValue Default value to return if the setting is not found\n * @returns The setting value or the default value\n *\n * @example\n * ```typescript\n * const isMetric = settings.getMentraOS<boolean>('metricSystemEnabled', false);\n * const brightness = settings.getMentraOS<number>('brightness', 50);\n * ```\n */\n getMentraOS<T = any>(key: string, defaultValue?: T): T {\n const value = this.mentraosSettings[key];\n\n if (value !== undefined) {\n return value as T;\n }\n\n return defaultValue as T;\n }\n\n /**\n * 🔍 Find a setting by key\n *\n * @param key Setting key to find\n * @returns Setting object or undefined\n */\n getSetting(key: string): AppSetting | undefined {\n return this.settings.find((s) => s.key === key);\n }\n\n /**\n * 🔄 Fetch settings from the cloud\n * This is generally not needed since settings are automatically kept in sync,\n * but can be used to force a refresh if needed.\n *\n * @returns Promise that resolves to the updated settings\n * @throws Error if the API client is not configured or the request fails\n */\n async fetch(): Promise<AppSettings> {\n if (!this.apiClient) {\n throw new Error(\"Settings API client is not configured\");\n }\n\n try {\n const newSettings = await this.apiClient.fetchSettings();\n this.updateSettings(newSettings);\n return this.settings;\n } catch (error) {\n console.error(\"Error fetching settings:\", error);\n throw error;\n }\n }\n\n /**\n * 🎛️ Listen for changes to a specific MentraOS setting (e.g., metricSystemEnabled)\n *\n * @param key The mentraosSettings key to listen for (e.g., 'metricSystemEnabled')\n * @param handler Function to call when the value changes\n * @returns Function to remove the listener\n *\n * @example\n * ```typescript\n * settings.onMentraosChange('metricSystemEnabled', (isMetric, wasMetric) => {\n * console.log(`Units changed: ${wasMetric ? 'metric' : 'imperial'} → ${isMetric ? 'metric' : 'imperial'}`);\n * });\n * ```\n */\n onMentraosChange<T = any>(\n key: string,\n handler: SettingValueChangeHandler<T>,\n ): () => void {\n return this.onMentraosSettingChange(key, handler);\n }\n\n /**\n * Listen for changes to a specific MentraOS setting (e.g., metricSystemEnabled)\n * This is a convenience wrapper for onValueChange for well-known mentraosSettings keys.\n * @param key The mentraosSettings key to listen for (e.g., 'metricSystemEnabled')\n * @param handler Function to call when the value changes\n * @returns Function to remove the listener\n * @deprecated Use onMentraosChange instead\n */\n onMentraosSettingsChange<T = any>(\n key: string,\n handler: SettingValueChangeHandler<T>,\n ): () => void {\n return this.onMentraosSettingChange(key, handler);\n }\n\n /**\n * Update the current MentraOS settings\n * Compares new and old values, emits per-key events, and updates stored values.\n * @param newSettings The new MentraOS settings object\n */\n updateMentraosSettings(newSettings: Record<string, any>): void {\n const oldSettings = this.mentraosSettings;\n logger.debug(\n { newSettings },\n `[SettingsManager] Updating MentraOS settings. New settings`,\n );\n for (const key of Object.keys(newSettings)) {\n const oldValue = oldSettings[key];\n const newValue = newSettings[key];\n if (oldValue !== newValue) {\n logger.info(\n `[SettingsManager] MentraOS setting '${key}' changed: ${oldValue} -> ${newValue}. Emitting event.`,\n );\n this.mentraosEmitter.emit(`augmentos:value:${key}`, newValue, oldValue);\n }\n }\n // Also handle keys that might have been removed from newSettings but existed in oldSettings\n for (const key of Object.keys(oldSettings)) {\n if (!(key in newSettings)) {\n logger.info(\n `[SettingsManager] MentraOS setting '${key}' removed. Old value: ${oldSettings[key]}. Emitting event with undefined newValue.`,\n );\n this.mentraosEmitter.emit(\n `augmentos:value:${key}`,\n undefined,\n oldSettings[key],\n );\n }\n }\n this.mentraosSettings = { ...newSettings };\n logger.debug(\n { mentraosSettings: this.mentraosSettings },\n `[SettingsManager] Finished updating MentraOS settings. Current state:`,\n );\n }\n\n /**\n * Subscribe to changes for a specific MentraOS setting (e.g., 'metricSystemEnabled')\n * @param key The MentraOS setting key to listen for\n * @param handler Function to call when the value changes (newValue, oldValue)\n * @returns Function to remove the listener\n */\n onMentraosSettingChange<T = any>(\n key: string,\n handler: (newValue: T, oldValue: T) => void,\n ): () => void {\n const eventName = `augmentos:value:${key}`;\n logger.info(\n `[SettingsManager] Registering handler for MentraOS setting '${key}' on event '${eventName}'.`,\n );\n this.mentraosEmitter.on(eventName, (...args) => {\n logger.info(\n { args },\n `[SettingsManager] MentraOS setting '${key}' event fired. Args:`,\n );\n handler(...(args as [T, T]));\n });\n\n if (this.subscribeFn) {\n const subscriptionKey = `augmentos:${key}`;\n logger.info(\n `[SettingsManager] Calling subscribeFn for stream '${subscriptionKey}'.`,\n );\n this.subscribeFn([subscriptionKey])\n .then(() => {\n logger.info(\n `[SettingsManager] subscribeFn resolved for stream '${subscriptionKey}'.`,\n );\n })\n .catch((err) => {\n logger.error(\n `[SettingsManager] subscribeFn failed for stream '${subscriptionKey}':`,\n err,\n );\n });\n } else {\n logger.warn(\n `[SettingsManager] 'subscribeFn' not provided. Cannot auto-subscribe for MentraOS setting '${key}'. Manual App subscription might be required.`,\n );\n }\n\n return () => {\n logger.info(\n `[SettingsManager] Unregistering handler for MentraOS setting '${key}' from event '${eventName}'.`,\n );\n this.mentraosEmitter.off(\n eventName,\n handler as (newValue: unknown, oldValue: unknown) => void,\n );\n };\n }\n\n /**\n * Get the current value of an MentraOS setting\n */\n getMentraosSetting<T = any>(key: string, defaultValue?: T): T {\n console.log(\n `[SettingsManager] Getting MentraOS setting '${key}' with settings:`,\n this.mentraosSettings,\n );\n if (key in this.mentraosSettings) {\n return this.mentraosSettings[key] as T;\n }\n return defaultValue as T;\n }\n}\n",
52
+ "/**\n * 🔧 Settings Manager Module\n *\n * Manages App settings with automatic synchronization and change notifications.\n * Provides type-safe access to settings with default values.\n */\nimport EventEmitter from \"events\";\nimport type { Logger } from \"pino\";\nimport { AppSetting, AppSettings } from \"../../types\";\nimport { ApiClient } from \"./api-client\";\n\n/**\n * Change information for a single setting\n */\nexport interface SettingChange {\n oldValue: any;\n newValue: any;\n}\n\n/**\n * Map of setting keys to their change information\n */\nexport type SettingsChangeMap = Record<string, SettingChange>;\n\n/**\n * Callback for when any setting changes\n */\nexport type SettingsChangeHandler = (changes: SettingsChangeMap) => void;\n\n/**\n * Callback for when a specific setting changes\n */\nexport type SettingValueChangeHandler<T = any> = (newValue: T, oldValue: T) => void;\n\n/**\n * Internal event names\n */\nenum SettingsEvents {\n CHANGE = \"settings:change\",\n VALUE_CHANGE = \"settings:value:\",\n}\n\n/**\n * 🔧 Settings Manager\n *\n * Provides a type-safe interface for accessing and reacting to App settings.\n * Automatically synchronizes with MentraOS Cloud.\n */\nexport class SettingsManager {\n // Current settings values\n private settings: AppSettings = [];\n\n // Event emitter for change notifications\n private emitter = new EventEmitter();\n\n // API client for fetching settings\n private apiClient?: ApiClient;\n\n // Logger instance (injected via constructor for proper log level respect)\n private logger: Logger;\n\n // --- MentraOS settings event system ---\n private mentraosSettings: Record<string, any> = {};\n private mentraosEmitter = new EventEmitter();\n private subscribeFn?: (streams: string[]) => Promise<void>; // Added for auto-subscriptions\n\n /**\n * Create a new settings manager\n *\n * @param initialSettings Initial settings values (if available)\n * @param packageName Package name for the App\n * @param wsUrl WebSocket URL (for deriving HTTP API URL)\n * @param userId User ID (for authenticated requests)\n * @param subscribeFn Optional function to call to subscribe to streams\n * @param logger Logger instance from the parent session\n */\n constructor(\n initialSettings: AppSettings = [],\n packageName?: string,\n wsUrl?: string,\n userId?: string,\n subscribeFn?: (streams: string[]) => Promise<void>, // Added parameter\n logger?: Logger,\n ) {\n this.settings = [...initialSettings];\n this.subscribeFn = subscribeFn; // Store the subscribe function\n\n // Use provided logger or create a minimal no-op fallback\n if (logger) {\n this.logger = logger.child({ module: \"settings\" });\n } else {\n // Fallback: import the default logger (backward compat during migration)\n const { logger: defaultLogger } = require(\"../../logging/logger\");\n this.logger = defaultLogger.child({ module: \"settings\" });\n }\n\n // Create API client if we have enough information\n if (packageName) {\n this.apiClient = new ApiClient(packageName, wsUrl, userId);\n }\n }\n\n /**\n * Configure the API client\n *\n * @param packageName Package name for the App\n * @param wsUrl WebSocket URL\n * @param userId User ID\n */\n configureApiClient(packageName: string, wsUrl: string, userId: string): void {\n if (!this.apiClient) {\n this.apiClient = new ApiClient(packageName, wsUrl, userId);\n } else {\n this.apiClient.setWebSocketUrl(wsUrl);\n this.apiClient.setUserId(userId);\n }\n }\n\n /**\n * Update the current settings\n * This is called internally when settings are loaded or changed\n *\n * @param newSettings New settings values\n * @returns Map of changed settings\n */\n updateSettings(newSettings: AppSettings): SettingsChangeMap {\n const changes: SettingsChangeMap = {};\n\n // Copy the new settings\n const updatedSettings = [...newSettings];\n\n // Detect changes comparing old and new settings\n for (const newSetting of updatedSettings) {\n const oldSetting = this.settings.find((s) => s.key === newSetting.key);\n\n // Skip if value hasn't changed\n if (oldSetting && this.areEqual(oldSetting.value, newSetting.value)) {\n continue;\n }\n\n // Record change\n changes[newSetting.key] = {\n oldValue: oldSetting?.value,\n newValue: newSetting.value,\n };\n }\n\n // Check for removed settings\n for (const oldSetting of this.settings) {\n const stillExists = updatedSettings.some((s) => s.key === oldSetting.key);\n\n if (!stillExists) {\n changes[oldSetting.key] = {\n oldValue: oldSetting.value,\n newValue: undefined,\n };\n }\n }\n\n // If there are changes, update the settings and emit events\n if (Object.keys(changes).length > 0) {\n this.settings = updatedSettings;\n this.emitChanges(changes);\n }\n\n return changes;\n }\n\n /**\n * Check if two setting values are equal\n *\n * @param a First value\n * @param b Second value\n * @returns True if the values are equal\n */\n private areEqual(a: any, b: any): boolean {\n // Simple equality check - for objects, this won't do a deep equality check\n // but for most setting values (strings, numbers, booleans) it works\n return a === b;\n }\n\n /**\n * Emit change events for updated settings\n *\n * @param changes Map of changed settings\n */\n private emitChanges(changes: SettingsChangeMap): void {\n // Emit the general change event\n this.emitter.emit(SettingsEvents.CHANGE, changes);\n\n // Emit individual value change events\n for (const [key, change] of Object.entries(changes)) {\n this.emitter.emit(`${SettingsEvents.VALUE_CHANGE}${key}`, change.newValue, change.oldValue);\n }\n }\n\n /**\n * 🔄 Listen for changes to any setting\n *\n * @param handler Function to call when settings change\n * @returns Function to remove the listener\n *\n * @example\n * ```typescript\n * settings.onChange((changes) => {\n * console.log('Settings changed:', changes);\n * });\n * ```\n */\n onChange(handler: SettingsChangeHandler): () => void {\n this.emitter.on(SettingsEvents.CHANGE, handler);\n return () => this.emitter.off(SettingsEvents.CHANGE, handler);\n }\n\n /**\n * 🔄 Listen for changes to a specific setting\n *\n * @param key Setting key to monitor\n * @param handler Function to call when the setting changes\n * @returns Function to remove the listener\n *\n * @example\n * ```typescript\n * settings.onValueChange('transcribe_language', (newValue, oldValue) => {\n * console.log(`Language changed from ${oldValue} to ${newValue}`);\n * });\n * ```\n */\n onValueChange<T = any>(key: string, handler: SettingValueChangeHandler<T>): () => void {\n const eventName = `${SettingsEvents.VALUE_CHANGE}${key}`;\n this.emitter.on(eventName, handler);\n return () => this.emitter.off(eventName, handler);\n }\n\n /**\n * 🔍 Check if a setting exists\n *\n * @param key Setting key to check\n * @returns True if the setting exists\n */\n has(key: string): boolean {\n return this.settings.some((s) => s.key === key);\n }\n\n /**\n * 🔍 Get all settings\n *\n * @returns Copy of all settings\n */\n getAll(): AppSettings {\n return [...this.settings];\n }\n\n /**\n * 🔍 Get a setting value with type safety\n *\n * @param key Setting key to get\n * @param defaultValue Default value if setting doesn't exist or is undefined\n * @returns Setting value or default value\n *\n * @example\n * ```typescript\n * const lineWidth = settings.get<number>('line_width', 30);\n * const language = settings.get<string>('transcribe_language', 'English');\n * ```\n */\n get<T = any>(key: string, defaultValue?: T): T {\n const setting = this.settings.find((s) => s.key === key);\n\n if (setting && setting.value !== undefined) {\n return setting.value as T;\n }\n\n return defaultValue as T;\n }\n\n /**\n * 🎛️ Get an MentraOS system setting value with optional default\n *\n * @param key MentraOS setting key (e.g., 'metricSystemEnabled', 'brightness')\n * @param defaultValue Default value to return if the setting is not found\n * @returns The setting value or the default value\n *\n * @example\n * ```typescript\n * const isMetric = settings.getMentraOS<boolean>('metricSystemEnabled', false);\n * const brightness = settings.getMentraOS<number>('brightness', 50);\n * ```\n */\n getMentraOS<T = any>(key: string, defaultValue?: T): T {\n const value = this.mentraosSettings[key];\n\n if (value !== undefined) {\n return value as T;\n }\n\n return defaultValue as T;\n }\n\n /**\n * 🔍 Find a setting by key\n *\n * @param key Setting key to find\n * @returns Setting object or undefined\n */\n getSetting(key: string): AppSetting | undefined {\n return this.settings.find((s) => s.key === key);\n }\n\n /**\n * 🔄 Fetch settings from the cloud\n * This is generally not needed since settings are automatically kept in sync,\n * but can be used to force a refresh if needed.\n *\n * @returns Promise that resolves to the updated settings\n * @throws Error if the API client is not configured or the request fails\n */\n async fetch(): Promise<AppSettings> {\n if (!this.apiClient) {\n throw new Error(\"Settings API client is not configured\");\n }\n\n try {\n const newSettings = await this.apiClient.fetchSettings();\n this.updateSettings(newSettings);\n return this.settings;\n } catch (error) {\n this.logger.error({ error }, \"Error fetching settings\");\n throw error;\n }\n }\n\n /**\n * 🎛️ Listen for changes to a specific MentraOS setting (e.g., metricSystemEnabled)\n *\n * @param key The mentraosSettings key to listen for (e.g., 'metricSystemEnabled')\n * @param handler Function to call when the value changes\n * @returns Function to remove the listener\n *\n * @example\n * ```typescript\n * settings.onMentraosChange('metricSystemEnabled', (isMetric, wasMetric) => {\n * console.log(`Units changed: ${wasMetric ? 'metric' : 'imperial'} → ${isMetric ? 'metric' : 'imperial'}`);\n * });\n * ```\n */\n onMentraosChange<T = any>(key: string, handler: SettingValueChangeHandler<T>): () => void {\n return this.onMentraosSettingChange(key, handler);\n }\n\n /**\n * Listen for changes to a specific MentraOS setting (e.g., metricSystemEnabled)\n * This is a convenience wrapper for onValueChange for well-known mentraosSettings keys.\n * @param key The mentraosSettings key to listen for (e.g., 'metricSystemEnabled')\n * @param handler Function to call when the value changes\n * @returns Function to remove the listener\n * @deprecated Use onMentraosChange instead\n */\n onMentraosSettingsChange<T = any>(key: string, handler: SettingValueChangeHandler<T>): () => void {\n return this.onMentraosSettingChange(key, handler);\n }\n\n /**\n * Update the current MentraOS settings\n * Compares new and old values, emits per-key events, and updates stored values.\n * @param newSettings The new MentraOS settings object\n */\n updateMentraosSettings(newSettings: Record<string, any>): void {\n const oldSettings = this.mentraosSettings;\n this.logger.debug({ newSettings }, `[SettingsManager] Updating MentraOS settings. New settings`);\n for (const key of Object.keys(newSettings)) {\n const oldValue = oldSettings[key];\n const newValue = newSettings[key];\n if (oldValue !== newValue) {\n this.logger.debug(\n `[SettingsManager] MentraOS setting '${key}' changed: ${oldValue} -> ${newValue}. Emitting event.`,\n );\n this.mentraosEmitter.emit(`augmentos:value:${key}`, newValue, oldValue);\n }\n }\n // Also handle keys that might have been removed from newSettings but existed in oldSettings\n for (const key of Object.keys(oldSettings)) {\n if (!(key in newSettings)) {\n this.logger.debug(\n `[SettingsManager] MentraOS setting '${key}' removed. Old value: ${oldSettings[key]}. Emitting event with undefined newValue.`,\n );\n this.mentraosEmitter.emit(`augmentos:value:${key}`, undefined, oldSettings[key]);\n }\n }\n this.mentraosSettings = { ...newSettings };\n this.logger.debug(\n { mentraosSettings: this.mentraosSettings },\n `[SettingsManager] Finished updating MentraOS settings. Current state:`,\n );\n }\n\n /**\n * Subscribe to changes for a specific MentraOS setting (e.g., 'metricSystemEnabled')\n * @param key The MentraOS setting key to listen for\n * @param handler Function to call when the value changes (newValue, oldValue)\n * @returns Function to remove the listener\n */\n onMentraosSettingChange<T = any>(key: string, handler: (newValue: T, oldValue: T) => void): () => void {\n const eventName = `augmentos:value:${key}`;\n this.logger.debug(`[SettingsManager] Registering handler for MentraOS setting '${key}' on event '${eventName}'.`);\n this.mentraosEmitter.on(eventName, (...args) => {\n this.logger.debug({ args }, `[SettingsManager] MentraOS setting '${key}' event fired. Args:`);\n handler(...(args as [T, T]));\n });\n\n if (this.subscribeFn) {\n const subscriptionKey = `augmentos:${key}`;\n this.logger.debug(`[SettingsManager] Calling subscribeFn for stream '${subscriptionKey}'.`);\n this.subscribeFn([subscriptionKey])\n .then(() => {\n this.logger.debug(`[SettingsManager] subscribeFn resolved for stream '${subscriptionKey}'.`);\n })\n .catch((err) => {\n this.logger.error(`[SettingsManager] subscribeFn failed for stream '${subscriptionKey}':`, err);\n });\n } else {\n this.logger.warn(\n `[SettingsManager] 'subscribeFn' not provided. Cannot auto-subscribe for MentraOS setting '${key}'. Manual App subscription might be required.`,\n );\n }\n\n return () => {\n this.logger.debug(\n `[SettingsManager] Unregistering handler for MentraOS setting '${key}' from event '${eventName}'.`,\n );\n this.mentraosEmitter.off(eventName, handler as (newValue: unknown, oldValue: unknown) => void);\n };\n }\n\n /**\n * Get the current value of an MentraOS setting\n */\n getMentraosSetting<T = any>(key: string, defaultValue?: T): T {\n this.logger.debug({ key, mentraosSettings: this.mentraosSettings }, `Getting MentraOS setting '${key}'`);\n if (key in this.mentraosSettings) {\n return this.mentraosSettings[key] as T;\n }\n return defaultValue as T;\n }\n}\n",
29
53
  "/**\n * 🔌 API Client Module\n *\n * Provides HTTP API access to MentraOS Cloud services.\n * Automatically uses the correct server URL derived from the WebSocket URL.\n */\n\n/**\n * Convert a WebSocket URL to a HTTP/HTTPS URL\n *\n * @param wsUrl WebSocket URL to convert\n * @returns HTTP URL equivalent\n */\nexport function wsUrlToHttpUrl(wsUrl?: string): string | undefined {\n if (!wsUrl) return undefined;\n\n try {\n // Parse the WebSocket URL\n const url = new URL(wsUrl);\n\n // Change protocol from ws/wss to http/https\n const protocol = url.protocol === \"wss:\" ? \"https:\" : \"http:\";\n\n // Recreate the URL with the new protocol\n return `${protocol}//${url.host}`;\n } catch (error) {\n console.error(\"Error converting WebSocket URL to HTTP URL:\", error);\n return undefined;\n }\n}\n\n/**\n * API client class for making HTTP requests to MentraOS Cloud\n */\nexport class ApiClient {\n private baseUrl: string | undefined;\n private packageName: string;\n private userId: string | undefined;\n\n /**\n * Create a new API client\n *\n * @param packageName App package name\n * @param wsUrl WebSocket URL (optional, can be set later)\n * @param userId User ID (optional, for authenticated requests)\n */\n constructor(packageName: string, wsUrl?: string, userId?: string) {\n this.packageName = packageName;\n this.userId = userId;\n\n if (wsUrl) {\n this.baseUrl = wsUrlToHttpUrl(wsUrl);\n }\n }\n\n /**\n * Set the WebSocket URL to derive the HTTP base URL\n *\n * @param wsUrl WebSocket URL\n */\n setWebSocketUrl(wsUrl: string): void {\n this.baseUrl = wsUrlToHttpUrl(wsUrl);\n }\n\n /**\n * Set the user ID for authenticated requests\n *\n * @param userId User ID\n */\n setUserId(userId: string): void {\n this.userId = userId;\n }\n\n /**\n * Fetch settings from MentraOS Cloud\n *\n * @returns Promise resolving to settings array\n * @throws Error if client is not configured correctly or if request fails\n */\n async fetchSettings(): Promise<any[]> {\n if (!this.baseUrl) {\n throw new Error(\"API client is not configured with a base URL\");\n }\n\n if (!this.userId) {\n throw new Error(\"User ID is required for fetching settings\");\n }\n\n const url = `${this.baseUrl}/appsettings/user/${this.packageName}`;\n\n try {\n const response = await fetch(url, {\n method: \"GET\",\n headers: {\n Authorization: `Bearer ${this.userId}`,\n \"Content-Type\": \"application/json\",\n },\n });\n\n if (!response.ok) {\n throw new Error(\n `Failed to fetch settings: ${response.status} ${response.statusText}`,\n );\n }\n\n const data = (await response.json()) as { settings: any[] };\n return data.settings || [];\n } catch (error) {\n console.error(\"Error fetching settings:\", error);\n throw error;\n }\n }\n}\n",
30
- "import pino from \"pino\"\n\n// Constants and configuration\nconst BETTERSTACK_SOURCE_TOKEN = process.env.BETTERSTACK_SOURCE_TOKEN\nconst BETTERSTACK_ENDPOINT = process.env.BETTERSTACK_ENDPOINT || \"https://s1311181.eu-nbg-2.betterstackdata.com\"\nconst NODE_ENV = process.env.NODE_ENV || \"development\"\nconst PORTER_APP_NAME = process.env.PORTER_APP_NAME || \"cloud-local\"\n\n// Determine log level based on environment\nconst LOG_LEVEL = NODE_ENV === \"production\" ? \"info\" : \"debug\"\n\n// Setup streams array for Pino multistream\nconst streams: pino.StreamEntry[] = []\n\n// Use pretty print in development for better readability\n// if (PRETTY_PRINT && NODE_ENV !== 'production') {\n// Pretty transport for development\nconst prettyTransport = pino.transport({\n target: \"pino-pretty\",\n options: {\n colorize: true,\n translateTime: \"SYS:standard\",\n ignore: \"pid,hostname,env,module,server\",\n messageFormat: \"{msg}\",\n errorProps: \"*\",\n customPrettifiers: {\n // Add custom prettifiers here if needed\n },\n },\n})\n\nstreams.push({\n stream: prettyTransport,\n level: LOG_LEVEL,\n})\n// } else {\n// // Plain console in production (JSON format)\n// streams.push({\n// stream: process.stdout,\n// level: LOG_LEVEL,\n// });\n// }\n\n// Add BetterStack transport if token is provided\nif (BETTERSTACK_SOURCE_TOKEN) {\n try {\n const betterStackTransport = pino.transport({\n target: \"@logtail/pino\",\n options: {\n sourceToken: BETTERSTACK_SOURCE_TOKEN,\n options: {endpoint: BETTERSTACK_ENDPOINT},\n },\n })\n\n streams.push({\n stream: betterStackTransport,\n level: LOG_LEVEL,\n })\n } catch (error) {\n console.warn(\"BetterStack logging unavailable:\", error instanceof Error ? error.message : error)\n }\n}\n\n// Create multistream\nconst multistream = pino.multistream(streams)\n\n/**\n * Configuration for the root logger\n */\nconst baseLoggerOptions: pino.LoggerOptions = {\n level: LOG_LEVEL,\n base: {\n env: NODE_ENV,\n server: PORTER_APP_NAME,\n },\n timestamp: pino.stdTimeFunctions.isoTime,\n}\n\n// Create the root logger with multiple streams\nexport const logger = pino(baseLoggerOptions, multistream)\n\n// Flush logger on process exit\n// process.on('beforeExit', async () => {\n// logger.flush(); // Flush the root logger\n// console.log('Logger flushed before exit');\n// });\n\n// Default export is the logger\nexport default logger\n",
31
- "import {locationWarnLog} from \"../../../utils/permissions-utils\"\n\nimport {AppSession} from \"..\"\nimport {AppToCloudMessageType, LocationUpdate, LocationStreamRequest} from \"../../../types\"\n\nexport class LocationManager {\n private session: AppSession\n private lastLocationCleanupHandler: () => void = () => {}\n\n constructor(session: AppSession) {\n this.session = session\n }\n\n // subscribes to the continuous location stream with a specified accuracy tier\n public subscribeToStream(\n options: {\n accuracy:\n | \"standard\"\n | \"high\"\n | \"realtime\"\n | \"tenMeters\"\n | \"hundredMeters\"\n | \"kilometer\"\n | \"threeKilometers\"\n | \"reduced\"\n },\n handler: (data: LocationUpdate) => void,\n ): () => void {\n //Checking for permission location from dev console:\n locationWarnLog(this.session.getHttpsServerUrl() || \"\", this.session.getPackageName(), this.subscribeToStream.name)\n\n const subscription: LocationStreamRequest = {\n stream: \"location_stream\",\n rate: options.accuracy,\n }\n this.session.subscribe(subscription)\n this.lastLocationCleanupHandler = this.session.events.onLocation(handler)\n return this.lastLocationCleanupHandler\n }\n\n // unsubscribes from the continuous location stream\n public unsubscribeFromStream(): void {\n if (this.lastLocationCleanupHandler) {\n this.lastLocationCleanupHandler()\n this.lastLocationCleanupHandler = () => {}\n } else {\n this.session.unsubscribe(\"location_stream\")\n }\n }\n\n // performs a one-time, intelligent poll for a location fix\n public async getLatestLocation(options: {accuracy: string}): Promise<LocationUpdate> {\n return new Promise((resolve, reject) => {\n const requestId = `poll_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`\n\n // listens for a location update with a matching correlationId\n const unsubscribe = this.session.events.on(\"location_update\", (data: LocationUpdate) => {\n if (data.correlationId === requestId) {\n unsubscribe() // clean up the listener\n resolve(data)\n }\n })\n\n // sends the poll request message to the cloud\n this.session.sendMessage({\n type: AppToCloudMessageType.LOCATION_POLL_REQUEST,\n correlationId: requestId,\n packageName: this.session.getPackageName(),\n sessionId: this.session.getSessionId(),\n accuracy: options.accuracy,\n })\n\n // sets a timeout to prevent the promise from hanging indefinitely\n setTimeout(() => {\n unsubscribe()\n reject(\"Location poll request timed out\")\n }, 15000) // 15 second timeout\n })\n }\n}\n",
32
- "/**\n * 📷 Camera Module\n *\n * Unified camera functionality for App Sessions.\n * Handles both photo requests and RTMP streaming from connected glasses.\n */\n\nimport {\n PhotoRequest,\n PhotoData,\n AppToCloudMessageType,\n RtmpStreamRequest,\n RtmpStreamStopRequest,\n RtmpStreamStatus,\n isRtmpStreamStatus,\n ManagedStreamStatus,\n StreamStatusCheckResponse,\n} from \"../../../types\";\nimport { VideoConfig, AudioConfig, StreamConfig, StreamStatusHandler } from \"../../../types/rtmp-stream\";\nimport { StreamType } from \"../../../types/streams\";\nimport { Logger } from \"pino\";\nimport { CameraManagedExtension, ManagedStreamOptions, ManagedStreamResult } from \"./camera-managed-extension\";\nimport { cameraWarnLog } from \"../../../utils/permissions-utils\";\n\n/**\n * Options for photo requests\n */\nexport interface PhotoRequestOptions {\n /** Whether to save the photo to the device gallery */\n saveToGallery?: boolean;\n /** Custom webhook URL to override the TPA's default webhookUrl */\n customWebhookUrl?: string;\n /** Authentication token for custom webhook authentication */\n authToken?: string;\n /**\n * Desired photo size. All sizes are optimized for fast transfer.\n * - small: 640x480 (VGA) - ultra-fast transfers\n * - medium: 1280x720 (720p) - good balance (default)\n * - large: 1920x1080 (1080p) - high quality\n * - full: native sensor resolution - maximum detail (slower transfer)\n */\n size?: \"small\" | \"medium\" | \"large\" | \"full\";\n /** Image compression level for upload optimization. Defaults to \"none\". */\n compress?: \"none\" | \"medium\" | \"heavy\";\n}\n\n/**\n * Configuration options for an RTMP stream\n */\nexport interface RtmpStreamOptions {\n /** The RTMP URL to stream to (e.g., rtmp://server.example.com/live/stream-key) */\n rtmpUrl: string;\n /** Optional video configuration settings */\n video?: VideoConfig;\n /** Optional audio configuration settings */\n audio?: AudioConfig;\n /** Optional stream configuration settings */\n stream?: StreamConfig;\n}\n\n/**\n * 📷 Camera Module Implementation\n *\n * Unified camera management for App Sessions.\n * Provides methods for:\n * - 📸 Requesting photos from glasses\n * - 📹 Starting/stopping RTMP streams\n * - 🔍 Monitoring photo and stream status\n * - 🧹 Cleanup and cancellation\n *\n * @example\n * ```typescript\n * // Request a photo\n * const photoData = await session.camera.requestPhoto({ saveToGallery: true });\n *\n * // Start streaming\n * await session.camera.startStream({ rtmpUrl: 'rtmp://example.com/live/key' });\n *\n * // Monitor stream status\n * session.camera.onStreamStatus((status) => {\n * console.log('Stream status:', status.status);\n * });\n *\n * // Stop streaming\n * await session.camera.stopStream();\n * ```\n */\nexport class CameraModule {\n private session: any; // Reference to AppSession\n private packageName: string;\n private sessionId: string;\n private logger: Logger;\n\n // Photo functionality\n /** Map to store pending photo request promises */\n private pendingPhotoRequests = new Map<\n string,\n {\n resolve: (value: PhotoData) => void;\n reject: (reason?: string) => void;\n }\n >();\n\n // Streaming functionality\n private isStreaming: boolean = false;\n private currentStreamUrl?: string;\n private currentStreamState?: RtmpStreamStatus;\n\n // Managed streaming extension\n private managedExtension: CameraManagedExtension;\n\n /**\n * Create a new CameraModule\n *\n * @param session - Reference to the parent AppSession\n * @param packageName - The App package name\n * @param sessionId - The current session ID\n * @param logger - Logger instance for debugging\n */\n constructor(session: any, packageName: string, sessionId: string, logger?: Logger) {\n this.session = session;\n this.packageName = packageName;\n this.sessionId = sessionId;\n this.logger = logger || (console as any);\n\n // Initialize managed extension\n this.managedExtension = new CameraManagedExtension(session, packageName, sessionId, this.logger);\n }\n\n // =====================================\n // 📸 Photo Functionality\n // =====================================\n\n /**\n * 📸 Request a photo from the connected glasses\n *\n * @param options - Optional configuration for the photo request\n * @returns Promise that resolves with the actual photo data\n *\n * @example\n * ```typescript\n * // Request a photo\n * const photo = await session.camera.requestPhoto();\n *\n * // Request a photo with custom webhook URL and authentication\n * const photo = await session.camera.requestPhoto({\n * customWebhookUrl: 'https://my-custom-endpoint.com/photo-upload',\n * authToken: 'your-auth-token-here'\n * });\n * ```\n */\n async requestPhoto(options?: PhotoRequestOptions): Promise<PhotoData> {\n return new Promise((resolve, reject) => {\n const baseUrl = this.session?.getHttpsServerUrl?.() || \"\";\n cameraWarnLog(baseUrl, this.packageName, \"requestPhoto\");\n try {\n console.log(\"DEBUG: requestPhoto options:\", options);\n\n // Generate unique request ID\n const requestId = `photo_req_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;\n\n // Store promise resolvers for when we get the response\n this.pendingPhotoRequests.set(requestId, { resolve, reject });\n\n // Also register with AppServer for persistence across session reconnections\n // This fixes race conditions where session disconnects during photo capture\n if (this.session?.appServer && this.session?.userId) {\n this.logger.info({ requestId, userId: this.session.userId }, \"📸 Registering photo request with AppServer\");\n this.session.appServer.registerPhotoRequest(requestId, this.session.userId, resolve, reject);\n } else {\n this.logger.warn(\n {\n requestId,\n hasAppServer: !!this.session?.appServer,\n hasUserId: !!this.session?.userId,\n },\n \"📸 Cannot register with AppServer - missing appServer or userId\",\n );\n }\n\n // Create photo request message\n const message: PhotoRequest = {\n type: AppToCloudMessageType.PHOTO_REQUEST,\n packageName: this.packageName,\n sessionId: this.sessionId,\n requestId,\n timestamp: new Date(),\n saveToGallery: options?.saveToGallery || false,\n customWebhookUrl: options?.customWebhookUrl,\n authToken: options?.authToken,\n size: options?.size || \"medium\",\n compress: options?.compress || \"none\",\n };\n\n // Send request to cloud\n this.session.sendMessage(message);\n\n this.logger.info(\n {\n requestId,\n saveToGallery: options?.saveToGallery,\n hasCustomWebhook: !!options?.customWebhookUrl,\n hasAuthToken: !!options?.authToken,\n },\n `📸 Photo request sent`,\n );\n\n // If using custom webhook URL, resolve immediately since photo will be uploaded directly to custom endpoint\n if (options?.customWebhookUrl) {\n this.logger.info(\n { requestId, customWebhookUrl: options.customWebhookUrl },\n `📸 Using custom webhook URL - resolving promise immediately since photo will be uploaded directly to custom endpoint`,\n );\n\n // Create a mock PhotoData object for custom webhook URLs\n const mockPhotoData: PhotoData = {\n buffer: Buffer.from([]), // Empty buffer since we don't have the actual photo\n mimeType: \"image/jpeg\",\n filename: \"photo.jpg\",\n requestId,\n size: 0,\n timestamp: new Date(),\n };\n\n // Resolve immediately and clean up\n this.pendingPhotoRequests.delete(requestId);\n resolve(mockPhotoData);\n return;\n }\n\n // Set timeout to avoid hanging promises (only for non-custom webhook requests)\n const timeoutMs = 30000; // 30 seconds\n if (this.session && this.session.resources) {\n // Use session's resource tracker for automatic cleanup\n this.session.resources.setTimeout(() => {\n if (this.pendingPhotoRequests.has(requestId)) {\n this.pendingPhotoRequests.get(requestId)!.reject(\"Photo request timed out\");\n this.pendingPhotoRequests.delete(requestId);\n this.logger.warn({ requestId }, `📸 Photo request timed out`);\n }\n }, timeoutMs);\n } else {\n // Fallback to regular setTimeout if session not available\n setTimeout(() => {\n if (this.pendingPhotoRequests.has(requestId)) {\n this.pendingPhotoRequests.get(requestId)!.reject(\"Photo request timed out\");\n this.pendingPhotoRequests.delete(requestId);\n this.logger.warn({ requestId }, `📸 Photo request timed out`);\n }\n }, timeoutMs);\n }\n } catch (error: unknown) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n reject(`Failed to request photo: ${errorMessage}`);\n }\n });\n }\n\n /**\n * 📥 Handle photo received from /photo-upload endpoint\n *\n * This method is called internally when a photo response is received.\n * It resolves the corresponding pending promise with the photo data.\n *\n * @param photoData - The photo data received\n * @internal This method is used internally by AppSession\n */\n handlePhotoReceived(photoData: PhotoData): void {\n const { requestId } = photoData;\n const pendingRequest = this.pendingPhotoRequests.get(requestId);\n\n if (pendingRequest) {\n this.logger.info({ requestId }, `📸 Photo received for request ${requestId}`);\n\n // Resolve the promise with the photo data\n pendingRequest.resolve(photoData);\n\n // Clean up from both session and AppServer registry\n this.pendingPhotoRequests.delete(requestId);\n if (this.session?.appServer) {\n this.session.appServer.unregisterPhotoRequest(requestId);\n }\n } else {\n this.logger.warn({ requestId }, `📸 Received photo for unknown request ID: ${requestId}`);\n }\n }\n\n /**\n * ❌ Handle photo error from /photo-upload endpoint\n *\n * This method is called internally when a photo error response is received.\n * It rejects the corresponding pending promise with the error information.\n *\n * @param errorResponse - The error response received\n * @internal This method is used internally by AppSession\n */\n handlePhotoError(errorResponse: {\n requestId: string;\n success: false;\n error: {\n code: string;\n message: string;\n };\n }): void {\n const { requestId, error } = errorResponse;\n const pendingRequest = this.pendingPhotoRequests.get(requestId);\n\n if (pendingRequest) {\n this.logger.error(\n { requestId, errorCode: error.code, errorMessage: error.message },\n `📸 Photo capture failed: ${error.code} - ${error.message}`,\n );\n\n // Reject the promise with the error information\n pendingRequest.reject(`${error.code}: ${error.message}`);\n\n // Clean up from both session and AppServer registry\n this.pendingPhotoRequests.delete(requestId);\n if (this.session?.appServer) {\n this.session.appServer.unregisterPhotoRequest(requestId);\n }\n } else {\n this.logger.warn(\n { requestId, errorCode: error.code, errorMessage: error.message },\n `📸 Received photo error for unknown request ID: ${requestId}`,\n );\n }\n }\n\n /**\n * 🔍 Check if there's a pending photo request for the given request ID\n *\n * @param requestId - The request ID to check\n * @returns true if there's a pending request\n */\n hasPhotoPendingRequest(requestId: string): boolean {\n return this.pendingPhotoRequests.has(requestId);\n }\n\n /**\n * 📊 Get the number of pending photo requests\n *\n * @returns Number of pending photo requests\n */\n getPhotoPendingRequestCount(): number {\n return this.pendingPhotoRequests.size;\n }\n\n /**\n * 📋 Get all pending photo request IDs\n *\n * @returns Array of pending request IDs\n */\n getPhotoPendingRequestIds(): string[] {\n return Array.from(this.pendingPhotoRequests.keys());\n }\n\n /**\n * ❌ Cancel a pending photo request\n *\n * @param requestId - The request ID to cancel\n * @returns true if the request was cancelled, false if it wasn't found\n */\n cancelPhotoRequest(requestId: string): boolean {\n const pendingRequest = this.pendingPhotoRequests.get(requestId);\n if (pendingRequest) {\n pendingRequest.reject(\"Photo request cancelled\");\n this.pendingPhotoRequests.delete(requestId);\n // Also unregister from AppServer\n if (this.session?.appServer) {\n this.session.appServer.unregisterPhotoRequest(requestId);\n }\n this.logger.info({ requestId }, `📸 Photo request cancelled`);\n return true;\n }\n return false;\n }\n\n /**\n * 🧹 Cancel all pending photo requests\n *\n * @returns Number of requests that were cancelled\n */\n cancelAllPhotoRequests(): number {\n const count = this.pendingPhotoRequests.size;\n\n for (const [requestId, { reject }] of this.pendingPhotoRequests) {\n reject(\"Photo request cancelled - session cleanup\");\n // Also unregister from AppServer\n if (this.session?.appServer) {\n this.session.appServer.unregisterPhotoRequest(requestId);\n }\n this.logger.info({ requestId }, `📸 Photo request cancelled during cleanup`);\n }\n\n this.pendingPhotoRequests.clear();\n return count;\n }\n\n // =====================================\n // 📹 Streaming Functionality\n // =====================================\n\n /**\n * 📹 Start an RTMP stream to the specified URL\n *\n * @param options - Configuration options for the stream\n * @returns Promise that resolves when the stream request is sent (not when streaming begins)\n *\n * @example\n * ```typescript\n * await session.camera.startStream({\n * rtmpUrl: 'rtmp://live.example.com/stream/key',\n * video: { resolution: '1920x1080', bitrate: 5000 },\n * audio: { bitrate: 128 }\n * });\n * ```\n */\n async startStream(options: RtmpStreamOptions): Promise<void> {\n this.logger.info({ rtmpUrl: options.rtmpUrl }, `📹 RTMP stream request starting`);\n\n cameraWarnLog(this.session.getHttpsServerUrl?.(), this.packageName, \"startStream\");\n\n if (!options.rtmpUrl) {\n throw new Error(\"rtmpUrl is required\");\n }\n\n if (this.isStreaming) {\n this.logger.error(\n {\n currentStreamUrl: this.currentStreamUrl,\n requestedUrl: options.rtmpUrl,\n },\n `📹 Already streaming error`,\n );\n throw new Error(\"Already streaming. Stop the current stream before starting a new one.\");\n }\n\n // Create stream request message\n const message: RtmpStreamRequest = {\n type: AppToCloudMessageType.RTMP_STREAM_REQUEST,\n packageName: this.packageName,\n sessionId: this.sessionId,\n rtmpUrl: options.rtmpUrl,\n video: options.video,\n audio: options.audio,\n stream: options.stream,\n timestamp: new Date(),\n };\n\n // Save stream URL for reference\n this.currentStreamUrl = options.rtmpUrl;\n\n // Send the request\n try {\n this.session.sendMessage(message);\n this.isStreaming = true;\n\n this.logger.info({ rtmpUrl: options.rtmpUrl }, `📹 RTMP stream request sent successfully`);\n return Promise.resolve();\n } catch (error) {\n this.logger.error({ error, rtmpUrl: options.rtmpUrl }, `📹 Failed to send RTMP stream request`);\n const errorMessage = error instanceof Error ? error.message : String(error);\n return Promise.reject(`Failed to request RTMP stream: ${errorMessage}`);\n }\n }\n\n /**\n * 🛑 Stop the current RTMP stream\n *\n * @returns Promise that resolves when the stop request is sent\n *\n * @example\n * ```typescript\n * await session.camera.stopStream();\n * ```\n */\n async stopStream(): Promise<void> {\n this.logger.info(\n {\n isCurrentlyStreaming: this.isStreaming,\n currentStreamUrl: this.currentStreamUrl,\n },\n `📹 RTMP stream stop request`,\n );\n\n if (!this.isStreaming) {\n this.logger.info(`📹 Not streaming - no-op`);\n // Not an error - just a no-op if not streaming\n return Promise.resolve();\n }\n\n // Create stop request message\n const message: RtmpStreamStopRequest = {\n type: AppToCloudMessageType.RTMP_STREAM_STOP,\n packageName: this.packageName,\n sessionId: this.sessionId,\n streamId: this.currentStreamState?.streamId, // Include streamId if available\n timestamp: new Date(),\n };\n\n // Send the request\n try {\n this.session.sendMessage(message);\n return Promise.resolve();\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n return Promise.reject(`Failed to stop RTMP stream: ${errorMessage}`);\n }\n }\n\n /**\n * 🔍 Check if currently streaming\n *\n * @returns True if a stream is active or initializing\n */\n isCurrentlyStreaming(): boolean {\n return this.isStreaming;\n }\n\n /**\n * 📍 Get the URL of the current stream (if any)\n *\n * @returns The RTMP URL of the current stream, or undefined if not streaming\n */\n getCurrentStreamUrl(): string | undefined {\n return this.currentStreamUrl;\n }\n\n /**\n * 📊 Get the current stream status\n *\n * @returns The current stream status, or undefined if not available\n */\n getStreamStatus(): RtmpStreamStatus | undefined {\n return this.currentStreamState;\n }\n\n /**\n * 📺 Subscribe to RTMP stream status updates\n * This uses the standard stream subscription mechanism\n */\n subscribeToStreamStatusUpdates(): void {\n if (this.session) {\n this.session.subscribe(StreamType.RTMP_STREAM_STATUS);\n } else {\n this.logger.error(\"Cannot subscribe to status updates: session reference not available\");\n }\n }\n\n /**\n * 📺 Unsubscribe from RTMP stream status updates\n */\n unsubscribeFromStreamStatusUpdates(): void {\n if (this.session) {\n this.session.unsubscribe(StreamType.RTMP_STREAM_STATUS);\n }\n }\n\n /**\n * 👂 Listen for stream status updates using the standard event system\n * @param handler - Function to call when stream status changes\n * @returns Cleanup function to remove the handler\n *\n * @example\n * ```typescript\n * const cleanup = session.camera.onStreamStatus((status) => {\n * console.log('Stream status:', status.status);\n * if (status.status === 'error') {\n * console.error('Stream error:', status.errorDetails);\n * }\n * });\n *\n * // Later, cleanup the listener\n * cleanup();\n * ```\n */\n onStreamStatus(handler: StreamStatusHandler): () => void {\n if (!this.session) {\n this.logger.error(\"Cannot listen for status updates: session reference not available\");\n return () => {};\n }\n\n this.subscribeToStreamStatusUpdates();\n return this.session.on(StreamType.RTMP_STREAM_STATUS, handler);\n }\n\n /**\n * 🔄 Update internal stream state based on a status message\n * For internal use by AppSession\n * @param message - The status message from the cloud\n * @internal This method is used internally by AppSession\n */\n updateStreamState(message: any): void {\n this.logger.debug(\n {\n messageType: message?.type,\n messageStatus: message?.status,\n currentIsStreaming: this.isStreaming,\n },\n `📹 Stream state update`,\n );\n\n // Verify this is a valid stream response\n if (!isRtmpStreamStatus(message)) {\n this.logger.warn({ message }, `📹 Received invalid stream status message`);\n return;\n }\n\n // Convert to StreamStatus format\n const status: RtmpStreamStatus = {\n type: message.type,\n streamId: message.streamId,\n status: message.status,\n errorDetails: message.errorDetails,\n appId: message.appId,\n stats: message.stats,\n timestamp: message.timestamp || new Date(),\n };\n\n this.logger.info(\n {\n streamId: status.streamId,\n oldStatus: this.currentStreamState?.status,\n newStatus: status.status,\n wasStreaming: this.isStreaming,\n },\n `📹 Stream status processed`,\n );\n\n // Update local state based on status\n if (status.status === \"stopped\" || status.status === \"error\" || status.status === \"timeout\") {\n this.logger.info(\n {\n status: status.status,\n wasStreaming: this.isStreaming,\n },\n `📹 Stream stopped - updating local state`,\n );\n this.isStreaming = false;\n this.currentStreamUrl = undefined;\n }\n\n // Save the latest status\n this.currentStreamState = status;\n }\n\n // =====================================\n // 📹 Managed Streaming Functionality\n // =====================================\n\n /**\n * 📹 Start a managed stream\n *\n * The cloud handles the RTMP endpoint and returns HLS/DASH URLs for viewing.\n * Multiple apps can consume the same managed stream simultaneously.\n *\n * @param options - Configuration options for the managed stream\n * @returns Promise that resolves with viewing URLs when the stream is ready\n *\n * @example\n * ```typescript\n * const urls = await session.camera.startManagedStream({\n * quality: '720p',\n * enableWebRTC: true\n * });\n * console.log('HLS URL:', urls.hlsUrl);\n * ```\n */\n async startManagedStream(options?: ManagedStreamOptions): Promise<ManagedStreamResult> {\n return this.managedExtension.startManagedStream(options);\n }\n\n /**\n * 🛑 Stop the current managed stream\n *\n * This will stop streaming for this app only. If other apps are consuming\n * the same managed stream, it will continue for them.\n *\n * @returns Promise that resolves when the stop request is sent\n */\n async stopManagedStream(): Promise<void> {\n return this.managedExtension.stopManagedStream();\n }\n\n /**\n * 🔔 Register a handler for managed stream status updates\n *\n * @param handler - Function to call when stream status changes\n * @returns Cleanup function to unregister the handler\n */\n onManagedStreamStatus(handler: (status: ManagedStreamStatus) => void): () => void {\n return this.managedExtension.onManagedStreamStatus(handler);\n }\n\n /**\n * 📊 Check if currently managed streaming\n *\n * @returns true if a managed stream is active\n */\n isManagedStreamActive(): boolean {\n return this.managedExtension.isManagedStreamActive();\n }\n\n /**\n * 🔗 Get current managed stream URLs\n *\n * @returns Current stream URLs or undefined if not streaming\n */\n getManagedStreamUrls(): ManagedStreamResult | undefined {\n return this.managedExtension.getManagedStreamUrls();\n }\n\n /**\n * 🔍 Check for any existing streams (managed or unmanaged) for the current user\n *\n * This method checks if there's already an active stream for the current user,\n * which is useful to avoid conflicts and to reconnect to existing streams.\n *\n * @returns Promise that resolves with stream information if a stream exists\n *\n * @example\n * ```typescript\n * const streamInfo = await session.camera.checkExistingStream();\n * if (streamInfo.hasActiveStream) {\n * console.log('Stream type:', streamInfo.streamInfo?.type);\n * if (streamInfo.streamInfo?.type === 'managed') {\n * console.log('HLS URL:', streamInfo.streamInfo.hlsUrl);\n * } else {\n * console.log('RTMP URL:', streamInfo.streamInfo.rtmpUrl);\n * }\n * }\n * ```\n */\n async checkExistingStream(): Promise<{\n hasActiveStream: boolean;\n streamInfo?: {\n type: \"managed\" | \"unmanaged\";\n streamId: string;\n status: string;\n createdAt: Date;\n // For managed streams\n hlsUrl?: string;\n dashUrl?: string;\n webrtcUrl?: string;\n previewUrl?: string;\n thumbnailUrl?: string;\n activeViewers?: number;\n // For unmanaged streams\n rtmpUrl?: string;\n requestingAppId?: string;\n };\n }> {\n return this.managedExtension.checkExistingStream();\n }\n\n /**\n * Handle incoming stream status check response\n * @internal\n */\n handleStreamCheckResponse(response: StreamStatusCheckResponse): void {\n this.managedExtension.handleStreamCheckResponse(response);\n }\n\n /**\n * Handle incoming managed stream status messages\n * @internal\n */\n handleManagedStreamStatus(message: ManagedStreamStatus): void {\n this.managedExtension.handleManagedStreamStatus(message);\n }\n\n // =====================================\n // 🔧 General Utilities\n // =====================================\n\n /**\n * 🔧 Update the session ID (used when reconnecting)\n *\n * @param newSessionId - The new session ID\n * @internal This method is used internally by AppSession\n */\n updateSessionId(newSessionId: string): void {\n this.sessionId = newSessionId;\n }\n\n /**\n * 🧹 Cancel all pending requests and clean up resources\n *\n * @returns Object with counts of cancelled requests\n */\n cancelAllRequests(): { photoRequests: number } {\n const photoRequests = this.cancelAllPhotoRequests();\n\n // Stop streaming if active\n if (this.isStreaming) {\n this.stopStream().catch((error) => {\n this.logger.error({ error }, \"Error stopping stream during cleanup\");\n });\n }\n\n // Clean up managed extension\n this.managedExtension.cleanup();\n\n return { photoRequests };\n }\n}\n\n// Re-export types for convenience\nexport { VideoConfig, AudioConfig, StreamConfig, StreamStatusHandler };\n",
33
- "/**\n * 📷 Camera Module Managed Streaming Extension\n *\n * Extends the camera module with managed streaming capabilities.\n * Apps can request managed streams and receive HLS/DASH URLs without managing RTMP endpoints.\n */\n\nimport {\n ManagedStreamRequest,\n ManagedStreamStopRequest,\n ManagedStreamStatus,\n StreamStatusCheckRequest,\n StreamStatusCheckResponse,\n AppToCloudMessageType,\n StreamType,\n RestreamDestination,\n} from \"../../../types\"\nimport {VideoConfig, AudioConfig, StreamConfig} from \"../../../types/rtmp-stream\"\nimport {Logger} from \"pino\"\n\n/**\n * Configuration options for a managed stream\n */\nexport interface ManagedStreamOptions {\n /** Stream quality preset */\n quality?: \"720p\" | \"1080p\"\n /** Enable WebRTC for ultra-low latency viewing */\n enableWebRTC?: boolean\n /** Optional video configuration settings */\n video?: VideoConfig\n /** Optional audio configuration settings */\n audio?: AudioConfig\n /** Optional stream configuration settings */\n stream?: StreamConfig\n /** Optional RTMP destinations to re-stream to (YouTube, Twitch, etc) */\n restreamDestinations?: RestreamDestination[]\n}\n\n/**\n * Result returned when starting a managed stream\n */\nexport interface ManagedStreamResult {\n /** HLS URL for viewing the stream */\n hlsUrl: string\n /** DASH URL for viewing the stream */\n dashUrl: string\n /** WebRTC URL if enabled */\n webrtcUrl?: string\n /** Cloudflare Stream player/preview URL for embedding */\n previewUrl?: string\n /** Thumbnail image URL */\n thumbnailUrl?: string\n /** Internal stream ID */\n streamId: string\n}\n\n/**\n * 📹 Managed Streaming Extension for Camera Module\n *\n * Provides managed streaming capabilities where the cloud handles\n * RTMP endpoints and returns HLS/DASH URLs for viewing.\n *\n * @example\n * ```typescript\n * // Start a managed stream\n * const urls = await session.camera.startManagedStream({\n * quality: '720p',\n * enableWebRTC: true\n * });\n * console.log('HLS URL:', urls.hlsUrl);\n * console.log('DASH URL:', urls.dashUrl);\n * console.log('Player URL:', urls.previewUrl);\n * console.log('Thumbnail:', urls.thumbnailUrl);\n *\n * // Monitor managed stream status\n * session.camera.onManagedStreamStatus((status) => {\n * console.log('Managed stream status:', status.status);\n * });\n *\n * // Stop managed stream\n * await session.camera.stopManagedStream();\n * ```\n */\nexport class CameraManagedExtension {\n private session: any\n private packageName: string\n private sessionId: string\n private logger: Logger\n\n // Managed streaming state\n private isManagedStreaming: boolean = false\n private currentManagedStreamId?: string\n private currentManagedStreamUrls?: ManagedStreamResult\n private managedStreamStatus?: ManagedStreamStatus\n\n // For tracking pending stream check requests\n private pendingStreamChecks?: Map<\n string,\n {\n resolve: (value: any) => void\n timeoutId: NodeJS.Timeout\n }\n >\n\n // Promise tracking for managed stream initialization\n private pendingManagedStreamRequest?: {\n resolve: (value: ManagedStreamResult) => void\n reject: (reason?: any) => void\n }\n\n constructor(session: any, packageName: string, sessionId: string, logger: Logger) {\n this.session = session\n this.packageName = packageName\n this.sessionId = sessionId\n this.logger = logger.child({module: \"CameraManagedExtension\"})\n }\n\n /**\n * 📹 Start a managed stream\n *\n * The cloud will handle the RTMP endpoint and return HLS/DASH URLs for viewing.\n * Multiple apps can consume the same managed stream simultaneously.\n *\n * @param options - Configuration options for the managed stream\n * @returns Promise that resolves with viewing URLs when the stream is ready\n *\n * @example\n * ```typescript\n * const urls = await session.camera.startManagedStream({\n * quality: '1080p',\n * enableWebRTC: true,\n * video: { fps: 30 },\n * audio: { sampleRate: 48000 }\n * });\n *\n * // Access all available URLs\n * console.log('HLS URL:', urls.hlsUrl);\n * console.log('DASH URL:', urls.dashUrl);\n * console.log('Player URL:', urls.previewUrl); // Embeddable player\n * console.log('Thumbnail:', urls.thumbnailUrl);\n * ```\n */\n async startManagedStream(options: ManagedStreamOptions = {}): Promise<ManagedStreamResult> {\n this.logger.info({options}, \"📹 Managed stream request starting\")\n\n if (this.isManagedStreaming) {\n this.logger.error(\n {\n currentStreamId: this.currentManagedStreamId,\n },\n \"📹 Already managed streaming error\",\n )\n throw new Error(\"Already streaming. Stop the current managed stream before starting a new one.\")\n }\n\n // Create the request message\n const request: ManagedStreamRequest = {\n type: AppToCloudMessageType.MANAGED_STREAM_REQUEST,\n packageName: this.packageName,\n quality: options.quality,\n enableWebRTC: options.enableWebRTC,\n video: options.video,\n audio: options.audio,\n stream: options.stream,\n restreamDestinations: options.restreamDestinations,\n }\n\n // Send the request\n this.session.sendMessage(request)\n this.isManagedStreaming = true\n\n // Create promise to wait for URLs\n return new Promise((resolve, reject) => {\n this.pendingManagedStreamRequest = {resolve, reject}\n\n // Set a timeout\n setTimeout(() => {\n if (this.pendingManagedStreamRequest) {\n this.pendingManagedStreamRequest = undefined\n this.isManagedStreaming = false\n reject(new Error(\"Managed stream request timeout\"))\n }\n }, 30000) // 30 second timeout\n })\n }\n\n /**\n * 🛑 Stop the current managed stream\n *\n * This will stop streaming for this app only. If other apps are consuming\n * the same managed stream, it will continue for them.\n *\n * @returns Promise that resolves when the stop request is sent\n */\n async stopManagedStream(): Promise<void> {\n // Always send the stop request - the cloud will handle whether there's actually\n // a stream to stop. This ensures the stop works even after reload/reconnection\n this.logger.info(\n {\n streamId: this.currentManagedStreamId,\n hasInternalState: this.isManagedStreaming,\n },\n \"📹 Sending managed stream stop request\",\n )\n\n const request: ManagedStreamStopRequest = {\n type: AppToCloudMessageType.MANAGED_STREAM_STOP,\n packageName: this.packageName,\n }\n\n this.session.sendMessage(request)\n\n // Don't clean up state immediately - wait for the 'stopped' status from cloud\n // This ensures we can retry stop if needed and maintains accurate state\n }\n\n /**\n * 🔍 Check for any existing streams (managed or unmanaged) for the current user\n *\n * @returns Promise that resolves with stream information if a stream exists\n *\n * @example\n * ```typescript\n * const streamInfo = await session.camera.checkExistingStream();\n * if (streamInfo.hasActiveStream) {\n * console.log('Stream type:', streamInfo.streamInfo?.type);\n * if (streamInfo.streamInfo?.type === 'managed') {\n * console.log('HLS URL:', streamInfo.streamInfo.hlsUrl);\n * }\n * }\n * ```\n */\n async checkExistingStream(): Promise<{\n hasActiveStream: boolean\n streamInfo?: {\n type: \"managed\" | \"unmanaged\"\n streamId: string\n status: string\n createdAt: Date\n // For managed streams\n hlsUrl?: string\n dashUrl?: string\n webrtcUrl?: string\n previewUrl?: string\n thumbnailUrl?: string\n activeViewers?: number\n // For unmanaged streams\n rtmpUrl?: string\n requestingAppId?: string\n }\n }> {\n return new Promise((resolve) => {\n // Store the resolver for the response\n const requestId = `stream_check_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`\n\n // Store a pending request that will be resolved when we get the response\n if (!this.pendingStreamChecks) {\n this.pendingStreamChecks = new Map()\n }\n\n const timeoutId = setTimeout(() => {\n this.pendingStreamChecks?.delete(requestId)\n resolve({hasActiveStream: false})\n }, 5000) // 5 second timeout\n\n this.pendingStreamChecks.set(requestId, {\n resolve,\n timeoutId,\n })\n\n // Send the check request with the requestId\n const request: StreamStatusCheckRequest = {\n type: AppToCloudMessageType.STREAM_STATUS_CHECK,\n packageName: this.packageName,\n sessionId: this.sessionId,\n }\n\n this.session.sendMessage(request)\n })\n }\n\n /**\n * 📊 Check if currently managed streaming\n *\n * @returns true if a managed stream is active\n */\n isManagedStreamActive(): boolean {\n return this.isManagedStreaming\n }\n\n /**\n * 🔗 Get current managed stream URLs\n *\n * @returns Current stream URLs or undefined if not streaming\n */\n getManagedStreamUrls(): ManagedStreamResult | undefined {\n return this.currentManagedStreamUrls\n }\n\n /**\n * 📊 Get current managed stream status\n *\n * @returns Current stream status or undefined\n */\n getManagedStreamStatus(): ManagedStreamStatus | undefined {\n return this.managedStreamStatus\n }\n\n /**\n * 🔔 Register a handler for managed stream status updates\n *\n * @param handler - Function to call when stream status changes\n * @returns Cleanup function to unregister the handler\n *\n * @example\n * ```typescript\n * const cleanup = session.camera.onManagedStreamStatus((status) => {\n * console.log('Status:', status.status);\n * if (status.status === 'active') {\n * console.log('Stream is live!');\n * }\n * });\n *\n * // Later, unregister the handler\n * cleanup();\n * ```\n */\n onManagedStreamStatus(handler: (status: ManagedStreamStatus) => void): () => void {\n if (!this.session) {\n this.logger.error(\"Cannot listen for managed status updates: session reference not available\")\n return () => {}\n }\n\n this.session.subscribe(StreamType.MANAGED_STREAM_STATUS)\n\n // Register the handler using the session's event system\n return this.session.on(StreamType.MANAGED_STREAM_STATUS, handler)\n }\n\n /**\n * Handle incoming stream status check response\n * Called by the parent AppSession when a response is received\n */\n handleStreamCheckResponse(response: StreamStatusCheckResponse): void {\n // Find and resolve any pending stream check\n if (this.pendingStreamChecks && this.pendingStreamChecks.size > 0) {\n const firstEntry = this.pendingStreamChecks.entries().next()\n if (!firstEntry.done && firstEntry.value) {\n const [requestId, pending] = firstEntry.value\n if (pending) {\n clearTimeout(pending.timeoutId)\n this.pendingStreamChecks.delete(requestId)\n pending.resolve(response)\n }\n }\n }\n }\n\n /**\n * Handle incoming managed stream status messages\n * Called by the parent AppSession when messages are received\n */\n handleManagedStreamStatus(status: ManagedStreamStatus): void {\n this.logger.info(\n {\n status: status.status,\n streamId: status.streamId,\n },\n \"📹 Received managed stream status\",\n )\n\n this.managedStreamStatus = status\n\n // Handle initializing status - stream is starting\n if (status.status === \"initializing\" && status.streamId) {\n this.isManagedStreaming = true\n this.currentManagedStreamId = status.streamId\n }\n\n // Handle initial stream ready status\n if (status.status === \"active\") {\n // Always update state when stream is active\n this.isManagedStreaming = true\n this.currentManagedStreamId = status.streamId\n\n if (status.hlsUrl && status.dashUrl) {\n const result: ManagedStreamResult = {\n hlsUrl: status.hlsUrl,\n dashUrl: status.dashUrl,\n webrtcUrl: status.webrtcUrl,\n previewUrl: status.previewUrl,\n thumbnailUrl: status.thumbnailUrl,\n streamId: status.streamId || \"\",\n }\n\n this.currentManagedStreamUrls = result\n\n // Resolve pending promise if exists\n if (this.pendingManagedStreamRequest) {\n this.pendingManagedStreamRequest.resolve(result)\n this.pendingManagedStreamRequest = undefined\n }\n }\n }\n\n // Handle error status\n if ((status.status === \"error\" || status.status === \"stopped\") && this.pendingManagedStreamRequest) {\n this.pendingManagedStreamRequest.reject(new Error(status.message || \"Managed stream failed\"))\n this.pendingManagedStreamRequest = undefined\n this.isManagedStreaming = false\n this.currentManagedStreamId = undefined\n this.currentManagedStreamUrls = undefined\n }\n\n // Clean up on stopped status\n if (status.status === \"stopped\") {\n this.isManagedStreaming = false\n this.currentManagedStreamId = undefined\n this.currentManagedStreamUrls = undefined\n }\n\n // Clean up local state on error regardless of pending request state\n if (status.status === \"error\") {\n this.isManagedStreaming = false\n this.currentManagedStreamId = undefined\n this.currentManagedStreamUrls = undefined\n }\n\n // Notify handlers (would use event emitter in real implementation)\n // this.emit('managedStreamStatus', status);\n }\n\n /**\n * 🧹 Clean up all managed streaming state\n */\n cleanup(): void {\n if (this.pendingManagedStreamRequest) {\n this.pendingManagedStreamRequest.reject(new Error(\"Camera module cleanup\"))\n this.pendingManagedStreamRequest = undefined\n }\n\n this.isManagedStreaming = false\n this.currentManagedStreamId = undefined\n this.currentManagedStreamUrls = undefined\n this.managedStreamStatus = undefined\n\n this.logger.info(\"📹 Managed streaming extension cleaned up\")\n }\n}\n",
34
- "/**\n * 💡 LED Control Module\n *\n * Provides RGB LED control functionality for App Sessions.\n * Controls the RGB LEDs on smart glasses with custom colors and timing patterns.\n */\n\nimport {RgbLedControlRequest, AppToCloudMessageType, LedColor} from \"../../../types\"\nimport {Logger} from \"pino\"\n\n/**\n * Options for LED control\n */\nexport interface LedControlOptions {\n /** LED color */\n color?: LedColor\n /** LED on duration in milliseconds */\n ontime?: number\n /** LED off duration in milliseconds */\n offtime?: number\n /** Number of on/off cycles */\n count?: number\n}\n\n/**\n * 💡 LED Control Module Implementation\n *\n * Provides methods for controlling LEDs on smart glasses.\n * Supports both low-level on/off control and higher-level pattern methods.\n *\n * @example\n * ```typescript\n * // General LED control\n * await session.led.turnOn({ color: 'red', ontime: 1000, count: 1 });\n *\n * // Blink green LED\n * await session.led.blink('green', 500, 500, 5);\n *\n * // Turn off all LEDs\n * await session.led.turnOff();\n * ```\n */\nexport class LedModule {\n private session: any\n private packageName: string\n private sessionId: string\n private logger: Logger\n\n /**\n * Create a new LedModule\n *\n * @param session - Reference to the parent AppSession\n * @param packageName - The App package name\n * @param sessionId - The current session ID\n * @param logger - Logger instance for debugging\n */\n constructor(session: any, packageName: string, sessionId: string, logger?: Logger) {\n this.session = session\n this.packageName = packageName\n this.sessionId = sessionId\n this.logger = logger || (console as any)\n }\n\n // =====================================\n // 💡 Low-level LED Control\n // =====================================\n\n /**\n * 💡 Turn on an LED with specified timing parameters\n *\n * @param options - LED control options (color, timing, count)\n * @returns Promise that resolves immediately after sending the command\n *\n * @example\n * ```typescript\n * // Solid red for 2 seconds\n * await session.led.turnOn({ color: 'red', ontime: 2000, count: 1 });\n *\n * // Blink white 3 times\n * await session.led.turnOn({ color: 'white', ontime: 500, offtime: 500, count: 3 });\n * ```\n */\n async turnOn(options: LedControlOptions): Promise<void> {\n try {\n // Generate unique request ID for tracking\n const requestId = `led_req_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`\n\n // Create LED control request message\n const message: RgbLedControlRequest = {\n type: AppToCloudMessageType.RGB_LED_CONTROL,\n packageName: this.packageName,\n sessionId: this.sessionId,\n requestId,\n timestamp: new Date(),\n action: \"on\",\n color: options.color || \"red\",\n ontime: options.ontime || 1000,\n offtime: options.offtime || 0,\n count: options.count || 1,\n }\n\n // Send request to cloud (fire-and-forget)\n this.session.sendMessage(message)\n\n this.logger.info(\n {\n requestId,\n color: options.color,\n ontime: options.ontime,\n offtime: options.offtime,\n count: options.count,\n },\n `💡 LED control request sent`,\n )\n\n // Resolve immediately - no waiting for response\n return Promise.resolve()\n } catch (error) {\n this.logger.error({error, options}, \"❌ Error in LED turnOn request\")\n throw error\n }\n }\n\n /**\n * 💡 Turn off all LEDs\n *\n * @returns Promise that resolves immediately after sending the command\n *\n * @example\n * ```typescript\n * await session.led.turnOff();\n * ```\n */\n async turnOff(): Promise<void> {\n try {\n // Generate unique request ID for tracking\n const requestId = `led_req_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`\n\n // Create LED control request message\n const message: RgbLedControlRequest = {\n type: AppToCloudMessageType.RGB_LED_CONTROL,\n packageName: this.packageName,\n sessionId: this.sessionId,\n requestId,\n timestamp: new Date(),\n action: \"off\",\n }\n\n // Send request to cloud (fire-and-forget)\n this.session.sendMessage(message)\n\n this.logger.info({requestId}, `💡 LED turn off request sent`)\n\n // Resolve immediately - no waiting for response\n return Promise.resolve()\n } catch (error) {\n this.logger.error({error}, \"❌ Error in LED turnOff request\")\n throw error\n }\n }\n\n /**\n * 💡 Get available LED capabilities\n *\n * @returns Array of available LED configurations\n *\n * @example\n * ```typescript\n * const capabilities = session.led.getCapabilities();\n * console.log('Available LEDs:', capabilities);\n * ```\n */\n getCapabilities(): Array<{\n id: string\n purpose: string\n isFullColor: boolean\n color?: string\n position?: string\n }> {\n // This would need to be implemented with access to session capabilities\n // For now, return empty array - this would be populated from session.capabilities\n return []\n }\n\n // =====================================\n // 💡 Pattern Methods (SDK-generated)\n // =====================================\n\n /**\n * 💡 Blink an LED with specified timing\n *\n * @param color - LED color to use\n * @param ontime - How long LED stays on (ms)\n * @param offtime - How long LED stays off (ms)\n * @param count - Number of blink cycles\n * @returns Promise that resolves immediately after sending the command\n *\n * @example\n * ```typescript\n * // Blink red LED 5 times (500ms on, 500ms off)\n * await session.led.blink('red', 500, 500, 5);\n * ```\n */\n async blink(color: LedColor, ontime: number, offtime: number, count: number): Promise<void> {\n return this.turnOn({\n color,\n ontime,\n offtime,\n count,\n })\n }\n\n /**\n * 💡 Solid LED mode - LED stays on continuously for specified duration\n *\n * Creates a solid LED effect with no off time, perfect for continuous illumination.\n *\n * @param color - LED color to use\n * @param duration - How long LED stays on (ms)\n * @returns Promise that resolves when the command is sent\n *\n * @example\n * ```typescript\n * // Solid red LED for 5 seconds\n * await session.led.solid('red', 5000);\n *\n * // Solid white LED for 30 seconds\n * await session.led.solid('white', 30000);\n * ```\n */\n async solid(color: LedColor, duration: number): Promise<void> {\n return this.turnOn({\n color,\n ontime: duration,\n offtime: 0, // No off time for solid mode\n count: 1, // Single cycle\n })\n }\n\n // =====================================\n // 💡 Cleanup\n // =====================================\n\n /**\n * Clean up LED module\n *\n * @internal\n */\n cleanup(): void {\n this.logger.info(\"🧹 LED module cleaned up\")\n }\n}\n",
35
- "/**\n * 🔊 Audio Module\n *\n * Audio functionality for App Sessions.\n * Handles audio playback on connected glasses.\n */\n\nimport {AudioPlayRequest, AudioPlayResponse, AudioStopRequest, AppToCloudMessageType} from \"../../../types\"\nimport {Logger} from \"pino\"\n\n/**\n * Options for audio playback\n */\nexport interface AudioPlayOptions {\n /** URL to audio file for download and play */\n audioUrl: string\n /** Volume level 0.0-1.0, defaults to 1.0 */\n volume?: number\n /** Whether to stop other audio playback, defaults to true */\n stopOtherAudio?: boolean\n /**\n * Track ID for audio playback (defaults to 0)\n * - 0: speaker (default audio playback)\n * - 1: app_audio (app-specific audio)\n * - 2: tts (text-to-speech audio)\n * Use different track IDs to play multiple audio streams simultaneously (mixing)\n */\n trackId?: number\n}\n\n/**\n * Options for text-to-speech\n */\nexport interface SpeakOptions {\n /** Voice ID to use (optional, defaults to server's ELEVENLABS_DEFAULT_VOICE_ID) */\n voice_id?: string\n /** Model ID to use (optional, defaults to eleven_flash_v2_5) */\n model_id?: string\n /** Voice settings object (optional) */\n voice_settings?: {\n stability?: number\n similarity_boost?: number\n style?: number\n use_speaker_boost?: boolean\n speed?: number\n }\n /** Volume level 0.0-1.0, defaults to 1.0 */\n volume?: number\n /** Whether to stop other audio playback, defaults to true */\n stopOtherAudio?: boolean\n /**\n * Track ID for audio playback (defaults to 2 for TTS)\n * - 0: speaker (default audio playback)\n * - 1: app_audio (app-specific audio)\n * - 2: tts (text-to-speech audio)\n * Use different track IDs to play multiple audio streams simultaneously (mixing)\n */\n trackId?: number\n}\n\n/**\n * Result of audio playback attempt\n */\nexport interface AudioPlayResult {\n /** Whether the audio playback was successful */\n success: boolean\n /** Error message if playback failed */\n error?: string\n /** Duration of the audio file in seconds (if available) */\n duration?: number\n}\n\n/**\n * 🔊 Audio Module Implementation\n *\n * Audio management for App Sessions.\n * Provides methods for:\n * - 🎵 Playing audio on glasses\n * - ⏹️ Stopping audio playback\n * - 🔍 Monitoring audio request status\n * - 🧹 Cleanup and cancellation\n *\n * @example\n * ```typescript\n * // Play audio\n * const result = await session.audio.playAudio({\n * audioUrl: 'https://example.com/sound.mp3',\n * volume: 0.8\n * });\n *\n * // Stop all audio\n * session.audio.stopAudio();\n * ```\n */\nexport class AudioManager {\n private session: any // Reference to AppSession\n private packageName: string\n private sessionId: string\n private logger: Logger\n\n /** Map to store pending audio play request promises */\n private pendingAudioRequests = new Map<\n string,\n {\n resolve: (value: AudioPlayResult) => void\n reject: (reason?: string) => void\n }\n >()\n\n /**\n * Create a new AudioManager\n *\n * @param packageName - The App package name\n * @param sessionId - The current session ID\n * @param send - Function to send messages to the cloud\n * @param session - Reference to the parent AppSession (optional)\n * @param logger - Logger instance for debugging\n */\n constructor(session: any, packageName: string, sessionId: string, logger?: Logger) {\n this.session = session\n this.packageName = packageName\n this.sessionId = sessionId\n this.logger = logger || (console as any)\n }\n\n // =====================================\n // 🎵 Audio Playback Functionality\n // =====================================\n\n /**\n * 🔊 Play audio on the connected glasses\n * @param options - Audio playback configuration\n * @returns Promise that resolves with playback result\n *\n * @example\n * ```typescript\n * // Play audio from URL\n * const result = await session.audio.playAudio({\n * audioUrl: 'https://example.com/sound.mp3',\n * volume: 0.8\n * });\n * ```\n */\n async playAudio(options: AudioPlayOptions): Promise<AudioPlayResult> {\n return new Promise((resolve, reject) => {\n try {\n // Validate input\n if (!options.audioUrl) {\n reject(\"audioUrl must be provided\")\n return\n }\n\n // Generate unique request ID\n const requestId = `audio_req_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`\n\n const stopOtherAudio = options.stopOtherAudio ?? true\n\n // CRITICAL: When stopOtherAudio=false (concurrent/mixing mode),\n // resolve immediately after sending the request (fire-and-forget)\n // This allows multiple audio streams to play simultaneously\n if (!stopOtherAudio) {\n // Create audio play request message\n const message: AudioPlayRequest = {\n type: AppToCloudMessageType.AUDIO_PLAY_REQUEST,\n packageName: this.packageName,\n sessionId: this.sessionId,\n requestId,\n timestamp: new Date(),\n audioUrl: options.audioUrl,\n volume: options.volume ?? 1.0,\n stopOtherAudio: false,\n trackId: options.trackId ?? 0, // Default to track 0 (speaker)\n }\n\n // Send request to cloud\n this.session.sendMessage(message)\n\n // Resolve immediately for concurrent playback (fire-and-forget)\n // The audio will play in the background without blocking\n this.logger.debug({requestId}, `🔊 Audio playback started in non-blocking mode (concurrent)`)\n resolve({\n success: true,\n duration: undefined, // Duration unknown in fire-and-forget mode\n })\n return\n }\n\n // For stopOtherAudio=true (blocking/interrupt mode),\n // wait for the COMPLETED/FAILED event before resolving\n this.pendingAudioRequests.set(requestId, {resolve, reject})\n\n // Create audio play request message\n const message: AudioPlayRequest = {\n type: AppToCloudMessageType.AUDIO_PLAY_REQUEST,\n packageName: this.packageName,\n sessionId: this.sessionId,\n requestId,\n timestamp: new Date(),\n audioUrl: options.audioUrl,\n volume: options.volume ?? 1.0,\n stopOtherAudio: true,\n trackId: options.trackId ?? 0, // Default to track 0 (speaker)\n }\n\n // Send request to cloud\n this.session.sendMessage(message)\n\n // Set timeout to avoid hanging promises (only for blocking mode)\n const timeoutMs = 60000 // 60 seconds\n if (this.session && this.session.resources) {\n // Use session's resource tracker for automatic cleeanup\n this.session.resources.setTimeout(() => {\n if (this.pendingAudioRequests.has(requestId)) {\n this.pendingAudioRequests.get(requestId)!.reject(\"Audio play request timed out\")\n this.pendingAudioRequests.delete(requestId)\n this.logger.warn({requestId}, `🔊 Audio play request timed out`)\n }\n }, timeoutMs)\n } else {\n // Fallback to regular setTimeout if session not available\n setTimeout(() => {\n if (this.pendingAudioRequests.has(requestId)) {\n this.pendingAudioRequests.get(requestId)!.reject(\"Audio play request timed out\")\n this.pendingAudioRequests.delete(requestId)\n this.logger.warn({requestId}, `🔊 Audio play request timed out`)\n }\n }, timeoutMs)\n }\n } catch (error: unknown) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n reject(`Failed to play audio: ${errorMessage}`)\n }\n })\n }\n\n /**\n * 🔇 Stop audio playback on the connected glasses\n * @param trackId - Optional track ID to stop (0=speaker, 1=app_audio, 2=tts). If omitted, stops all tracks.\n *\n * @example\n * ```typescript\n * // Stop all currently playing audio\n * session.audio.stopAudio();\n *\n * // Stop only the speaker track (track_id 0)\n * session.audio.stopAudio(0);\n *\n * // Stop only TTS track (track_id 2)\n * session.audio.stopAudio(2);\n * ```\n */\n stopAudio(trackId?: number): void {\n try {\n // Create audio stop request message\n const message: AudioStopRequest = {\n type: AppToCloudMessageType.AUDIO_STOP_REQUEST,\n packageName: this.packageName,\n sessionId: this.sessionId,\n trackId,\n timestamp: new Date(),\n }\n\n // Send request to cloud (one-way, no response expected)\n this.session.sendMessage(message)\n\n const trackInfo = trackId !== undefined ? ` (track ${trackId})` : \" (all tracks)\"\n this.logger.info(`🔇 Audio stop request sent${trackInfo}`)\n } catch (error: unknown) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n this.logger.error(`Failed to stop audio: ${errorMessage}`)\n }\n }\n\n /**\n * 🗣️ Convert text to speech and play it on the connected glasses\n * @param text - Text to convert to speech (required)\n * @param options - Text-to-speech configuration (optional)\n * @returns Promise that resolves with playback result\n *\n * @example\n * ```typescript\n * // Basic text-to-speech\n * const result = await session.audio.speak('Hello, world!');\n *\n * // With custom voice settings\n * const result = await session.audio.speak('Hello, world!', {\n * voice_id: 'your_voice_id',\n * voice_settings: {\n * stability: 0.5,\n * speed: 1.2\n * },\n * volume: 0.8\n * });\n *\n * // Play TTS without stopping other audio\n * const result = await session.audio.speak('Hello, world!', {\n * stopOtherAudio: false\n * });\n * ```\n */\n async speak(text: string, options: SpeakOptions = {}): Promise<AudioPlayResult> {\n // Validate input\n if (!text) {\n throw new Error(\"text must be provided\")\n }\n\n // Get the HTTPS server URL from the session\n const baseUrl = this.session?.getHttpsServerUrl?.()\n if (!baseUrl) {\n throw new Error(\"Cannot determine server URL for TTS endpoint\")\n }\n\n // Build query parameters for the TTS endpoint\n const queryParams = new URLSearchParams()\n queryParams.append(\"text\", text)\n\n if (options.voice_id) {\n queryParams.append(\"voice_id\", options.voice_id)\n }\n\n if (options.model_id) {\n queryParams.append(\"model_id\", options.model_id)\n }\n\n if (options.voice_settings) {\n queryParams.append(\"voice_settings\", JSON.stringify(options.voice_settings))\n }\n\n // Construct the TTS URL\n const ttsUrl = `${baseUrl}/api/tts?${queryParams.toString()}`\n\n this.logger.info({text, ttsUrl}, `🗣️ Generating speech from text`)\n\n // IMPORTANT: Don't call stopAudio() here - it closes tracks completely!\n // The backend will handle stopping any ongoing playback when it receives\n // the new audio play request (via stopOtherAudio flag)\n\n // Use the existing playAudio method to play the TTS audio\n // The stopOtherAudio flag will cancel ongoing playback without closing tracks\n return this.playAudio({\n audioUrl: ttsUrl,\n volume: options.volume,\n stopOtherAudio: options.stopOtherAudio ?? true, // This flag tells backend to stop current playback\n trackId: options.trackId ?? 2, // Default to track 2 (tts)\n })\n }\n\n // =====================================\n // 📥 Response Handling\n // =====================================\n\n /**\n * 📥 Handle audio play response from cloud\n *\n * This method is called internally when an audio play response is received.\n * It resolves the corresponding pending promise with the response data.\n *\n * @param response - The audio play response received\n * @internal This method is used internally by AppSession\n */\n handleAudioPlayResponse(response: AudioPlayResponse): void {\n const pendingRequest = this.pendingAudioRequests.get(response.requestId)\n\n if (pendingRequest) {\n // Resolve the promise with the response data\n pendingRequest.resolve({\n success: response.success,\n error: response.error,\n duration: response.duration,\n })\n\n // Clean up\n this.pendingAudioRequests.delete(response.requestId)\n\n this.logger.info(\n {\n requestId: response.requestId,\n success: response.success,\n duration: response.duration,\n },\n `🔊 Audio play response received`,\n )\n } else {\n this.logger.warn({requestId: response.requestId}, `🔊 Received audio play response for unknown request ID`)\n }\n }\n\n // =====================================\n // 🔍 Status and Management\n // =====================================\n\n /**\n * 🔍 Check if there are pending audio requests\n * @param requestId - Optional specific request ID to check\n * @returns True if there are pending requests (or specific request exists)\n */\n hasPendingRequest(requestId?: string): boolean {\n if (requestId) {\n return this.pendingAudioRequests.has(requestId)\n }\n return this.pendingAudioRequests.size > 0\n }\n\n /**\n * 📊 Get the number of pending audio requests\n * @returns Number of pending requests\n */\n getPendingRequestCount(): number {\n return this.pendingAudioRequests.size\n }\n\n /**\n * 📋 Get all pending request IDs\n * @returns Array of pending request IDs\n */\n getPendingRequestIds(): string[] {\n return Array.from(this.pendingAudioRequests.keys())\n }\n\n /**\n * ❌ Cancel a specific audio request\n * @param requestId - The request ID to cancel\n * @returns True if the request was found and cancelled\n */\n cancelAudioRequest(requestId: string): boolean {\n const pendingRequest = this.pendingAudioRequests.get(requestId)\n if (pendingRequest) {\n pendingRequest.reject(\"Audio request cancelled\")\n this.pendingAudioRequests.delete(requestId)\n this.logger.info({requestId}, `🔊 Audio request cancelled`)\n return true\n }\n return false\n }\n\n /**\n * 🧹 Cancel all pending audio requests\n * @returns Number of requests that were cancelled\n */\n cancelAllAudioRequests(): number {\n const count = this.pendingAudioRequests.size\n this.pendingAudioRequests.forEach((request, requestId) => {\n request.reject(\"Audio request cancelled due to cleanup\")\n this.logger.debug({requestId}, `🔊 Audio request cancelled during cleanup`)\n })\n this.pendingAudioRequests.clear()\n\n if (count > 0) {\n this.logger.info({cancelledCount: count}, `🧹 Cancelled all pending audio requests`)\n }\n\n return count\n }\n\n // =====================================\n // 🔧 Internal Management\n // =====================================\n\n /**\n * 🔄 Update the session ID when reconnecting\n * @param newSessionId - The new session ID\n * @internal Used by AppSession during reconnection\n */\n updateSessionId(newSessionId: string): void {\n this.sessionId = newSessionId\n this.logger.debug({newSessionId}, `🔄 Audio module session ID updated`)\n }\n\n /**\n * 🧹 Cancel all pending requests (cleanup)\n * @returns Object with count of cancelled requests\n * @internal Used by AppSession during cleanup\n */\n cancelAllRequests(): {audioRequests: number} {\n const audioRequests = this.cancelAllAudioRequests()\n return {audioRequests}\n }\n}\n",
54
+ "import { locationWarnLog } from \"../../../utils/permissions-utils\";\n\nimport { AppSession } from \"..\";\nimport { AppToCloudMessageType, LocationUpdate, LocationStreamRequest } from \"../../../types\";\n\nexport class LocationManager {\n private session: AppSession;\n private lastLocationCleanupHandler: () => void = () => {};\n\n constructor(session: AppSession) {\n this.session = session;\n }\n\n // subscribes to the continuous location stream with a specified accuracy tier\n public subscribeToStream(\n options: {\n accuracy:\n | \"standard\"\n | \"high\"\n | \"realtime\"\n | \"tenMeters\"\n | \"hundredMeters\"\n | \"kilometer\"\n | \"threeKilometers\"\n | \"reduced\";\n },\n handler: (data: LocationUpdate) => void,\n ): () => void {\n //Checking for permission location from dev console:\n locationWarnLog(\n this.session.getHttpsServerUrl() || \"\",\n this.session.getPackageName(),\n this.subscribeToStream.name,\n this.session.logger,\n );\n\n const subscription: LocationStreamRequest = {\n stream: \"location_stream\",\n rate: options.accuracy,\n };\n this.session.subscribe(subscription);\n this.lastLocationCleanupHandler = this.session.events.onLocation(handler);\n return this.lastLocationCleanupHandler;\n }\n\n // unsubscribes from the continuous location stream\n public unsubscribeFromStream(): void {\n if (this.lastLocationCleanupHandler) {\n this.lastLocationCleanupHandler();\n this.lastLocationCleanupHandler = () => {};\n } else {\n this.session.unsubscribe(\"location_stream\");\n }\n }\n\n // performs a one-time, intelligent poll for a location fix\n public async getLatestLocation(options: { accuracy: string }): Promise<LocationUpdate> {\n return new Promise((resolve, reject) => {\n const requestId = `poll_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;\n\n // listens for a location update with a matching correlationId\n const unsubscribe = this.session.events.on(\"location_update\", (data: LocationUpdate) => {\n if (data.correlationId === requestId) {\n unsubscribe(); // clean up the listener\n resolve(data);\n }\n });\n\n // sends the poll request message to the cloud\n this.session.sendMessage({\n type: AppToCloudMessageType.LOCATION_POLL_REQUEST,\n correlationId: requestId,\n packageName: this.session.getPackageName(),\n sessionId: this.session.getSessionId(),\n accuracy: options.accuracy,\n });\n\n // sets a timeout to prevent the promise from hanging indefinitely\n setTimeout(() => {\n unsubscribe();\n reject(\"Location poll request timed out\");\n }, 15000); // 15 second timeout\n });\n }\n}\n",
55
+ "/**\n * 📷 Camera Module\n *\n * Unified camera functionality for App Sessions.\n * Handles photo requests and livestreaming from connected glasses.\n */\n\nimport {\n PhotoRequest,\n PhotoData,\n AppToCloudMessageType,\n StreamRequest,\n StreamStopRequest,\n StreamStatus,\n isStreamStatus,\n ManagedStreamStatus,\n StreamStatusCheckResponse,\n CameraFovSetRequest,\n CameraRoiPosition,\n} from \"../../../types\";\nimport { VideoConfig, AudioConfig, StreamConfig, StreamStatusHandler } from \"../../../types/rtmp-stream\";\nimport { StreamType } from \"../../../types/streams\";\nimport { Logger } from \"pino\";\nimport { CameraManagedExtension, ManagedStreamOptions, ManagedStreamResult } from \"./camera-managed-extension\";\nimport { cameraWarnLog } from \"../../../utils/permissions-utils\";\n\n/**\n * Options for photo requests\n */\nexport interface PhotoRequestOptions {\n /** Whether to save the photo to the device gallery */\n saveToGallery?: boolean;\n /** Custom webhook URL to override the TPA's default webhookUrl */\n customWebhookUrl?: string;\n /** Authentication token for custom webhook authentication */\n authToken?: string;\n /**\n * Desired photo size. All sizes are optimized for fast transfer.\n * - small: 640x480 (VGA) - ultra-fast transfers\n * - medium: 1280x720 (720p) - good balance (default)\n * - large: 1920x1080 (1080p) - high quality\n * - full: native sensor resolution - maximum detail (slower transfer)\n */\n size?: \"small\" | \"medium\" | \"large\" | \"full\";\n /** Image compression level for upload optimization. Defaults to \"none\". */\n compress?: \"none\" | \"medium\" | \"heavy\";\n /** Controls shutter sound. Defaults to true if omitted. */\n sound?: boolean;\n}\n\n/**\n * Configuration options for a stream (RTMP, SRT, or WHIP)\n */\nexport interface StreamOptions {\n /** The stream URL to stream to. Supports rtmp://, rtmps://, srt://, and https:// (WHIP) protocols.\n * @example \"rtmp://server.example.com/live/stream-key\"\n * @example \"srt://server.example.com:4201?streamid=your-stream-id\"\n * @example \"https://server.example.com/whip/endpoint\"\n */\n streamUrl: string;\n /** Optional video configuration settings */\n video?: VideoConfig;\n /** Optional audio configuration settings */\n audio?: AudioConfig;\n /** Optional stream configuration settings */\n stream?: StreamConfig;\n /** Controls stream start/stop sounds. Defaults to true if omitted. */\n sound?: boolean;\n}\n\n/**\n * Options for setting the camera FOV and ROI position\n */\nexport interface CameraFovOptions {\n /** Field of view in degrees (82-118). 118 means full sensor, no crop. */\n fov: number;\n /** ROI crop position. Ignored when fov is 118. Defaults to \"center\". */\n roiPosition?: CameraRoiPosition;\n}\n\nconst VALID_ROI_POSITIONS: CameraRoiPosition[] = [\"center\", \"top\", \"bottom\"];\n\n/**\n * 📷 Camera Module Implementation\n *\n * Unified camera management for App Sessions.\n * Provides methods for:\n * - 📸 Requesting photos from glasses\n * - 📹 Starting/stopping livestreams\n * - 🔍 Monitoring photo and stream status\n * - 🧹 Cleanup and cancellation\n *\n * @example\n * ```typescript\n * // Request a photo\n * const photoData = await session.camera.requestPhoto({ saveToGallery: true });\n *\n * // Start a livestream (managed, WebRTC by default)\n * const urls = await session.camera.startLivestream();\n *\n * // Start a local livestream (unmanaged, to your own server)\n * await session.camera.startLocalLivestream({ streamUrl: 'srt://192.168.1.100:4201' });\n *\n * // Monitor stream status\n * session.camera.onLocalLivestreamStatus((status) => {\n * console.log('Stream status:', status.status);\n * });\n * ```\n */\nexport class CameraModule {\n private session: any; // Reference to AppSession\n private packageName: string;\n private sessionId: string;\n private logger: Logger;\n\n // Photo functionality\n // NOTE: Pending photo requests are now stored at AppServer level, not here.\n // This allows O(1) lookup when HTTP responses arrive and survives session reconnections.\n // See: cloud/issues/019-sdk-photo-request-architecture\n\n // Streaming functionality\n private isStreaming: boolean = false;\n private currentStreamUrl?: string;\n private currentStreamState?: StreamStatus;\n\n // Managed streaming extension\n private managedExtension: CameraManagedExtension;\n\n /**\n * Create a new CameraModule\n *\n * @param session - Reference to the parent AppSession\n * @param packageName - The App package name\n * @param sessionId - The current session ID\n * @param logger - Logger instance for debugging\n */\n constructor(session: any, packageName: string, sessionId: string, logger?: Logger) {\n this.session = session;\n this.packageName = packageName;\n this.sessionId = sessionId;\n this.logger = logger || (console as any);\n\n // Initialize managed extension\n this.managedExtension = new CameraManagedExtension(session, packageName, sessionId, this.logger);\n }\n\n // =====================================\n // 📸 Photo Functionality\n // =====================================\n\n /**\n * 📸 Request a photo from the connected glasses\n *\n * @param options - Optional configuration for the photo request\n * @returns Promise that resolves with the actual photo data\n *\n * @example\n * ```typescript\n * // Request a photo\n * const photo = await session.camera.requestPhoto();\n *\n * // Request a photo with custom webhook URL and authentication\n * const photo = await session.camera.requestPhoto({\n * customWebhookUrl: 'https://my-custom-endpoint.com/photo-upload',\n * authToken: 'your-auth-token-here'\n * });\n * ```\n */\n async requestPhoto(options?: PhotoRequestOptions): Promise<PhotoData> {\n return new Promise((resolve, reject) => {\n const baseUrl = this.session?.getHttpsServerUrl?.() || \"\";\n cameraWarnLog(baseUrl, this.packageName, \"requestPhoto\");\n try {\n // Generate unique request ID\n const requestId = `photo_req_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;\n\n // Register the photo request at AppServer level (single source of truth)\n // This allows O(1) lookup when HTTP response arrives and survives session reconnections\n // See: cloud/issues/019-sdk-photo-request-architecture\n this.session.appServer.registerPhotoRequest(requestId, {\n userId: this.session.userId,\n sessionId: this.sessionId,\n session: this.session,\n resolve,\n reject: (error: Error) => reject(error.message),\n timestamp: Date.now(),\n });\n\n // Create photo request message\n const message: PhotoRequest = {\n type: AppToCloudMessageType.PHOTO_REQUEST,\n packageName: this.packageName,\n sessionId: this.sessionId,\n requestId,\n timestamp: new Date(),\n saveToGallery: options?.saveToGallery || false,\n customWebhookUrl: options?.customWebhookUrl,\n authToken: options?.authToken,\n size: options?.size || \"medium\",\n compress: options?.compress || \"none\",\n sound: options?.sound,\n };\n\n // Send request to cloud\n this.session.sendMessage(message);\n\n this.logger.info(\n {\n requestId,\n saveToGallery: options?.saveToGallery,\n hasCustomWebhook: !!options?.customWebhookUrl,\n hasAuthToken: !!options?.authToken,\n },\n `📸 Photo request sent`,\n );\n\n // If using custom webhook URL, resolve immediately since photo will be uploaded directly to custom endpoint\n if (options?.customWebhookUrl) {\n this.logger.info(\n { requestId, customWebhookUrl: options.customWebhookUrl },\n `📸 Using custom webhook URL - resolving promise immediately since photo will be uploaded directly to custom endpoint`,\n );\n\n // Complete the request at AppServer level and resolve with mock data\n const pending = this.session.appServer.completePhotoRequest(requestId);\n if (pending) {\n const mockPhotoData: PhotoData = {\n buffer: Buffer.from([]), // Empty buffer since we don't have the actual photo\n mimeType: \"image/jpeg\",\n filename: \"photo.jpg\",\n requestId,\n size: 0,\n timestamp: new Date(),\n };\n pending.resolve(mockPhotoData);\n }\n return;\n }\n\n // Timeout is now handled at AppServer level in registerPhotoRequest()\n } catch (error: unknown) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n reject(`Failed to request photo: ${errorMessage}`);\n }\n });\n }\n\n // NOTE: handlePhotoReceived() and handlePhotoError() have been removed.\n // Photo responses are now handled directly by AppServer's /photo-upload endpoint,\n // which resolves/rejects the promise stored in AppServer.pendingPhotoRequests.\n // See: cloud/issues/019-sdk-photo-request-architecture\n\n /**\n * 🔍 Check if there's a pending photo request for the given request ID\n * @deprecated Photo requests are now managed at AppServer level. This method delegates to AppServer.\n *\n * @param requestId - The request ID to check\n * @returns true if there's a pending request\n */\n hasPhotoPendingRequest(requestId: string): boolean {\n return this.session.appServer.getPhotoRequest(requestId) !== undefined;\n }\n\n /**\n * ❌ Cancel a pending photo request\n * @deprecated Photo requests are now managed at AppServer level. This method delegates to AppServer.\n *\n * @param requestId - The request ID to cancel\n * @returns true if the request was found and cancelled\n */\n cancelPhotoRequest(requestId: string): boolean {\n const pending = this.session.appServer.completePhotoRequest(requestId);\n if (pending) {\n pending.reject(new Error(\"Photo request cancelled\"));\n this.logger.debug({ requestId }, \"Photo request cancelled\");\n return true;\n }\n return false;\n }\n\n /**\n * 🧹 Cancel all pending photo requests for this session\n * @deprecated Photo requests are now managed at AppServer level. Use AppServer.cleanupPhotoRequestsForSession() instead.\n *\n * @returns Number of requests that were cancelled (always 0, cleanup happens at AppServer level)\n */\n cancelAllPhotoRequests(): number {\n // Photo request cleanup is now handled by AppServer.cleanupPhotoRequestsForSession()\n // which is called when the session permanently disconnects\n this.logger.debug(\"cancelAllPhotoRequests called — cleanup now happens at AppServer level\");\n return 0;\n }\n\n // =====================================\n // 🔭 FOV / ROI Control\n // =====================================\n\n /**\n * 🔭 Set the camera field-of-view and ROI crop position\n *\n * Fire-and-forget: the promise resolves once the message is sent.\n * The phone applies the setting and pushes it to the glasses over BLE.\n *\n * @param options - FOV (82-118) and optional ROI position\n *\n * @example\n * ```typescript\n * // Narrow crop, looking at the top of the frame\n * await session.camera.setFov({ fov: 92, roiPosition: \"top\" });\n *\n * // Full sensor, no crop (roiPosition is ignored)\n * await session.camera.setFov({ fov: 118 });\n * ```\n */\n async setFov(options: CameraFovOptions): Promise<void> {\n const { fov } = options;\n let roiPosition: CameraRoiPosition = options.roiPosition ?? \"center\";\n\n if (fov < 82 || fov > 118) {\n throw new Error(`fov must be between 82 and 118, got ${fov}`);\n }\n\n if (!VALID_ROI_POSITIONS.includes(roiPosition)) {\n throw new Error(`roiPosition must be one of ${VALID_ROI_POSITIONS.join(\", \")}, got \"${roiPosition}\"`);\n }\n\n if (fov === 118) {\n roiPosition = \"center\";\n }\n\n const requestId = `cam_fov_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;\n\n const message: CameraFovSetRequest = {\n type: AppToCloudMessageType.CAMERA_FOV_SET,\n packageName: this.packageName,\n sessionId: this.sessionId,\n requestId,\n fov,\n roiPosition,\n timestamp: new Date(),\n };\n\n this.session.sendMessage(message);\n this.logger.info({ fov, roiPosition, requestId }, \"🔭 Camera FOV set request sent\");\n }\n\n // =====================================\n // 📹 Local Livestream (Unmanaged)\n // =====================================\n\n /**\n * 📹 Start a local livestream to your own server (supports SRT, RTMP, WHIP)\n *\n * Use this when streaming to a server you control (local network or remote).\n * For managed streaming with automatic WebRTC playback, use `startLivestream()` instead.\n *\n * @param options - Configuration options including the stream URL\n * @returns Promise that resolves when the stream request is sent (not when streaming begins)\n *\n * @example\n * ```typescript\n * await session.camera.startLocalLivestream({\n * streamUrl: 'srt://192.168.1.100:4201?streamid=my-stream',\n * });\n * ```\n */\n async startLocalLivestream(options: StreamOptions): Promise<void> {\n this.logger.info({ streamUrl: options.streamUrl }, `📹 Stream request starting`);\n\n cameraWarnLog(this.session.getHttpsServerUrl?.(), this.packageName, \"startLocalLivestream\");\n\n if (!options.streamUrl) {\n throw new Error(\"streamUrl is required\");\n }\n\n const url = options.streamUrl;\n if (!url.startsWith(\"rtmp://\") && !url.startsWith(\"rtmps://\") && !url.startsWith(\"srt://\") && !url.startsWith(\"https://\") && !url.startsWith(\"http://\")) {\n throw new Error(\"Invalid stream URL: must start with rtmp://, rtmps://, srt://, https://, or http://\");\n }\n\n if (this.isStreaming) {\n this.logger.error(\n {\n currentStreamUrl: this.currentStreamUrl,\n requestedUrl: options.streamUrl,\n },\n `📹 Already streaming error`,\n );\n throw new Error(\"Already streaming. Stop the current stream before starting a new one.\");\n }\n\n // Create stream request message\n const message: StreamRequest = {\n type: AppToCloudMessageType.STREAM_REQUEST,\n packageName: this.packageName,\n sessionId: this.sessionId,\n streamUrl: options.streamUrl,\n video: options.video,\n audio: options.audio,\n stream: options.stream,\n sound: options.sound,\n timestamp: new Date(),\n };\n\n // Save stream URL for reference\n this.currentStreamUrl = options.streamUrl;\n\n // Send the request\n try {\n this.session.sendMessage(message);\n this.isStreaming = true;\n\n this.logger.info({ streamUrl: options.streamUrl }, `📹 Stream request sent successfully`);\n return Promise.resolve();\n } catch (error) {\n this.logger.error({ error, streamUrl: options.streamUrl }, `📹 Failed to send stream request`);\n const errorMessage = error instanceof Error ? error.message : String(error);\n return Promise.reject(`Failed to request stream: ${errorMessage}`);\n }\n }\n\n /**\n * 🛑 Stop the current local livestream\n *\n * @returns Promise that resolves when the stop request is sent\n *\n * @example\n * ```typescript\n * await session.camera.stopLocalLivestream();\n * ```\n */\n async stopLocalLivestream(): Promise<void> {\n this.logger.info(\n {\n isCurrentlyStreaming: this.isStreaming,\n currentStreamUrl: this.currentStreamUrl,\n },\n `📹 Stream stop request`,\n );\n\n if (!this.isStreaming) {\n this.logger.info(`📹 Not streaming - no-op`);\n // Not an error - just a no-op if not streaming\n return Promise.resolve();\n }\n\n // Create stop request message\n const message: StreamStopRequest = {\n type: AppToCloudMessageType.STREAM_STOP,\n packageName: this.packageName,\n sessionId: this.sessionId,\n streamId: this.currentStreamState?.streamId, // Include streamId if available\n timestamp: new Date(),\n };\n\n // Send the request\n try {\n this.session.sendMessage(message);\n return Promise.resolve();\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n return Promise.reject(`Failed to stop RTMP stream: ${errorMessage}`);\n }\n }\n\n /**\n * 🔍 Check if currently streaming\n *\n * @returns True if a stream is active or initializing\n */\n isCurrentlyStreaming(): boolean {\n return this.isStreaming;\n }\n\n /**\n * 📍 Get the URL of the current stream (if any)\n *\n * @returns The RTMP URL of the current stream, or undefined if not streaming\n */\n getCurrentStreamUrl(): string | undefined {\n return this.currentStreamUrl;\n }\n\n /**\n * 📊 Get the current stream status\n *\n * @returns The current stream status, or undefined if not available\n */\n getStreamStatus(): StreamStatus | undefined {\n return this.currentStreamState;\n }\n\n /**\n * 📺 Subscribe to RTMP stream status updates\n * This uses the standard stream subscription mechanism\n */\n subscribeToStreamStatusUpdates(): void {\n if (this.session) {\n this.session.subscribe(StreamType.STREAM_STATUS);\n } else {\n this.logger.error(\"Cannot subscribe to status updates: session reference not available\");\n }\n }\n\n /**\n * 📺 Unsubscribe from RTMP stream status updates\n */\n unsubscribeFromStreamStatusUpdates(): void {\n if (this.session) {\n this.session.unsubscribe(StreamType.STREAM_STATUS);\n }\n }\n\n /**\n * 👂 Listen for local livestream status updates\n * @param handler - Function to call when stream status changes\n * @returns Cleanup function to remove the handler\n *\n * @example\n * ```typescript\n * const cleanup = session.camera.onLocalLivestreamStatus((status) => {\n * console.log('Stream status:', status.status);\n * if (status.status === 'error') {\n * console.error('Stream error:', status.errorDetails);\n * }\n * });\n *\n * // Later, cleanup the listener\n * cleanup();\n * ```\n */\n onLocalLivestreamStatus(handler: StreamStatusHandler): () => void {\n if (!this.session) {\n this.logger.error(\"Cannot listen for status updates: session reference not available\");\n return () => {};\n }\n\n this.subscribeToStreamStatusUpdates();\n return this.session.on(StreamType.STREAM_STATUS, handler);\n }\n\n /**\n * 🔄 Update internal stream state based on a status message\n * For internal use by AppSession\n * @param message - The status message from the cloud\n * @internal This method is used internally by AppSession\n */\n updateStreamState(message: any): void {\n this.logger.debug(\n {\n messageType: message?.type,\n messageStatus: message?.status,\n currentIsStreaming: this.isStreaming,\n },\n `📹 Stream state update`,\n );\n\n // Verify this is a valid stream response\n if (!isStreamStatus(message)) {\n this.logger.warn({ message }, `📹 Received invalid stream status message`);\n return;\n }\n\n // Convert to StreamStatus format\n const status: StreamStatus = {\n type: message.type,\n streamId: message.streamId,\n status: message.status,\n errorDetails: message.errorDetails,\n appId: message.appId,\n stats: message.stats,\n timestamp: message.timestamp || new Date(),\n };\n\n this.logger.info(\n {\n streamId: status.streamId,\n oldStatus: this.currentStreamState?.status,\n newStatus: status.status,\n wasStreaming: this.isStreaming,\n },\n `📹 Stream status processed`,\n );\n\n // Update local state based on status\n if (status.status === \"stopped\" || status.status === \"error\" || status.status === \"timeout\") {\n this.logger.info(\n {\n status: status.status,\n wasStreaming: this.isStreaming,\n },\n `📹 Stream stopped - updating local state`,\n );\n this.isStreaming = false;\n this.currentStreamUrl = undefined;\n }\n\n // Save the latest status\n this.currentStreamState = status;\n }\n\n // =====================================\n // 📹 Livestream (Managed)\n // =====================================\n\n /**\n * 📹 Start a livestream\n *\n * Managed by Mentra cloud. Returns playback URLs automatically.\n * By default uses WebRTC for sub-second latency. If restreamDestinations\n * are provided, switches to SRT ingest with HLS/DASH playback.\n * Multiple miniapps can consume the same livestream simultaneously.\n *\n * @param options - Configuration options for the livestream\n * @returns Promise that resolves with playback URLs when the stream is ready\n *\n * @example\n * ```typescript\n * // Default: WebRTC (low latency)\n * const urls = await session.camera.startLivestream();\n * console.log('WebRTC URL:', urls.webrtcUrl);\n *\n * // With restream destinations: SRT + HLS/DASH\n * const urls = await session.camera.startLivestream({\n * restreamDestinations: [{ url: 'rtmp://...', name: 'YouTube' }]\n * });\n * console.log('HLS URL:', urls.hlsUrl);\n * ```\n */\n async startLivestream(options?: ManagedStreamOptions): Promise<ManagedStreamResult> {\n return this.managedExtension.startManagedStream(options);\n }\n\n /**\n * 🛑 Stop the current livestream\n *\n * This will stop streaming for this miniapp only. If other miniapps are consuming\n * the same livestream, it will continue for them.\n *\n * @returns Promise that resolves when the stop request is sent\n */\n async stopLivestream(): Promise<void> {\n return this.managedExtension.stopManagedStream();\n }\n\n /**\n * 🔔 Register a handler for livestream status updates\n *\n * @param handler - Function to call when stream status changes\n * @returns Cleanup function to unregister the handler\n */\n onLivestreamStatus(handler: (status: ManagedStreamStatus) => void): () => void {\n return this.managedExtension.onManagedStreamStatus(handler);\n }\n\n /**\n * 📊 Check if a livestream is active\n *\n * @returns true if a livestream is active\n */\n isLivestreamActive(): boolean {\n return this.managedExtension.isManagedStreamActive();\n }\n\n /**\n * 🔗 Get current livestream URLs\n *\n * @returns Current stream URLs or undefined if not streaming\n */\n getLivestreamUrls(): ManagedStreamResult | undefined {\n return this.managedExtension.getManagedStreamUrls();\n }\n\n // =====================================\n // 🔄 Deprecated aliases (old method names)\n // =====================================\n\n /** @deprecated Use `startLivestream()` instead */\n async startManagedStream(options?: ManagedStreamOptions): Promise<ManagedStreamResult> {\n return this.startLivestream(options);\n }\n /** @deprecated Use `stopLivestream()` instead */\n async stopManagedStream(): Promise<void> {\n return this.stopLivestream();\n }\n /** @deprecated Use `onLivestreamStatus()` instead */\n onManagedStreamStatus(handler: (status: ManagedStreamStatus) => void): () => void {\n return this.onLivestreamStatus(handler);\n }\n /** @deprecated Use `isLivestreamActive()` instead */\n isManagedStreamActive(): boolean {\n return this.isLivestreamActive();\n }\n /** @deprecated Use `getLivestreamUrls()` instead */\n getManagedStreamUrls(): ManagedStreamResult | undefined {\n return this.getLivestreamUrls();\n }\n /** @deprecated Use `startLocalLivestream()` instead */\n async startStream(options: StreamOptions): Promise<void> {\n return this.startLocalLivestream(options);\n }\n /** @deprecated Use `stopLocalLivestream()` instead */\n async stopStream(): Promise<void> {\n return this.stopLocalLivestream();\n }\n /** @deprecated Use `onLocalLivestreamStatus()` instead */\n onStreamStatus(handler: StreamStatusHandler): () => void {\n return this.onLocalLivestreamStatus(handler);\n }\n\n /**\n * 🔍 Check for any existing streams (managed or unmanaged) for the current user\n *\n * This method checks if there's already an active stream for the current user,\n * which is useful to avoid conflicts and to reconnect to existing streams.\n *\n * @returns Promise that resolves with stream information if a stream exists\n *\n * @example\n * ```typescript\n * const streamInfo = await session.camera.checkExistingStream();\n * if (streamInfo.hasActiveStream) {\n * console.log('Stream type:', streamInfo.streamInfo?.type);\n * if (streamInfo.streamInfo?.type === 'managed') {\n * console.log('HLS URL:', streamInfo.streamInfo.hlsUrl);\n * } else {\n * console.log('Stream URL:', streamInfo.streamInfo.streamUrl);\n * }\n * }\n * ```\n */\n async checkExistingStream(): Promise<{\n hasActiveStream: boolean;\n streamInfo?: {\n type: \"managed\" | \"unmanaged\";\n streamId: string;\n status: string;\n createdAt: Date;\n // For managed streams\n hlsUrl?: string;\n dashUrl?: string;\n webrtcUrl?: string;\n previewUrl?: string;\n thumbnailUrl?: string;\n activeViewers?: number;\n // For unmanaged streams\n streamUrl?: string;\n requestingAppId?: string;\n };\n }> {\n return this.managedExtension.checkExistingStream();\n }\n\n /**\n * Handle incoming stream status check response\n * @internal\n */\n handleStreamCheckResponse(response: StreamStatusCheckResponse): void {\n this.managedExtension.handleStreamCheckResponse(response);\n }\n\n /**\n * Handle incoming managed stream status messages\n * @internal\n */\n handleManagedStreamStatus(message: ManagedStreamStatus): void {\n this.managedExtension.handleManagedStreamStatus(message);\n }\n\n // =====================================\n // 🔧 General Utilities\n // =====================================\n\n /**\n * 🔧 Update the session ID (used when reconnecting)\n *\n * @param newSessionId - The new session ID\n * @internal This method is used internally by AppSession\n */\n updateSessionId(newSessionId: string): void {\n this.sessionId = newSessionId;\n }\n\n /**\n * 🧹 Cancel all pending requests and clean up resources\n *\n * @returns Object with counts of cancelled requests\n */\n cancelAllRequests(): { photoRequests: number } {\n const photoRequests = this.cancelAllPhotoRequests();\n\n // Stop streaming if active\n if (this.isStreaming) {\n this.stopLocalLivestream().catch((error) => {\n this.logger.error({ error }, \"Error stopping stream during cleanup\");\n });\n }\n\n // Clean up managed extension\n this.managedExtension.cleanup();\n\n return { photoRequests };\n }\n}\n\n// Re-export types for convenience\nexport type { VideoConfig, AudioConfig, StreamConfig, StreamStatusHandler };\n",
56
+ "/**\n * 📷 Camera Module Managed Streaming Extension\n *\n * Extends the camera module with managed streaming capabilities.\n * Apps can request managed streams and receive HLS/DASH URLs without managing RTMP endpoints.\n */\n\nimport {\n ManagedStreamRequest,\n ManagedStreamStopRequest,\n ManagedStreamStatus,\n StreamStatusCheckRequest,\n StreamStatusCheckResponse,\n AppToCloudMessageType,\n StreamType,\n RestreamDestination,\n} from \"../../../types\";\nimport { VideoConfig, AudioConfig, StreamConfig } from \"../../../types/rtmp-stream\";\nimport { Logger } from \"pino\";\n\n/**\n * Configuration options for a managed stream.\n * By default, managed streams use WebRTC (WHIP ingest → WHEP playback) for low latency.\n * If restreamDestinations are provided, the stream automatically falls back to\n * SRT ingest with HLS/DASH playback (required for RTMP fan-out).\n */\nexport interface ManagedStreamOptions {\n /** Optional video configuration settings */\n video?: VideoConfig;\n /** Optional audio configuration settings */\n audio?: AudioConfig;\n /** Optional stream configuration settings */\n stream?: StreamConfig;\n /** Optional RTMP destinations to re-stream to (YouTube, Twitch, etc).\n * When present, stream uses SRT ingest + HLS/DASH playback instead of WebRTC. */\n restreamDestinations?: RestreamDestination[];\n /** Controls stream start/stop sounds. Defaults to true if omitted. */\n sound?: boolean;\n}\n\n/**\n * Result returned when starting a managed stream.\n * In WebRTC mode (default): use webrtcUrl for low-latency playback.\n * In SRT mode (when restreamDestinations provided): use hlsUrl/dashUrl for playback.\n */\nexport interface ManagedStreamResult {\n /** HLS URL for viewing the stream (functional in SRT mode only) */\n hlsUrl: string;\n /** DASH URL for viewing the stream (functional in SRT mode only) */\n dashUrl: string;\n /** WebRTC (WHEP) URL for low-latency playback (functional in WebRTC mode, the default) */\n webrtcUrl?: string;\n /** Cloudflare Stream player/preview URL for embedding */\n previewUrl?: string;\n /** Thumbnail image URL */\n thumbnailUrl?: string;\n /** Internal stream ID */\n streamId: string;\n}\n\n/**\n * 📹 Managed Streaming Extension for Camera Module\n *\n * Provides managed streaming capabilities where the cloud handles\n * ingest and returns playback URLs.\n *\n * @example\n * ```typescript\n * // Start a livestream (WebRTC by default for low latency)\n * const urls = await session.camera.startLivestream();\n * console.log('WebRTC URL:', urls.webrtcUrl);\n *\n * // Or with restream destinations (uses SRT + HLS/DASH instead)\n * const urls = await session.camera.startLivestream({\n * restreamDestinations: [{ url: 'rtmp://...', name: 'YouTube' }]\n * });\n * console.log('HLS URL:', urls.hlsUrl);\n *\n * // Monitor livestream status\n * session.camera.onLivestreamStatus((status) => {\n * console.log('Livestream status:', status.status);\n * });\n *\n * // Stop livestream\n * await session.camera.stopLivestream();\n * ```\n */\nexport class CameraManagedExtension {\n private session: any;\n private packageName: string;\n private sessionId: string;\n private logger: Logger;\n\n // Managed streaming state\n private isManagedStreaming: boolean = false;\n private currentManagedStreamId?: string;\n private currentManagedStreamUrls?: ManagedStreamResult;\n private managedStreamStatus?: ManagedStreamStatus;\n\n // For tracking pending stream check requests\n private pendingStreamChecks?: Map<\n string,\n {\n resolve: (value: any) => void;\n timeoutId: NodeJS.Timeout;\n }\n >;\n\n // Promise tracking for managed stream initialization\n private pendingManagedStreamRequest?: {\n resolve: (value: ManagedStreamResult) => void;\n reject: (reason?: any) => void;\n };\n\n constructor(session: any, packageName: string, sessionId: string, logger: Logger) {\n this.session = session;\n this.packageName = packageName;\n this.sessionId = sessionId;\n this.logger = logger.child({ module: \"CameraManagedExtension\" });\n }\n\n /**\n * 📹 Start a managed stream\n *\n * The cloud will handle the RTMP endpoint and return HLS/DASH URLs for viewing.\n * Multiple apps can consume the same managed stream simultaneously.\n *\n * @param options - Configuration options for the managed stream\n * @returns Promise that resolves with viewing URLs when the stream is ready\n *\n * @example\n * ```typescript\n * // Default: WebRTC for low latency\n * const urls = await session.camera.startLivestream({\n * video: { frameRate: 30 },\n * audio: { sampleRate: 48000 }\n * });\n * console.log('WebRTC URL:', urls.webrtcUrl);\n *\n * // With restream: falls back to SRT + HLS/DASH\n * const urls = await session.camera.startLivestream({\n * restreamDestinations: [{ url: 'rtmp://...', name: 'YouTube' }]\n * });\n * console.log('HLS URL:', urls.hlsUrl);\n * ```\n */\n async startManagedStream(options: ManagedStreamOptions = {}): Promise<ManagedStreamResult> {\n this.logger.info({ options }, \"Managed stream request starting\");\n\n if (this.isManagedStreaming) {\n this.logger.error(\n {\n currentStreamId: this.currentManagedStreamId,\n },\n \"Already managed streaming error\",\n );\n throw new Error(\"Already streaming. Stop the current managed stream before starting a new one.\");\n }\n\n // Create the request message\n const request: ManagedStreamRequest = {\n type: AppToCloudMessageType.MANAGED_STREAM_REQUEST,\n packageName: this.packageName,\n video: options.video,\n audio: options.audio,\n stream: options.stream,\n restreamDestinations: options.restreamDestinations,\n sound: options.sound,\n };\n\n // Send the request\n this.session.sendMessage(request);\n this.isManagedStreaming = true;\n\n // Create promise to wait for URLs\n return new Promise((resolve, reject) => {\n this.pendingManagedStreamRequest = { resolve, reject };\n\n // Set a timeout\n setTimeout(() => {\n if (this.pendingManagedStreamRequest) {\n this.pendingManagedStreamRequest = undefined;\n this.isManagedStreaming = false;\n reject(new Error(\"Managed stream request timeout\"));\n }\n }, 30000); // 30 second timeout\n });\n }\n\n /**\n * 🛑 Stop the current managed stream\n *\n * This will stop streaming for this app only. If other apps are consuming\n * the same managed stream, it will continue for them.\n *\n * @returns Promise that resolves when the stop request is sent\n */\n async stopManagedStream(): Promise<void> {\n // Always send the stop request - the cloud will handle whether there's actually\n // a stream to stop. This ensures the stop works even after reload/reconnection\n this.logger.debug(\n {\n streamId: this.currentManagedStreamId,\n hasInternalState: this.isManagedStreaming,\n },\n \"Sending managed stream stop request\",\n );\n\n const request: ManagedStreamStopRequest = {\n type: AppToCloudMessageType.MANAGED_STREAM_STOP,\n packageName: this.packageName,\n };\n\n this.session.sendMessage(request);\n\n // Don't clean up state immediately - wait for the 'stopped' status from cloud\n // This ensures we can retry stop if needed and maintains accurate state\n }\n\n /**\n * 🔍 Check for any existing streams (managed or unmanaged) for the current user\n *\n * @returns Promise that resolves with stream information if a stream exists\n *\n * @example\n * ```typescript\n * const streamInfo = await session.camera.checkExistingStream();\n * if (streamInfo.hasActiveStream) {\n * console.log('Stream type:', streamInfo.streamInfo?.type);\n * if (streamInfo.streamInfo?.type === 'managed') {\n * console.log('HLS URL:', streamInfo.streamInfo.hlsUrl);\n * }\n * }\n * ```\n */\n async checkExistingStream(): Promise<{\n hasActiveStream: boolean;\n streamInfo?: {\n type: \"managed\" | \"unmanaged\";\n streamId: string;\n status: string;\n createdAt: Date;\n // For managed streams\n hlsUrl?: string;\n dashUrl?: string;\n webrtcUrl?: string;\n previewUrl?: string;\n thumbnailUrl?: string;\n activeViewers?: number;\n // For unmanaged streams\n streamUrl?: string;\n requestingAppId?: string;\n };\n }> {\n return new Promise((resolve) => {\n // Store the resolver for the response\n const requestId = `stream_check_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;\n\n // Store a pending request that will be resolved when we get the response\n if (!this.pendingStreamChecks) {\n this.pendingStreamChecks = new Map();\n }\n\n const timeoutId = setTimeout(() => {\n this.pendingStreamChecks?.delete(requestId);\n resolve({ hasActiveStream: false });\n }, 5000); // 5 second timeout\n\n this.pendingStreamChecks.set(requestId, {\n resolve,\n timeoutId,\n });\n\n // Send the check request with the requestId\n const request: StreamStatusCheckRequest = {\n type: AppToCloudMessageType.STREAM_STATUS_CHECK,\n packageName: this.packageName,\n sessionId: this.sessionId,\n };\n\n this.session.sendMessage(request);\n });\n }\n\n /**\n * 📊 Check if currently managed streaming\n *\n * @returns true if a managed stream is active\n */\n isManagedStreamActive(): boolean {\n return this.isManagedStreaming;\n }\n\n /**\n * 🔗 Get current managed stream URLs\n *\n * @returns Current stream URLs or undefined if not streaming\n */\n getManagedStreamUrls(): ManagedStreamResult | undefined {\n return this.currentManagedStreamUrls;\n }\n\n /**\n * 📊 Get current managed stream status\n *\n * @returns Current stream status or undefined\n */\n getManagedStreamStatus(): ManagedStreamStatus | undefined {\n return this.managedStreamStatus;\n }\n\n /**\n * 🔔 Register a handler for managed stream status updates\n *\n * @param handler - Function to call when stream status changes\n * @returns Cleanup function to unregister the handler\n *\n * @example\n * ```typescript\n * const cleanup = session.camera.onLivestreamStatus((status) => {\n * console.log('Status:', status.status);\n * if (status.status === 'active') {\n * console.log('Stream is live!');\n * }\n * });\n *\n * // Later, unregister the handler\n * cleanup();\n * ```\n */\n onManagedStreamStatus(handler: (status: ManagedStreamStatus) => void): () => void {\n if (!this.session) {\n this.logger.error(\"Cannot listen for managed status updates: session reference not available\");\n return () => {};\n }\n\n this.session.subscribe(StreamType.MANAGED_STREAM_STATUS);\n\n // Register the handler using the session's event system\n return this.session.on(StreamType.MANAGED_STREAM_STATUS, handler);\n }\n\n /**\n * Handle incoming stream status check response\n * Called by the parent AppSession when a response is received\n */\n handleStreamCheckResponse(response: StreamStatusCheckResponse): void {\n // Find and resolve any pending stream check\n if (this.pendingStreamChecks && this.pendingStreamChecks.size > 0) {\n const firstEntry = this.pendingStreamChecks.entries().next();\n if (!firstEntry.done && firstEntry.value) {\n const [requestId, pending] = firstEntry.value;\n if (pending) {\n clearTimeout(pending.timeoutId);\n this.pendingStreamChecks.delete(requestId);\n pending.resolve(response);\n }\n }\n }\n }\n\n /**\n * Handle incoming managed stream status messages\n * Called by the parent AppSession when messages are received\n */\n handleManagedStreamStatus(status: ManagedStreamStatus): void {\n this.logger.debug(\n {\n status: status.status,\n streamId: status.streamId,\n },\n \"Received managed stream status\",\n );\n\n this.managedStreamStatus = status;\n\n // Handle initializing status - stream is starting\n if (status.status === \"initializing\" && status.streamId) {\n this.isManagedStreaming = true;\n this.currentManagedStreamId = status.streamId;\n }\n\n // Handle initial stream ready status\n if (status.status === \"active\") {\n // Always update state when stream is active\n this.isManagedStreaming = true;\n this.currentManagedStreamId = status.streamId;\n\n if (status.hlsUrl && status.dashUrl) {\n const result: ManagedStreamResult = {\n hlsUrl: status.hlsUrl,\n dashUrl: status.dashUrl,\n webrtcUrl: status.webrtcUrl,\n previewUrl: status.previewUrl,\n thumbnailUrl: status.thumbnailUrl,\n streamId: status.streamId || \"\",\n };\n\n this.currentManagedStreamUrls = result;\n\n // Resolve pending promise if exists\n if (this.pendingManagedStreamRequest) {\n this.pendingManagedStreamRequest.resolve(result);\n this.pendingManagedStreamRequest = undefined;\n }\n }\n }\n\n // Handle error status\n if ((status.status === \"error\" || status.status === \"stopped\") && this.pendingManagedStreamRequest) {\n this.pendingManagedStreamRequest.reject(new Error(status.message || \"Managed stream failed\"));\n this.pendingManagedStreamRequest = undefined;\n this.isManagedStreaming = false;\n this.currentManagedStreamId = undefined;\n this.currentManagedStreamUrls = undefined;\n }\n\n // Clean up on stopped status\n if (status.status === \"stopped\") {\n this.isManagedStreaming = false;\n this.currentManagedStreamId = undefined;\n this.currentManagedStreamUrls = undefined;\n }\n\n // Clean up local state on error regardless of pending request state\n if (status.status === \"error\") {\n this.isManagedStreaming = false;\n this.currentManagedStreamId = undefined;\n this.currentManagedStreamUrls = undefined;\n }\n\n // Notify handlers (would use event emitter in real implementation)\n // this.emit('managedStreamStatus', status);\n }\n\n /**\n * 🧹 Clean up all managed streaming state\n */\n cleanup(): void {\n if (this.pendingManagedStreamRequest) {\n this.pendingManagedStreamRequest.reject(new Error(\"Camera module cleanup\"));\n this.pendingManagedStreamRequest = undefined;\n }\n\n this.isManagedStreaming = false;\n this.currentManagedStreamId = undefined;\n this.currentManagedStreamUrls = undefined;\n this.managedStreamStatus = undefined;\n\n this.logger.debug(\"Managed streaming extension cleaned up\");\n }\n}\n",
57
+ "/**\n * 💡 LED Control Module\n *\n * Provides RGB LED control functionality for App Sessions.\n * Controls the RGB LEDs on smart glasses with custom colors and timing patterns.\n */\n\nimport { RgbLedControlRequest, AppToCloudMessageType, LedColor } from \"../../../types\";\nimport { Logger } from \"pino\";\n\n/**\n * Options for LED control\n */\nexport interface LedControlOptions {\n /** LED color */\n color?: LedColor;\n /** LED on duration in milliseconds */\n ontime?: number;\n /** LED off duration in milliseconds */\n offtime?: number;\n /** Number of on/off cycles */\n count?: number;\n}\n\n/**\n * 💡 LED Control Module Implementation\n *\n * Provides methods for controlling LEDs on smart glasses.\n * Supports both low-level on/off control and higher-level pattern methods.\n *\n * @example\n * ```typescript\n * // General LED control\n * await session.led.turnOn({ color: 'red', ontime: 1000, count: 1 });\n *\n * // Blink green LED\n * await session.led.blink('green', 500, 500, 5);\n *\n * // Turn off all LEDs\n * await session.led.turnOff();\n * ```\n */\nexport class LedModule {\n private session: any;\n private packageName: string;\n private sessionId: string;\n private logger: Logger;\n\n /**\n * Create a new LedModule\n *\n * @param session - Reference to the parent AppSession\n * @param packageName - The App package name\n * @param sessionId - The current session ID\n * @param logger - Logger instance for debugging\n */\n constructor(session: any, packageName: string, sessionId: string, logger?: Logger) {\n this.session = session;\n this.packageName = packageName;\n this.sessionId = sessionId;\n this.logger = logger || (console as any);\n }\n\n // =====================================\n // 💡 Low-level LED Control\n // =====================================\n\n /**\n * 💡 Turn on an LED with specified timing parameters\n *\n * @param options - LED control options (color, timing, count)\n * @returns Promise that resolves immediately after sending the command\n *\n * @example\n * ```typescript\n * // Solid red for 2 seconds\n * await session.led.turnOn({ color: 'red', ontime: 2000, count: 1 });\n *\n * // Blink white 3 times\n * await session.led.turnOn({ color: 'white', ontime: 500, offtime: 500, count: 3 });\n * ```\n */\n async turnOn(options: LedControlOptions): Promise<void> {\n try {\n // Generate unique request ID for tracking\n const requestId = `led_req_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;\n\n // Create LED control request message\n const message: RgbLedControlRequest = {\n type: AppToCloudMessageType.RGB_LED_CONTROL,\n packageName: this.packageName,\n sessionId: this.sessionId,\n requestId,\n timestamp: new Date(),\n action: \"on\",\n color: options.color || \"red\",\n ontime: options.ontime || 1000,\n offtime: options.offtime || 0,\n count: options.count || 1,\n };\n\n // Send request to cloud (fire-and-forget)\n this.session.sendMessage(message);\n\n this.logger.debug(\n {\n requestId,\n color: options.color,\n ontime: options.ontime,\n offtime: options.offtime,\n count: options.count,\n },\n `LED control request sent`,\n );\n\n // Resolve immediately - no waiting for response\n return Promise.resolve();\n } catch (error) {\n this.logger.error({ error, options }, \"Error in LED turnOn request\");\n throw error;\n }\n }\n\n /**\n * 💡 Turn off all LEDs\n *\n * @returns Promise that resolves immediately after sending the command\n *\n * @example\n * ```typescript\n * await session.led.turnOff();\n * ```\n */\n async turnOff(): Promise<void> {\n try {\n // Generate unique request ID for tracking\n const requestId = `led_req_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;\n\n // Create LED control request message\n const message: RgbLedControlRequest = {\n type: AppToCloudMessageType.RGB_LED_CONTROL,\n packageName: this.packageName,\n sessionId: this.sessionId,\n requestId,\n timestamp: new Date(),\n action: \"off\",\n };\n\n // Send request to cloud (fire-and-forget)\n this.session.sendMessage(message);\n\n this.logger.debug({ requestId }, `LED turn off request sent`);\n\n // Resolve immediately - no waiting for response\n return Promise.resolve();\n } catch (error) {\n this.logger.error({ error }, \"Error in LED turnOff request\");\n throw error;\n }\n }\n\n /**\n * 💡 Get available LED capabilities\n *\n * @returns Array of available LED configurations\n *\n * @example\n * ```typescript\n * const capabilities = session.led.getCapabilities();\n * console.log('Available LEDs:', capabilities);\n * ```\n */\n getCapabilities(): Array<{\n id: string;\n purpose: string;\n isFullColor: boolean;\n color?: string;\n position?: string;\n }> {\n // This would need to be implemented with access to session capabilities\n // For now, return empty array - this would be populated from session.capabilities\n return [];\n }\n\n // =====================================\n // 💡 Pattern Methods (SDK-generated)\n // =====================================\n\n /**\n * 💡 Blink an LED with specified timing\n *\n * @param color - LED color to use\n * @param ontime - How long LED stays on (ms)\n * @param offtime - How long LED stays off (ms)\n * @param count - Number of blink cycles\n * @returns Promise that resolves immediately after sending the command\n *\n * @example\n * ```typescript\n * // Blink red LED 5 times (500ms on, 500ms off)\n * await session.led.blink('red', 500, 500, 5);\n * ```\n */\n async blink(color: LedColor, ontime: number, offtime: number, count: number): Promise<void> {\n return this.turnOn({\n color,\n ontime,\n offtime,\n count,\n });\n }\n\n /**\n * 💡 Solid LED mode - LED stays on continuously for specified duration\n *\n * Creates a solid LED effect with no off time, perfect for continuous illumination.\n *\n * @param color - LED color to use\n * @param duration - How long LED stays on (ms)\n * @returns Promise that resolves when the command is sent\n *\n * @example\n * ```typescript\n * // Solid red LED for 5 seconds\n * await session.led.solid('red', 5000);\n *\n * // Solid white LED for 30 seconds\n * await session.led.solid('white', 30000);\n * ```\n */\n async solid(color: LedColor, duration: number): Promise<void> {\n return this.turnOn({\n color,\n ontime: duration,\n offtime: 0, // No off time for solid mode\n count: 1, // Single cycle\n });\n }\n\n // =====================================\n // 💡 Cleanup\n // =====================================\n\n /**\n * Clean up LED module\n *\n * @internal\n */\n cleanup(): void {\n this.logger.debug(\"LED module cleaned up\");\n }\n}\n",
58
+ "/**\n * 🔊 Audio Module\n *\n * Audio functionality for App Sessions.\n * Handles audio playback and audio output streaming on connected glasses.\n */\n\nimport {AudioPlayRequest, AudioPlayResponse, AudioStopRequest, AppToCloudMessageType} from \"../../../types\"\nimport {Logger} from \"pino\"\nimport {AudioOutputStream, AudioOutputStreamOptions} from \"./audio-output-stream\"\n\n/**\n * Options for audio playback\n */\nexport interface AudioPlayOptions {\n /** URL to audio file for download and play */\n audioUrl: string\n /** Volume level 0.0-1.0, defaults to 1.0 */\n volume?: number\n /** Whether to stop other audio playback, defaults to true */\n stopOtherAudio?: boolean\n /**\n * Track ID for audio playback (defaults to 0)\n * - 0: speaker (default audio playback)\n * - 1: app_audio (app-specific audio)\n * - 2: tts (text-to-speech audio)\n * Use different track IDs to play multiple audio streams simultaneously (mixing)\n */\n trackId?: number\n}\n\n/**\n * Options for text-to-speech\n */\nexport interface SpeakOptions {\n /** Voice ID to use (optional, defaults to server's ELEVENLABS_DEFAULT_VOICE_ID) */\n voice_id?: string\n /** Model ID to use (optional, defaults to eleven_flash_v2_5) */\n model_id?: string\n /** Voice settings object (optional) */\n voice_settings?: {\n stability?: number\n similarity_boost?: number\n style?: number\n use_speaker_boost?: boolean\n speed?: number\n }\n /** Volume level 0.0-1.0, defaults to 1.0 */\n volume?: number\n /** Whether to stop other audio playback, defaults to true */\n stopOtherAudio?: boolean\n /**\n * Track ID for audio playback (defaults to 2 for TTS)\n * - 0: speaker (default audio playback)\n * - 1: app_audio (app-specific audio)\n * - 2: tts (text-to-speech audio)\n * Use different track IDs to play multiple audio streams simultaneously (mixing)\n */\n trackId?: number\n}\n\n/**\n * Result of audio playback attempt\n */\nexport interface AudioPlayResult {\n /** Whether the audio playback was successful */\n success: boolean\n /** Error message if playback failed */\n error?: string\n /** Duration of the audio file in seconds (if available) */\n duration?: number\n}\n\n/**\n * 🔊 Audio Module Implementation\n *\n * Audio management for App Sessions.\n * Provides methods for:\n * - 🎵 Playing audio on glasses\n * - ⏹️ Stopping audio playback\n * - 🔍 Monitoring audio request status\n * - 🧹 Cleanup and cancellation\n *\n * @example\n * ```typescript\n * // Play audio\n * const result = await session.audio.playAudio({\n * audioUrl: 'https://example.com/sound.mp3',\n * volume: 0.8\n * });\n *\n * // Stop all audio\n * session.audio.stopAudio();\n * ```\n */\nexport class AudioManager {\n private session: any // Reference to AppSession\n private packageName: string\n private sessionId: string\n private logger: Logger\n\n /** Map to store pending audio play request promises */\n private pendingAudioRequests = new Map<\n string,\n {\n resolve: (value: AudioPlayResult) => void\n reject: (reason?: string) => void\n }\n >()\n\n /** Current active output stream (one at a time) */\n private activeOutputStream: AudioOutputStream | null = null\n\n /**\n * Create a new AudioManager\n *\n * @param packageName - The App package name\n * @param sessionId - The current session ID\n * @param send - Function to send messages to the cloud\n * @param session - Reference to the parent AppSession (optional)\n * @param logger - Logger instance for debugging\n */\n constructor(session: any, packageName: string, sessionId: string, logger?: Logger) {\n this.session = session\n this.packageName = packageName\n this.sessionId = sessionId\n this.logger = logger || (console as any)\n }\n\n // =====================================\n // 🎵 Audio Playback Functionality\n // =====================================\n\n /**\n * 🔊 Play audio on the connected glasses\n * @param options - Audio playback configuration\n * @returns Promise that resolves with playback result\n *\n * @example\n * ```typescript\n * // Play audio from URL\n * const result = await session.audio.playAudio({\n * audioUrl: 'https://example.com/sound.mp3',\n * volume: 0.8\n * });\n * ```\n */\n async playAudio(options: AudioPlayOptions): Promise<AudioPlayResult> {\n return new Promise((resolve, reject) => {\n try {\n // Validate input\n if (!options.audioUrl) {\n reject(\"audioUrl must be provided\")\n return\n }\n\n // Generate unique request ID\n const requestId = `audio_req_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`\n\n const stopOtherAudio = options.stopOtherAudio ?? true\n\n // CRITICAL: When stopOtherAudio=false (concurrent/mixing mode),\n // resolve immediately after sending the request (fire-and-forget)\n // This allows multiple audio streams to play simultaneously\n if (!stopOtherAudio) {\n // Create audio play request message\n const message: AudioPlayRequest = {\n type: AppToCloudMessageType.AUDIO_PLAY_REQUEST,\n packageName: this.packageName,\n sessionId: this.sessionId,\n requestId,\n timestamp: new Date(),\n audioUrl: options.audioUrl,\n volume: options.volume ?? 1.0,\n stopOtherAudio: false,\n trackId: options.trackId ?? 0, // Default to track 0 (speaker)\n }\n\n // Send request to cloud\n this.session.sendMessage(message)\n\n // Resolve immediately for concurrent playback (fire-and-forget)\n // The audio will play in the background without blocking\n this.logger.debug({requestId}, `🔊 Audio playback started in non-blocking mode (concurrent)`)\n resolve({\n success: true,\n duration: undefined, // Duration unknown in fire-and-forget mode\n })\n return\n }\n\n // For stopOtherAudio=true (blocking/interrupt mode),\n // wait for the COMPLETED/FAILED event before resolving\n this.pendingAudioRequests.set(requestId, {resolve, reject})\n\n // Create audio play request message\n const message: AudioPlayRequest = {\n type: AppToCloudMessageType.AUDIO_PLAY_REQUEST,\n packageName: this.packageName,\n sessionId: this.sessionId,\n requestId,\n timestamp: new Date(),\n audioUrl: options.audioUrl,\n volume: options.volume ?? 1.0,\n stopOtherAudio: true,\n trackId: options.trackId ?? 0, // Default to track 0 (speaker)\n }\n\n // Send request to cloud\n this.session.sendMessage(message)\n\n // Set timeout to avoid hanging promises (only for blocking mode)\n const timeoutMs = 60000 // 60 seconds\n if (this.session && this.session.resources) {\n // Use session's resource tracker for automatic cleeanup\n this.session.resources.setTimeout(() => {\n if (this.pendingAudioRequests.has(requestId)) {\n this.pendingAudioRequests.get(requestId)!.reject(\"Audio play request timed out\")\n this.pendingAudioRequests.delete(requestId)\n this.logger.warn({requestId}, `🔊 Audio play request timed out`)\n }\n }, timeoutMs)\n } else {\n // Fallback to regular setTimeout if session not available\n setTimeout(() => {\n if (this.pendingAudioRequests.has(requestId)) {\n this.pendingAudioRequests.get(requestId)!.reject(\"Audio play request timed out\")\n this.pendingAudioRequests.delete(requestId)\n this.logger.warn({requestId}, `🔊 Audio play request timed out`)\n }\n }, timeoutMs)\n }\n } catch (error: unknown) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n reject(`Failed to play audio: ${errorMessage}`)\n }\n })\n }\n\n /**\n * 🔇 Stop audio playback on the connected glasses\n * @param trackId - Optional track ID to stop (0=speaker, 1=app_audio, 2=tts). If omitted, stops all tracks.\n *\n * @example\n * ```typescript\n * // Stop all currently playing audio\n * session.audio.stopAudio();\n *\n * // Stop only the speaker track (track_id 0)\n * session.audio.stopAudio(0);\n *\n * // Stop only TTS track (track_id 2)\n * session.audio.stopAudio(2);\n * ```\n */\n stopAudio(trackId?: number): void {\n try {\n // Create audio stop request message\n const message: AudioStopRequest = {\n type: AppToCloudMessageType.AUDIO_STOP_REQUEST,\n packageName: this.packageName,\n sessionId: this.sessionId,\n trackId,\n timestamp: new Date(),\n }\n\n // Send request to cloud (one-way, no response expected)\n this.session.sendMessage(message)\n\n const trackInfo = trackId !== undefined ? ` (track ${trackId})` : \" (all tracks)\"\n this.logger.info(`🔇 Audio stop request sent${trackInfo}`)\n } catch (error: unknown) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n this.logger.error(`Failed to stop audio: ${errorMessage}`)\n }\n }\n\n /**\n * 🗣️ Convert text to speech and play it on the connected glasses\n * @param text - Text to convert to speech (required)\n * @param options - Text-to-speech configuration (optional)\n * @returns Promise that resolves with playback result\n *\n * @example\n * ```typescript\n * // Basic text-to-speech\n * const result = await session.audio.speak('Hello, world!');\n *\n * // With custom voice settings\n * const result = await session.audio.speak('Hello, world!', {\n * voice_id: 'your_voice_id',\n * voice_settings: {\n * stability: 0.5,\n * speed: 1.2\n * },\n * volume: 0.8\n * });\n *\n * // Play TTS without stopping other audio\n * const result = await session.audio.speak('Hello, world!', {\n * stopOtherAudio: false\n * });\n * ```\n */\n async speak(text: string, options: SpeakOptions = {}): Promise<AudioPlayResult> {\n // Validate input\n if (!text) {\n throw new Error(\"text must be provided\")\n }\n\n // Get the HTTPS server URL from the session\n const baseUrl = this.session?.getHttpsServerUrl?.()\n if (!baseUrl) {\n throw new Error(\"Cannot determine server URL for TTS endpoint\")\n }\n\n // Build query parameters for the TTS endpoint\n const queryParams = new URLSearchParams()\n queryParams.append(\"text\", text)\n\n if (options.voice_id) {\n queryParams.append(\"voice_id\", options.voice_id)\n }\n\n if (options.model_id) {\n queryParams.append(\"model_id\", options.model_id)\n }\n\n if (options.voice_settings) {\n queryParams.append(\"voice_settings\", JSON.stringify(options.voice_settings))\n }\n\n // Construct the TTS URL\n const ttsUrl = `${baseUrl}/api/tts?${queryParams.toString()}`\n\n this.logger.debug({text, ttsUrl}, \"Generating speech from text\")\n\n // IMPORTANT: Don't call stopAudio() here - it closes tracks completely!\n // The backend will handle stopping any ongoing playback when it receives\n // the new audio play request (via stopOtherAudio flag)\n\n // Use the existing playAudio method to play the TTS audio\n // The stopOtherAudio flag will cancel ongoing playback without closing tracks\n return this.playAudio({\n audioUrl: ttsUrl,\n volume: options.volume,\n stopOtherAudio: options.stopOtherAudio ?? true, // This flag tells backend to stop current playback\n trackId: options.trackId ?? 2, // Default to track 2 (tts)\n })\n }\n\n // =====================================\n // 📥 Response Handling\n // =====================================\n\n /**\n * 📥 Handle audio play response from cloud\n *\n * This method is called internally when an audio play response is received.\n * It resolves the corresponding pending promise with the response data.\n *\n * @param response - The audio play response received\n * @internal This method is used internally by AppSession\n */\n handleAudioPlayResponse(response: AudioPlayResponse): void {\n const pendingRequest = this.pendingAudioRequests.get(response.requestId)\n\n if (pendingRequest) {\n // Resolve the promise with the response data\n pendingRequest.resolve({\n success: response.success,\n error: response.error,\n duration: response.duration,\n })\n\n // Clean up\n this.pendingAudioRequests.delete(response.requestId)\n\n this.logger.info(\n {\n requestId: response.requestId,\n success: response.success,\n duration: response.duration,\n },\n `🔊 Audio play response received`,\n )\n } else {\n this.logger.warn({requestId: response.requestId}, `🔊 Received audio play response for unknown request ID`)\n }\n }\n\n // =====================================\n // 🔍 Status and Management\n // =====================================\n\n /**\n * 🔍 Check if there are pending audio requests\n * @param requestId - Optional specific request ID to check\n * @returns True if there are pending requests (or specific request exists)\n */\n hasPendingRequest(requestId?: string): boolean {\n if (requestId) {\n return this.pendingAudioRequests.has(requestId)\n }\n return this.pendingAudioRequests.size > 0\n }\n\n /**\n * 📊 Get the number of pending audio requests\n * @returns Number of pending requests\n */\n getPendingRequestCount(): number {\n return this.pendingAudioRequests.size\n }\n\n /**\n * 📋 Get all pending request IDs\n * @returns Array of pending request IDs\n */\n getPendingRequestIds(): string[] {\n return Array.from(this.pendingAudioRequests.keys())\n }\n\n /**\n * ❌ Cancel a specific audio request\n * @param requestId - The request ID to cancel\n * @returns True if the request was found and cancelled\n */\n cancelAudioRequest(requestId: string): boolean {\n const pendingRequest = this.pendingAudioRequests.get(requestId)\n if (pendingRequest) {\n pendingRequest.reject(\"Audio request cancelled\")\n this.pendingAudioRequests.delete(requestId)\n this.logger.info({requestId}, `🔊 Audio request cancelled`)\n return true\n }\n return false\n }\n\n /**\n * 🧹 Cancel all pending audio requests\n * @returns Number of requests that were cancelled\n */\n cancelAllAudioRequests(): number {\n const count = this.pendingAudioRequests.size\n this.pendingAudioRequests.forEach((request, requestId) => {\n request.reject(\"Audio request cancelled due to cleanup\")\n this.logger.debug({requestId}, `🔊 Audio request cancelled during cleanup`)\n })\n this.pendingAudioRequests.clear()\n\n if (count > 0) {\n this.logger.info({cancelledCount: count}, `🧹 Cancelled all pending audio requests`)\n }\n\n return count\n }\n\n // =====================================\n // 🎙️ Audio Output Streaming\n // =====================================\n\n /**\n * Create a real-time audio output stream.\n *\n * This opens a streaming relay on the cloud and tells the phone to play it.\n * You write audio chunks to the returned stream, and they play on the glasses\n * speaker in real-time — like internet radio.\n *\n * **MP3 pass-through** (most common — ElevenLabs, Cartesia, OpenAI TTS, Azure):\n * ```typescript\n * const output = await session.audio.createOutputStream({ format: \"mp3\" })\n * elevenlabs.on(\"chunk\", (mp3) => output.write(mp3))\n * elevenlabs.on(\"end\", () => output.end())\n * ```\n *\n * **PCM encoding** (Gemini Live, OpenAI Realtime — requires `lamejs`):\n * ```typescript\n * const output = await session.audio.createOutputStream({\n * format: \"pcm16\",\n * sampleRate: 24000,\n * channels: 1,\n * })\n * realtimeApi.on(\"audio\", (pcm) => output.write(pcm))\n * ```\n *\n * @param options - Stream configuration\n * @returns The AudioOutputStream (already connected and playing)\n */\n async createOutputStream(options: AudioOutputStreamOptions = {}): Promise<AudioOutputStream> {\n // Enforce explicit stream lifecycle: callers must end/flush before creating another stream.\n if (this.activeOutputStream && this.activeOutputStream.state === \"streaming\") {\n const activeStreamId = this.activeOutputStream.streamId\n const error = new Error(\n `AUDIO_STREAM_ALREADY_ACTIVE: Stream ${activeStreamId} is still active. Call end() or flush() before creating a new output stream.`,\n ) as Error & {code?: string}\n error.code = \"AUDIO_STREAM_ALREADY_ACTIVE\"\n this.logger.warn({activeStreamId}, \"Refusing to create a second output stream while one is active\")\n throw error\n }\n\n // Generate a unique stream ID\n const streamId = crypto.randomUUID()\n\n const stream = new AudioOutputStream(streamId, this.session, this.logger, options)\n\n // Open the stream (sends AUDIO_STREAM_START, waits for relay URL, tells phone to play)\n await stream.open()\n\n this.activeOutputStream = stream\n\n // Clean up reference when the stream ends\n stream.on(\"close\", () => {\n if (this.activeOutputStream === stream) {\n this.activeOutputStream = null\n }\n })\n\n return stream\n }\n\n /**\n * Get the currently active output stream (if any).\n */\n getActiveOutputStream(): AudioOutputStream | null {\n return this.activeOutputStream\n }\n\n // =====================================\n // 🔧 Internal Management\n // =====================================\n\n /**\n * 🔄 Update the session ID when reconnecting\n * @param newSessionId - The new session ID\n * @internal Used by AppSession during reconnection\n */\n updateSessionId(newSessionId: string): void {\n this.sessionId = newSessionId\n this.logger.debug({newSessionId}, \"Audio module session ID updated\")\n }\n\n /**\n * 🧹 Cancel all pending requests (cleanup)\n * @returns Object with count of cancelled requests\n * @internal Used by AppSession during cleanup\n */\n cancelAllRequests(): {audioRequests: number} {\n const audioRequests = this.cancelAllAudioRequests()\n\n // Also end any active output stream\n if (this.activeOutputStream && this.activeOutputStream.state === \"streaming\") {\n this.activeOutputStream.end().catch(() => {})\n this.activeOutputStream = null\n }\n\n return {audioRequests}\n }\n}\n",
59
+ "/**\n * AudioOutputStream — streams audio from an SDK app to the phone via the cloud relay.\n *\n * The developer writes MP3 bytes (or PCM that the SDK encodes to MP3) into this\n * stream. Each write sends a WS binary frame to the cloud:\n *\n * [36 bytes: streamId UUID as ASCII] [N bytes: audio data]\n *\n * The cloud pipes those bytes into an HTTP chunked response that the phone's\n * ExoPlayer/AVPlayer plays like internet radio. Zero transcoding on the cloud.\n *\n * Lifecycle:\n * const output = await session.audio.createOutputStream({ format: \"mp3\" })\n * output.write(mp3Chunk) // as many times as needed\n * output.end() // graceful close — phone finishes buffered audio\n * output.flush() // interrupt — discard everything, silence immediately\n *\n * See: cloud/issues/041-sdk-audio-output-streaming/\n */\n\nimport {EventEmitter} from \"events\"\nimport type {Logger} from \"pino\"\nimport {AppToCloudMessageType} from \"../../../types/message-types\"\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\nexport interface AudioOutputStreamOptions {\n /**\n * Format of the audio being written.\n *\n * - \"mp3\": You're writing MP3 bytes directly (ElevenLabs, OpenAI TTS, Cartesia, etc.)\n * The SDK passes them straight through — zero encoding overhead.\n *\n * - \"pcm16\": You're writing raw 16-bit signed PCM samples (Gemini Live, OpenAI Realtime).\n * The SDK will encode to MP3 before sending. Requires sampleRate and channels.\n */\n format?: \"mp3\" | \"pcm16\"\n\n /** PCM sample rate in Hz (required when format is \"pcm16\"). Default: 24000 */\n sampleRate?: number\n\n /** Number of audio channels (required when format is \"pcm16\"). Default: 1 (mono) */\n channels?: number\n\n /** MP3 bitrate in kbps for PCM encoding. Default: 128 */\n bitrate?: number\n\n /**\n * Volume level 0.0–1.0 for playback on the phone. Default: 1.0\n */\n volume?: number\n\n /**\n * Track ID for playback (0=speaker, 1=app_audio, 2=tts). Default: 1\n */\n trackId?: number\n\n /**\n * Whether starting this stream should stop other audio. Default: true\n */\n stopOtherAudio?: boolean\n}\n\nexport type AudioOutputStreamState = \"created\" | \"streaming\" | \"ending\" | \"ended\" | \"error\"\n\n// ─── Constants ───────────────────────────────────────────────────────────────\n\n/** How long (ms) to wait for the cloud to respond with AUDIO_STREAM_READY */\nconst READY_TIMEOUT_MS = 10_000\n\n/** UUID length in ASCII bytes */\nconst STREAM_ID_LENGTH = 36\n\n// ─── Main Class ──────────────────────────────────────────────────────────────\n\nexport class AudioOutputStream extends EventEmitter {\n public readonly streamId: string\n public streamUrl: string | null = null\n\n private _state: AudioOutputStreamState = \"created\"\n private session: any // AppSession — typed as any to avoid circular imports\n private logger: Logger\n private options: Required<\n Pick<\n AudioOutputStreamOptions,\n \"format\" | \"sampleRate\" | \"channels\" | \"bitrate\" | \"volume\" | \"trackId\" | \"stopOtherAudio\"\n >\n >\n private encoder: Mp3Encoder | null = null\n private streamIdBytes: Uint8Array\n\n constructor(streamId: string, session: any, logger: Logger, opts: AudioOutputStreamOptions = {}) {\n super()\n this.streamId = streamId\n this.session = session\n this.logger = logger.child({module: \"AudioOutputStream\", streamId})\n\n this.options = {\n format: opts.format ?? \"mp3\",\n sampleRate: opts.sampleRate ?? 24000,\n channels: opts.channels ?? 1,\n bitrate: opts.bitrate ?? 128,\n volume: opts.volume ?? 1.0,\n trackId: opts.trackId ?? 1,\n stopOtherAudio: opts.stopOtherAudio ?? true,\n }\n\n // Pre-encode the streamId as ASCII bytes (reused on every write)\n this.streamIdBytes = new TextEncoder().encode(this.streamId)\n }\n\n /** Current state of the stream */\n get state(): AudioOutputStreamState {\n return this._state\n }\n\n /**\n * Initialize the stream — sends AUDIO_STREAM_START to the cloud and waits\n * for AUDIO_STREAM_READY with the relay URL. Then tells the phone to play it.\n *\n * Called internally by `session.audio.createOutputStream()`.\n * Do NOT call this directly — use `createOutputStream()` instead.\n *\n * @internal\n */\n async open(): Promise<void> {\n if (this._state !== \"created\") {\n throw new Error(`Cannot open stream in state \"${this._state}\"`)\n }\n\n // If PCM format, initialize the MP3 encoder\n if (this.options.format === \"pcm16\") {\n this.encoder = createMp3Encoder(this.options.channels, this.options.sampleRate, this.options.bitrate)\n }\n\n // Send AUDIO_STREAM_START to the cloud\n const startMessage = {\n type: AppToCloudMessageType.AUDIO_STREAM_START,\n packageName: this.session.getPackageName(),\n sessionId: this.session.getSessionId(),\n streamId: this.streamId,\n contentType: \"audio/mpeg\",\n timestamp: new Date(),\n }\n this.session.sendMessage(startMessage)\n\n // Wait for AUDIO_STREAM_READY response\n this.streamUrl = await this.waitForReady()\n\n this._state = \"streaming\"\n\n // Tell the phone to play the relay URL\n // This uses the existing playAudio path — the phone doesn't know it's a stream\n const playMessage = {\n type: AppToCloudMessageType.AUDIO_PLAY_REQUEST,\n packageName: this.session.getPackageName(),\n sessionId: this.session.getSessionId(),\n requestId: `stream_${this.streamId}`,\n audioUrl: this.streamUrl,\n volume: this.options.volume,\n stopOtherAudio: this.options.stopOtherAudio,\n trackId: this.options.trackId,\n timestamp: new Date(),\n }\n this.session.sendMessage(playMessage)\n\n this.logger.debug({streamUrl: this.streamUrl}, \"Audio output stream opened\")\n }\n\n /**\n * Write audio data to the stream.\n *\n * - If format is \"mp3\", the bytes are sent directly to the cloud.\n * - If format is \"pcm16\", the bytes are encoded to MP3 first.\n *\n * @param data - Audio data as Buffer, Uint8Array, or ArrayBuffer\n */\n write(data: Buffer | Uint8Array | ArrayBuffer): void {\n if (this._state !== \"streaming\") {\n this.logger.debug({state: this._state}, \"Write called on non-streaming output, ignoring\")\n return\n }\n\n let mp3Data: Uint8Array\n\n if (this.options.format === \"pcm16\" && this.encoder) {\n // Encode PCM → MP3\n const pcm = toInt16Array(data)\n const encoded = this.encoder.encodeBuffer(pcm)\n if (encoded.length === 0) return // Encoder is buffering, no complete frame yet\n mp3Data = new Uint8Array(encoded)\n } else {\n // MP3 pass-through\n mp3Data = toUint8Array(data)\n }\n\n if (mp3Data.length === 0) return\n\n // Build the binary frame: [36 bytes streamId] [N bytes audio]\n this.sendBinaryFrame(mp3Data)\n }\n\n /**\n * End the stream gracefully.\n *\n * If using PCM encoding, flushes any remaining encoder buffer first.\n * The phone will finish playing any buffered audio, then stop.\n */\n async end(): Promise<void> {\n if (this._state !== \"streaming\") return\n this._state = \"ending\"\n\n // Flush the MP3 encoder if we have one\n if (this.encoder) {\n const flushed = this.encoder.flush()\n if (flushed.length > 0) {\n this.sendBinaryFrame(new Uint8Array(flushed))\n }\n this.encoder = null\n }\n\n // Tell the cloud to close the relay\n const endMessage = {\n type: AppToCloudMessageType.AUDIO_STREAM_END,\n packageName: this.session.getPackageName(),\n sessionId: this.session.getSessionId(),\n streamId: this.streamId,\n timestamp: new Date(),\n }\n this.session.sendMessage(endMessage)\n\n this._state = \"ended\"\n this.emit(\"close\")\n this.logger.debug(\"Audio output stream ended\")\n }\n\n /**\n * Flush/interrupt — discard all buffered audio and stop playback immediately.\n *\n * Use this when the user starts talking and you want to silence the AI response.\n */\n async flush(): Promise<void> {\n if (this._state !== \"streaming\") return\n this._state = \"ending\"\n\n // Discard encoder state\n this.encoder = null\n\n // End the stream on the cloud side (relay closes → HTTP response ends → ExoPlayer stops)\n const endMessage = {\n type: AppToCloudMessageType.AUDIO_STREAM_END,\n packageName: this.session.getPackageName(),\n sessionId: this.session.getSessionId(),\n streamId: this.streamId,\n timestamp: new Date(),\n }\n this.session.sendMessage(endMessage)\n\n // Also explicitly stop audio playback on the phone\n const stopMessage = {\n type: AppToCloudMessageType.AUDIO_STOP_REQUEST,\n packageName: this.session.getPackageName(),\n sessionId: this.session.getSessionId(),\n trackId: this.options.trackId,\n timestamp: new Date(),\n }\n this.session.sendMessage(stopMessage)\n\n this._state = \"ended\"\n this.emit(\"close\")\n this.logger.debug(\"Audio output stream flushed (interrupted)\")\n }\n\n // ─── Internal ──────────────────────────────────────────────────────────────\n\n /**\n * Send a binary frame over the WebSocket.\n * Frame format: [36 bytes streamId] [N bytes audio data]\n */\n private sendBinaryFrame(audioData: Uint8Array): void {\n const frame = new Uint8Array(STREAM_ID_LENGTH + audioData.length)\n frame.set(this.streamIdBytes, 0)\n frame.set(audioData, STREAM_ID_LENGTH)\n\n try {\n this.session.sendBinary(frame)\n } catch (err) {\n this.logger.debug({err}, \"Failed to send binary frame\")\n this._state = \"error\"\n this.emit(\"error\", err)\n }\n }\n\n /**\n * Wait for the cloud to respond with AUDIO_STREAM_READY.\n * Returns the relay URL.\n */\n private waitForReady(): Promise<string> {\n return new Promise<string>((resolve, reject) => {\n const timeout = setTimeout(() => {\n cleanup()\n reject(new Error(`Audio stream relay not ready after ${READY_TIMEOUT_MS}ms`))\n }, READY_TIMEOUT_MS)\n\n const handler = (msg: any) => {\n if (msg.type === \"audio_stream_ready\" && msg.streamId === this.streamId) {\n cleanup()\n resolve(msg.streamUrl)\n }\n }\n\n const cleanup = () => {\n clearTimeout(timeout)\n this.session.events?.off?.(\"__raw_message\", handler)\n // Also try removing from the internal listener if events.off doesn't exist\n this.session.removeInternalListener?.(\"audio_stream_ready\", handler)\n }\n\n // Listen for the AUDIO_STREAM_READY response.\n // The AppSession handleMessage method will need to emit this.\n // We register on a special internal channel that gets raw cloud messages.\n if (this.session._audioStreamReadyHandlers) {\n this.session._audioStreamReadyHandlers.set(this.streamId, handler)\n }\n })\n }\n}\n\n// ─── MP3 Encoder Wrapper ─────────────────────────────────────────────────────\n\n/**\n * Minimal interface for an MP3 encoder (compatible with lamejs).\n */\ninterface Mp3Encoder {\n encodeBuffer(samples: Int16Array): Int32Array | Uint8Array\n flush(): Int32Array | Uint8Array\n}\n\n/**\n * Create an MP3 encoder for PCM→MP3 conversion.\n * Used by Gemini Live / OpenAI Realtime which output raw PCM.\n * lamejs is a regular SDK dependency — always available.\n *\n * lamejs has broken CJS modules — individual source files reference globals\n * (MPEGMode, Lame, BitStream, etc.) that are only defined when loaded via\n * the concatenated lame.all.js bundle. Bun resolves to src/js/index.js\n * which doesn't set these globals. We inject them manually before constructing.\n */\nfunction createMp3Encoder(channels: number, sampleRate: number, bitrate: number): Mp3Encoder {\n // Inject the globals that lamejs's broken CJS modules expect\n ;(globalThis as any).MPEGMode ??= require(\"lamejs/src/js/MPEGMode.js\")\n ;(globalThis as any).Lame ??= require(\"lamejs/src/js/Lame.js\")\n ;(globalThis as any).BitStream ??= require(\"lamejs/src/js/BitStream.js\")\n\n const lamejs = require(\"lamejs\")\n const Encoder = lamejs.Mp3Encoder ?? lamejs.default?.Mp3Encoder\n return new Encoder(channels, sampleRate, bitrate) as Mp3Encoder\n}\n\n// ─── Buffer Helpers ──────────────────────────────────────────────────────────\n\nfunction toUint8Array(data: Buffer | Uint8Array | ArrayBuffer): Uint8Array {\n if (data instanceof ArrayBuffer) return new Uint8Array(data)\n if (data instanceof Uint8Array) return data\n // Buffer fallback — Buffer extends Uint8Array so this branch is rarely hit,\n // but keeps the compiler happy when the type is narrowed to `never`.\n const buf = data as unknown as {buffer: ArrayBuffer; byteOffset: number; byteLength: number}\n return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength)\n}\n\nfunction toInt16Array(data: Buffer | Uint8Array | ArrayBuffer): Int16Array {\n if (data instanceof Int16Array) return data\n const bytes = toUint8Array(data)\n // PCM16 is little-endian 16-bit signed integers\n return new Int16Array(bytes.buffer, bytes.byteOffset, bytes.byteLength / 2)\n}\n",
36
60
  "/**\n * Resource Tracker\n * \n * A utility class for tracking and automatically cleaning up resources\n * like timers, event listeners, and other disposable objects.\n * \n * This helps prevent memory leaks by ensuring that all resources are\n * properly disposed when they're no longer needed.\n */\n\n/**\n * Type for a cleanup function that doesn't take any arguments\n */\nexport type CleanupFunction = () => void;\n\n/**\n * Type for any object with a dispose or close method\n */\nexport interface Disposable {\n dispose?: () => void;\n close?: () => void;\n}\n\n/**\n * Manages resources to prevent memory leaks\n */\nexport class ResourceTracker {\n // Collection of cleanup functions to call when dispose() is called\n private cleanupFunctions: CleanupFunction[] = [];\n \n // Flag to track if this resource tracker has been disposed\n private isDisposed = false;\n \n /**\n * Add a cleanup function to be executed when dispose() is called\n * \n * @param cleanup - The cleanup function to register\n * @returns A function that will remove this cleanup function\n */\n track(cleanup: CleanupFunction): CleanupFunction {\n if (this.isDisposed) {\n throw new Error('Cannot track resources on a disposed ResourceTracker');\n }\n \n this.cleanupFunctions.push(cleanup);\n \n // Return a function that will remove this cleanup function\n return () => {\n const index = this.cleanupFunctions.indexOf(cleanup);\n if (index !== -1) {\n this.cleanupFunctions.splice(index, 1);\n }\n };\n }\n \n /**\n * Track a disposable object (anything with a dispose or close method)\n * \n * @param disposable - The object to track\n * @returns A function that will remove this disposable\n */\n trackDisposable(disposable: Disposable): CleanupFunction {\n return this.track(() => {\n if (typeof disposable.dispose === 'function') {\n disposable.dispose();\n } else if (typeof disposable.close === 'function') {\n disposable.close();\n }\n });\n }\n \n /**\n * Track a timer and ensure it gets cleared\n * \n * @param timerId - The timer ID to track\n * @param isInterval - Whether this is an interval (true) or timeout (false)\n * @returns A function that will remove this timer\n */\n trackTimer(timerId: NodeJS.Timeout, isInterval = false): CleanupFunction {\n return this.track(() => {\n if (isInterval) {\n clearInterval(timerId);\n } else {\n clearTimeout(timerId);\n }\n });\n }\n \n /**\n * Track a timeout and ensure it gets cleared\n * \n * @param timerId - The timeout ID to track\n * @returns A function that will remove this timeout\n */\n trackTimeout(timerId: NodeJS.Timeout): CleanupFunction {\n return this.trackTimer(timerId, false);\n }\n \n /**\n * Track an interval and ensure it gets cleared\n * \n * @param timerId - The interval ID to track\n * @returns A function that will remove this interval\n */\n trackInterval(timerId: NodeJS.Timeout): CleanupFunction {\n return this.trackTimer(timerId, true);\n }\n \n /**\n * Create a tracked timeout\n * \n * @param callback - Function to call when the timeout expires\n * @param ms - Milliseconds to wait\n * @returns The timeout ID\n */\n setTimeout(callback: (...args: any[]) => void, ms: number): NodeJS.Timeout {\n const timerId = setTimeout(callback, ms);\n this.trackTimeout(timerId);\n return timerId;\n }\n \n /**\n * Create a tracked interval\n * \n * @param callback - Function to call at each interval\n * @param ms - Milliseconds between intervals\n * @returns The interval ID\n */\n setInterval(callback: (...args: any[]) => void, ms: number): NodeJS.Timeout {\n const timerId = setInterval(callback, ms);\n this.trackInterval(timerId);\n return timerId;\n }\n \n /**\n * Dispose of all tracked resources\n */\n dispose(): void {\n if (this.isDisposed) {\n return;\n }\n \n // Run all cleanup functions\n for (const cleanup of this.cleanupFunctions) {\n try {\n cleanup();\n } catch (error) {\n console.error('Error during resource cleanup:', error);\n }\n }\n \n // Clear the array\n this.cleanupFunctions = [];\n this.isDisposed = true;\n }\n \n /**\n * Check if this tracker has been disposed\n */\n get disposed(): boolean {\n return this.isDisposed;\n }\n}\n\n/**\n * Create a new ResourceTracker instance\n * \n * @returns A new ResourceTracker\n */\nexport function createResourceTracker(): ResourceTracker {\n return new ResourceTracker();\n}",
61
+ "/**\n * MentraOS SDK Error Classes\n *\n * Structured error hierarchy for programmatic error handling.\n * All classes extend Error so `instanceof Error` checks still work (backward compatible).\n *\n * Usage:\n * ```typescript\n * import { MentraAuthError, MentraConnectionError } from '@mentra/sdk';\n *\n * session.events.onError((error) => {\n * if (error instanceof MentraAuthError) {\n * console.log('Bad API key, check your config');\n * } else if (error instanceof MentraConnectionError) {\n * console.log('Connection issue, will retry');\n * }\n * });\n * ```\n */\n\n/**\n * Base error class for all MentraOS SDK errors.\n *\n * Every SDK error has a `.code` string for programmatic matching\n * without relying on `.message` string parsing.\n */\nexport class MentraError extends Error {\n constructor(\n message: string,\n public readonly code: string,\n ) {\n super(message);\n this.name = \"MentraError\";\n // Fix prototype chain for instanceof checks in transpiled code.\n // TypeScript/Bun transpilation can break `instanceof` for subclassed builtins\n // without this. See: https://github.com/microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/**\n * Authentication or authorization failure.\n *\n * Thrown/emitted when:\n * - API key is invalid or expired\n * - Token verification fails\n * - Cloud rejects credentials\n */\nexport class MentraAuthError extends MentraError {\n constructor(message: string) {\n super(message, \"AUTH_ERROR\");\n this.name = \"MentraAuthError\";\n }\n}\n\n/**\n * Connection-level failure.\n *\n * Thrown/emitted when:\n * - WebSocket connection is refused (ECONNREFUSED)\n * - Connection is lost unexpectedly\n * - All reconnection attempts are exhausted\n * - Server is unreachable\n */\nexport class MentraConnectionError extends MentraError {\n constructor(message: string, code: string = \"CONNECTION_ERROR\") {\n super(message, code);\n this.name = \"MentraConnectionError\";\n }\n}\n\n/**\n * Operation timed out.\n *\n * Thrown/emitted when:\n * - WebSocket connection handshake exceeds timeout\n * - Photo request times out\n * - Audio play request times out\n */\nexport class MentraTimeoutError extends MentraError {\n constructor(message: string) {\n super(message, \"TIMEOUT_ERROR\");\n this.name = \"MentraTimeoutError\";\n }\n}\n\n/**\n * Configuration or input validation failure.\n *\n * Thrown synchronously when:\n * - WebSocket URL is missing or malformed\n * - Layout is missing required properties\n * - Invalid language code is provided\n * - Required config fields are absent\n */\nexport class MentraValidationError extends MentraError {\n constructor(message: string) {\n super(message, \"VALIDATION_ERROR\");\n this.name = \"MentraValidationError\";\n }\n}\n\n/**\n * Permission denied by MentraOS Cloud.\n *\n * Emitted when:\n * - App subscribes to a stream it lacks permission for\n * - Cloud rejects a subscription request\n *\n * Includes the stream name and required permission for programmatic handling.\n */\nexport class MentraPermissionError extends MentraError {\n constructor(\n message: string,\n public readonly stream: string,\n public readonly requiredPermission: string,\n ) {\n super(message, \"PERMISSION_ERROR\");\n this.name = \"MentraPermissionError\";\n }\n}\n",
62
+ "/**\n * Telemetry Transport for MentraOS SDK\n *\n * A pino-compatible Writable stream that intercepts log entries and pushes\n * them into a session-scoped ring buffer for incident debugging.\n *\n * This transport is added to the session logger (not the server logger), so\n * the buffer is automatically scoped to one user's session. Every log line\n * written via session.logger — including all module child loggers (camera,\n * audio, led, etc.) — is captured here with zero effort from app developers.\n *\n * Design notes:\n * - Only captures info+ entries. Debug is too noisy for incident bundles;\n * BetterStack captures debug separately.\n * - Ring buffer: oldest entries are dropped when full. This is intentional —\n * the most recent logs before a bug report are the most useful.\n * - The buffer is an external array passed in by reference, so AppSession\n * owns the lifetime and can clear it on disconnect simply by reassigning.\n * - This transport does NOT forward to BetterStack or console — those run\n * via the parent appServer.logger streams independently.\n */\n\nimport { Writable } from \"stream\";\nimport type { TelemetryLogEntry } from \"../types/messages/app-to-cloud\";\n\n// Pino level numbers → our TelemetryLogEntry level strings\n// https://getpino.io/#/docs/api?id=loggerlevel-string-gettersetter\nconst PINO_LEVEL_MAP: Record<number, TelemetryLogEntry[\"level\"]> = {\n 10: \"debug\", // trace → debug (we filter these out anyway)\n 20: \"debug\",\n 30: \"info\",\n 40: \"warn\",\n 50: \"error\",\n 60: \"error\", // fatal → error\n};\n\n// Minimum pino level number to capture. 30 = info.\n// Debug (20) and trace (10) are excluded — too noisy for incident bundles.\nconst MIN_CAPTURE_LEVEL = 30;\n\n/**\n * Creates a pino-compatible Writable stream that pushes log entries into\n * a caller-owned ring buffer.\n *\n * @param buffer - The array to push entries into. Owned by the caller\n * (AppSession). Passed by reference so the caller can\n * clear it by splicing or reassigning.\n * @param bufferSize - Maximum number of entries to keep. When the buffer\n * exceeds this, the oldest entries are dropped.\n * Default: 500.\n * @returns A Node.js Writable stream compatible with pino's multistream.\n *\n * @example\n * ```typescript\n * const telemetryBuffer: TelemetryLogEntry[] = []\n * const stream = createTelemetryStream(telemetryBuffer, 500)\n *\n * const logger = pino(\n * { level: \"debug\" },\n * pino.multistream([\n * { stream: parentStream, level: \"warn\" },\n * { stream: stream, level: \"info\" },\n * ])\n * )\n * ```\n */\nexport function createTelemetryStream(buffer: TelemetryLogEntry[], bufferSize = 500): Writable {\n return new Writable({\n write(chunk: Buffer, _encoding: string, callback: () => void) {\n try {\n const line = chunk.toString().trim();\n if (!line) {\n callback();\n return;\n }\n\n const obj = JSON.parse(line);\n\n // Filter out below-info entries\n const pinoLevel: number = obj.level ?? 30;\n if (pinoLevel < MIN_CAPTURE_LEVEL) {\n callback();\n return;\n }\n\n const level: TelemetryLogEntry[\"level\"] = PINO_LEVEL_MAP[pinoLevel] ?? \"info\";\n const msg: string = obj.msg ?? \"\";\n\n // Skip empty messages — pino internal events or structured-only logs\n if (!msg) {\n callback();\n return;\n }\n\n // Build the entry. We capture the structured context fields (everything\n // except the pino internals) as `data` so the incident viewer can see\n // userId, module, requestId, etc. alongside the message.\n const { level: _l, msg: _m, time, pid: _pid, hostname: _hostname, ...rest } = obj;\n const entry: TelemetryLogEntry = {\n timestamp: typeof time === \"number\" ? time : Date.now(),\n level,\n message: msg,\n // Preserve source tagging from pino child logger bindings\n source: obj.service ?? obj.module ?? undefined,\n // Attach remaining structured fields only if there's anything useful\n data: Object.keys(rest).length > 0 ? rest : undefined,\n };\n\n buffer.push(entry);\n\n // Trim to keep only the most recent entries\n if (buffer.length > bufferSize) {\n buffer.splice(0, buffer.length - bufferSize);\n }\n } catch {\n // Silently ignore parse errors — a broken telemetry stream should\n // never crash the app or interfere with other log transports.\n }\n\n callback();\n },\n });\n}\n",
37
63
  "/**\n * Simple Storage SDK Module for MentraOS Apps\n * Provides localStorage-like API with cloud synchronization\n *\n * Mental Model: App server RAM = source of truth, MongoDB = crash recovery backup\n * - User interactions read/write to RAM instantly\n * - Changes are debounced and batched to MongoDB (3s idle / 10s max)\n * - On disconnect, pending writes are flushed automatically\n */\n\nimport {AppSession} from \"..\"\n\n/**\n * Response types for Simple Storage API\n */\ninterface StorageResponse {\n success: boolean\n data?: Record<string, string>\n}\n\ninterface StorageOperationResponse {\n success: boolean\n}\n\n/**\n * Key-value storage with local caching and debounced cloud sync\n * Data is isolated by userId and packageName\n */\nexport class SimpleStorage {\n private storage: Record<string, string> | null = null\n private appSession: AppSession\n private userId: string\n private packageName: string\n private baseUrl: string\n\n // Debounce batching state\n private pendingWrites = new Map<string, string>()\n private debounceTimer?: NodeJS.Timeout\n private maxWaitTimer?: NodeJS.Timeout\n private firstWriteTime?: number\n\n // Constants\n private static readonly MAX_VALUE_SIZE = 100_000 // 100KB\n private static readonly DEBOUNCE_MS = 3_000 // 3 seconds idle\n private static readonly MAX_WAIT_MS = 10_000 // 10 seconds max\n\n constructor(appSession: AppSession) {\n this.appSession = appSession\n this.userId = appSession.userId\n this.packageName = appSession.getPackageName()\n this.baseUrl = this.getBaseUrl()\n }\n\n // Convert WebSocket URL to HTTP for API calls\n private getBaseUrl(): string {\n const serverUrl = this.appSession.getServerUrl()\n if (!serverUrl) return \"http://localhost:8002\"\n return serverUrl.replace(/\\/app-ws$/, \"\").replace(/^ws/, \"http\")\n }\n\n // Generate auth headers for API requests\n private getAuthHeaders() {\n const apiKey = (this.appSession as any).config?.apiKey || \"unknown-api-key\"\n return {\n \"Authorization\": `Bearer ${this.packageName}:${apiKey}`,\n \"Content-Type\": \"application/json\",\n }\n }\n\n // Fetch all data from cloud and cache locally\n private async fetchStorageFromCloud(): Promise<void> {\n try {\n const response = await fetch(`${this.baseUrl}/api/sdk/simple-storage/${encodeURIComponent(this.userId)}`, {\n headers: this.getAuthHeaders(),\n })\n\n if (response.ok) {\n const result = (await response.json()) as StorageResponse\n if (result.success && result.data) {\n this.storage = result.data\n } else {\n this.storage = {}\n }\n } else {\n console.error(\"Failed to fetch storage from cloud:\", await response.text())\n this.storage = {}\n }\n } catch (error) {\n console.error(\"Error fetching storage from cloud:\", error)\n this.storage = {}\n }\n }\n\n // Get item from cache or cloud\n public async get(key: string): Promise<string | undefined> {\n try {\n if (this.storage !== null && this.storage !== undefined) {\n return this.storage[key]\n }\n\n await this.fetchStorageFromCloud()\n return this.storage?.[key]\n } catch (error) {\n console.error(\"Error getting item:\", error)\n return undefined\n }\n }\n\n // Set item with size validation and debounced batching\n // RAM updated immediately, MongoDB synced after 3s idle or 10s max\n public async set(key: string, value: string): Promise<void> {\n try {\n // Validate value size\n if (value.length > SimpleStorage.MAX_VALUE_SIZE) {\n throw new Error(\n `SimpleStorage value exceeds 100KB limit (${value.length} bytes). ` +\n `For large files, use your own S3 bucket storage.`,\n )\n }\n\n if (this.storage === null || this.storage === undefined) {\n await this.fetchStorageFromCloud()\n }\n\n // Optimistic update - RAM is source of truth (instant)\n if (this.storage) {\n this.storage[key] = value\n }\n\n // Add to pending batch for MongoDB persistence\n this.pendingWrites.set(key, value)\n\n // Schedule debounced flush\n this.scheduleFlush()\n } catch (error) {\n console.error(\"Error setting item:\", error)\n throw error\n }\n }\n\n /**\n * Schedule flush with debounce (3s idle) and max wait (10s)\n */\n private scheduleFlush(): void {\n // Track first write time for max wait\n if (!this.firstWriteTime) {\n this.firstWriteTime = Date.now()\n }\n\n // Clear existing debounce timer\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer)\n }\n\n // Calculate time until max wait\n const elapsedMs = Date.now() - this.firstWriteTime\n const remainingMaxWaitMs = SimpleStorage.MAX_WAIT_MS - elapsedMs\n\n // If we've hit max wait, flush immediately\n if (remainingMaxWaitMs <= 0) {\n this.flush().catch((err) => {\n console.error(\"Error flushing SimpleStorage:\", err)\n })\n return\n }\n\n // Set debounce timer (3s idle)\n this.debounceTimer = setTimeout(\n () => {\n this.flush().catch((err) => {\n console.error(\"Error flushing SimpleStorage:\", err)\n })\n },\n Math.min(SimpleStorage.DEBOUNCE_MS, remainingMaxWaitMs),\n )\n\n // Set max wait timer if not already set\n if (!this.maxWaitTimer && remainingMaxWaitMs > 0) {\n this.maxWaitTimer = setTimeout(() => {\n this.flush().catch((err) => {\n console.error(\"Error flushing SimpleStorage (max wait):\", err)\n })\n }, remainingMaxWaitMs)\n }\n }\n\n /**\n * Flush pending writes immediately\n * Called by: debounce timeout, max wait timeout, disconnect, or explicit flush()\n */\n public async flush(): Promise<void> {\n if (this.pendingWrites.size === 0) return\n\n // Clear all timers\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer)\n this.debounceTimer = undefined\n }\n if (this.maxWaitTimer) {\n clearTimeout(this.maxWaitTimer)\n this.maxWaitTimer = undefined\n }\n this.firstWriteTime = undefined\n\n const batch = Object.fromEntries(this.pendingWrites)\n this.pendingWrites.clear()\n\n try {\n // Persist to MongoDB (backup for crash recovery)\n const response = await fetch(`${this.baseUrl}/api/sdk/simple-storage/${encodeURIComponent(this.userId)}`, {\n method: \"PUT\",\n headers: this.getAuthHeaders(),\n body: JSON.stringify({data: batch}),\n })\n\n if (!response.ok) {\n const error = await response.text()\n console.error(\"Failed to persist SimpleStorage to MongoDB:\", error)\n\n // Re-throw with helpful message\n if (response.status === 413) {\n throw new Error(\"SimpleStorage total size exceeds 1MB limit. Delete unused keys.\")\n }\n if (response.status === 429) {\n throw new Error(\"SimpleStorage rate limit exceeded.\")\n }\n throw new Error(`SimpleStorage flush failed: ${error}`)\n }\n } catch (error) {\n console.error(\"Error flushing SimpleStorage:\", error)\n throw error\n }\n }\n\n // Delete item from cache and debounced sync to cloud\n public async delete(key: string): Promise<boolean> {\n try {\n if (this.storage === null || this.storage === undefined) {\n await this.fetchStorageFromCloud()\n }\n\n // Remove from cache (RAM = source of truth)\n if (this.storage) {\n delete this.storage[key]\n }\n\n // Remove from pending writes if exists\n this.pendingWrites.delete(key)\n\n // For deletes, we flush immediately to ensure consistency\n // (could batch this too, but deletes are rare)\n const response = await fetch(\n `${this.baseUrl}/api/sdk/simple-storage/${encodeURIComponent(this.userId)}/${encodeURIComponent(key)}`,\n {\n method: \"DELETE\",\n headers: this.getAuthHeaders(),\n },\n )\n\n if (response.ok) {\n const result = (await response.json()) as StorageOperationResponse\n return result.success\n } else {\n console.error(\"Failed to delete item from cloud:\", await response.text())\n return false\n }\n } catch (error) {\n console.error(\"Error deleting item:\", error)\n return false\n }\n }\n\n // Clear all data from cache and cloud\n public async clear(): Promise<boolean> {\n try {\n this.storage = {}\n\n const response = await fetch(`${this.baseUrl}/api/sdk/simple-storage/${encodeURIComponent(this.userId)}`, {\n method: \"DELETE\",\n headers: this.getAuthHeaders(),\n })\n\n if (response.ok) {\n const result = (await response.json()) as StorageOperationResponse\n return result.success\n } else {\n console.error(\"Failed to clear storage from cloud:\", await response.text())\n return false\n }\n } catch (error) {\n console.error(\"Error clearing storage:\", error)\n return false\n }\n }\n\n // Get all storage keys\n public async keys(): Promise<string[]> {\n try {\n if (this.storage === null || this.storage === undefined) {\n await this.fetchStorageFromCloud()\n }\n return Object.keys(this.storage || {})\n } catch (error) {\n console.error(\"Error getting keys:\", error)\n return []\n }\n }\n\n // Get number of stored items\n public async size(): Promise<number> {\n try {\n if (this.storage === null || this.storage === undefined) {\n await this.fetchStorageFromCloud()\n }\n return Object.keys(this.storage || {}).length\n } catch (error) {\n console.error(\"Error getting storage size:\", error)\n return 0\n }\n }\n\n // Check if key exists\n public async hasKey(key: string): Promise<boolean> {\n try {\n if (this.storage === null || this.storage === undefined) {\n await this.fetchStorageFromCloud()\n }\n return key in (this.storage || {})\n } catch (error) {\n console.error(\"Error checking key:\", error)\n return false\n }\n }\n\n // Get copy of all stored data\n public async getAllData(): Promise<Record<string, string>> {\n try {\n if (this.storage === null || this.storage === undefined) {\n await this.fetchStorageFromCloud()\n }\n return {...(this.storage || {})}\n } catch (error) {\n console.error(\"Error getting all data:\", error)\n return {}\n }\n }\n\n // Set multiple items at once with validation\n public async setMultiple(data: Record<string, string>): Promise<void> {\n try {\n // Validate all values first\n for (const [key, value] of Object.entries(data)) {\n if (value.length > SimpleStorage.MAX_VALUE_SIZE) {\n throw new Error(`SimpleStorage value for key \"${key}\" exceeds 100KB limit (${value.length} bytes)`)\n }\n }\n\n if (this.storage === null || this.storage === undefined) {\n await this.fetchStorageFromCloud()\n }\n\n // Update cache (RAM = source of truth)\n if (this.storage) {\n Object.assign(this.storage, data)\n }\n\n // Add all to pending batch\n for (const [key, value] of Object.entries(data)) {\n this.pendingWrites.set(key, value)\n }\n\n // Schedule debounced flush\n this.scheduleFlush()\n } catch (error) {\n console.error(\"Error setting multiple items:\", error)\n throw error\n }\n }\n}\n",
38
- "/**\n * Observable<T> - A reactive value wrapper that notifies listeners of changes\n *\n * Provides synchronous value access and reactive subscriptions via onChange().\n * Supports implicit coercion for use in conditionals and comparisons.\n *\n * @example\n * ```typescript\n * const wifiStatus = new Observable(false);\n *\n * // Synchronous read\n * console.log(wifiStatus.value); // false\n *\n * // Implicit coercion\n * if (wifiStatus) { ... } // Works via valueOf()\n *\n * // Reactive subscription\n * const cleanup = wifiStatus.onChange((connected) => {\n * console.log(\"WiFi:\", connected);\n * });\n *\n * // Update (triggers callbacks)\n * wifiStatus.setValue(true);\n *\n * // Cleanup\n * cleanup();\n * ```\n */\nexport class Observable<T> {\n private _value: T;\n private _listeners: Set<(value: T) => void> = new Set();\n private _initialized: boolean = false; // Track if value has been set from WebSocket\n\n constructor(initialValue: T) {\n this._value = initialValue;\n }\n\n /**\n * Get the current value synchronously\n */\n get value(): T {\n return this._value;\n }\n\n /**\n * Implicit coercion to primitive value (for conditionals/comparisons)\n */\n valueOf(): T {\n return this._value;\n }\n\n /**\n * String representation\n */\n toString(): string {\n return String(this._value);\n }\n\n /**\n * Symbol.toPrimitive for implicit type coercion\n * Allows usage in conditionals: if (observable) { ... }\n */\n [Symbol.toPrimitive](hint: string): T | string {\n if (hint === 'string') {\n return String(this._value);\n }\n return this._value;\n }\n\n /**\n * Subscribe to value changes\n *\n * The callback is called immediately with the current value ONLY if\n * the Observable has been initialized (setValue() called at least once).\n * This prevents callbacks from firing with default/uninitialized values.\n *\n * @param callback - Function to call when value changes\n * @returns Cleanup function to unsubscribe\n *\n * @example\n * ```typescript\n * const cleanup = observable.onChange((value) => {\n * console.log(\"New value:\", value);\n * });\n *\n * // Later: unsubscribe\n * cleanup();\n * ```\n */\n onChange(callback: (value: T) => void): () => void {\n this._listeners.add(callback);\n // Call immediately with current value ONLY if initialized\n if (this._initialized) {\n callback(this._value);\n }\n // Return cleanup function\n return () => this._listeners.delete(callback);\n }\n\n /**\n * Update the value and notify listeners\n *\n * Triggers callbacks if:\n * 1. This is the first setValue() call (initialization from WebSocket), OR\n * 2. The new value is different from current value\n *\n * Uses strict equality (===) for comparison.\n *\n * @param value - New value to set\n *\n * @internal This method is called by DeviceState when receiving WebSocket updates\n */\n setValue(value: T): void {\n const isFirstInit = !this._initialized;\n\n // Mark as initialized (first setValue call from WebSocket)\n if (isFirstInit) {\n this._initialized = true;\n }\n\n // Notify listeners if this is initialization OR value changed\n if (isFirstInit || this._value !== value) {\n this._value = value;\n // Notify all listeners\n this._listeners.forEach((cb) => {\n try {\n cb(value);\n } catch (error) {\n console.error('Error in Observable onChange callback:', error);\n }\n });\n }\n }\n\n /**\n * Get the number of active listeners\n * @internal Used for debugging/testing\n */\n get listenerCount(): number {\n return this._listeners.size;\n }\n}\n",
39
64
  "/**\n * DeviceState - Reactive device state management via WebSocket\n *\n * Provides real-time Observable properties for device state (WiFi, battery, hotspot, etc.)\n * Uses flat structure matching GlassesInfo field names exactly.\n *\n * @example\n * ```typescript\n * // Synchronous read\n * if (session.device.state.wifiConnected) {\n * console.log(\"Connected to:\", session.device.state.wifiSsid.value);\n * }\n *\n * // Reactive subscription\n * session.device.state.wifiConnected.onChange((connected) => {\n * console.log(\"WiFi status:\", connected);\n * });\n *\n * session.device.state.batteryLevel.onChange((level) => {\n * console.log(\"Battery:\", level, \"%\");\n * });\n * ```\n */\n\nimport {Observable} from '../../utils/Observable';\nimport type {GlassesInfo} from '@mentra/types';\nimport type {AppSession} from './index';\n\nexport class DeviceState {\n // ============================================================================\n // WiFi Status Observables\n // ============================================================================\n\n /** WiFi connection status */\n public readonly wifiConnected: Observable<boolean>;\n\n /** WiFi network SSID (null if not connected) */\n public readonly wifiSsid: Observable<string | null>;\n\n /** WiFi local IP address (null if not connected) */\n public readonly wifiLocalIp: Observable<string | null>;\n\n // ============================================================================\n // Battery Status Observables\n // ============================================================================\n\n /** Glasses battery level (0-100, null if unknown) */\n public readonly batteryLevel: Observable<number | null>;\n\n /** Glasses charging status (null if unknown) */\n public readonly charging: Observable<boolean | null>;\n\n /** Case battery level (0-100, null if no case or unknown) */\n public readonly caseBatteryLevel: Observable<number | null>;\n\n /** Case charging status (null if no case or unknown) */\n public readonly caseCharging: Observable<boolean | null>;\n\n /** Case open/closed status (true = open, null if no case or unknown) */\n public readonly caseOpen: Observable<boolean | null>;\n\n /** Case removed status (true = glasses not in case, null if no case or unknown) */\n public readonly caseRemoved: Observable<boolean | null>;\n\n // ============================================================================\n // Hotspot Status Observables\n // ============================================================================\n\n /** Hotspot enabled status (null if not supported or unknown) */\n public readonly hotspotEnabled: Observable<boolean | null>;\n\n /** Hotspot SSID (null if disabled or unknown) */\n public readonly hotspotSsid: Observable<string | null>;\n\n // ============================================================================\n // Connection & Device Info Observables\n // ============================================================================\n\n /** Glasses connected to phone/cloud (true = connected) */\n public readonly connected: Observable<boolean>;\n\n /** Glasses model name (e.g., \"even_g1\", \"xreal_air_2_pro\") */\n public readonly modelName: Observable<string | null>;\n\n // ============================================================================\n // Internal State\n // ============================================================================\n\n private appSession: AppSession;\n\n constructor(appSession: AppSession) {\n this.appSession = appSession;\n\n // Initialize all observables with default/safe values\n // These will be updated when cloud sends DEVICE_STATE_UPDATE\n\n // WiFi (default: disconnected)\n this.wifiConnected = new Observable<boolean>(false);\n this.wifiSsid = new Observable<string | null>(null);\n this.wifiLocalIp = new Observable<string | null>(null);\n\n // Battery (default: unknown)\n this.batteryLevel = new Observable<number | null>(null);\n this.charging = new Observable<boolean | null>(null);\n this.caseBatteryLevel = new Observable<number | null>(null);\n this.caseCharging = new Observable<boolean | null>(null);\n this.caseOpen = new Observable<boolean | null>(null);\n this.caseRemoved = new Observable<boolean | null>(null);\n\n // Hotspot (default: disabled/unknown)\n this.hotspotEnabled = new Observable<boolean | null>(null);\n this.hotspotSsid = new Observable<string | null>(null);\n\n // Connection (default: disconnected)\n this.connected = new Observable<boolean>(false);\n this.modelName = new Observable<string | null>(null);\n }\n\n /**\n * Update device state from WebSocket message\n *\n * Called internally by AppSession when DEVICE_STATE_UPDATE message is received.\n * Only updates Observables for fields present in the state object.\n * Observables automatically notify listeners only if value changed.\n *\n * @param state - Partial device state (only changed fields, or full snapshot)\n * @internal\n */\n updateFromMessage(state: Partial<GlassesInfo>): void {\n // Connection state\n if (state.connected !== undefined) {\n this.connected.setValue(state.connected);\n }\n if (state.modelName !== undefined) {\n this.modelName.setValue(state.modelName);\n }\n\n // WiFi state\n if (state.wifiConnected !== undefined) {\n this.wifiConnected.setValue(state.wifiConnected);\n }\n if (state.wifiSsid !== undefined) {\n this.wifiSsid.setValue(state.wifiSsid ?? null);\n }\n if (state.wifiLocalIp !== undefined) {\n this.wifiLocalIp.setValue(state.wifiLocalIp ?? null);\n }\n\n // Battery state\n if (state.batteryLevel !== undefined) {\n this.batteryLevel.setValue(state.batteryLevel ?? null);\n }\n if (state.charging !== undefined) {\n this.charging.setValue(state.charging ?? null);\n }\n if (state.caseBatteryLevel !== undefined) {\n this.caseBatteryLevel.setValue(state.caseBatteryLevel ?? null);\n }\n if (state.caseCharging !== undefined) {\n this.caseCharging.setValue(state.caseCharging ?? null);\n }\n if (state.caseOpen !== undefined) {\n this.caseOpen.setValue(state.caseOpen ?? null);\n }\n if (state.caseRemoved !== undefined) {\n this.caseRemoved.setValue(state.caseRemoved ?? null);\n }\n\n // Hotspot state\n if (state.hotspotEnabled !== undefined) {\n this.hotspotEnabled.setValue(state.hotspotEnabled ?? null);\n }\n if (state.hotspotSsid !== undefined) {\n this.hotspotSsid.setValue(state.hotspotSsid ?? null);\n }\n }\n\n /**\n * Get snapshot of current device state\n *\n * Returns a plain object with current values of all Observables.\n * Useful for compatibility with REST-style code or debugging.\n *\n * @returns Current device state as GlassesInfo partial object\n *\n * @example\n * ```typescript\n * const currentState = session.device.state.getSnapshot();\n * console.log(\"Current state:\", currentState);\n * ```\n */\n getSnapshot(): Partial<GlassesInfo> {\n return {\n // Connection\n connected: this.connected.value,\n modelName: this.modelName.value ?? undefined,\n\n // WiFi\n wifiConnected: this.wifiConnected.value,\n wifiSsid: this.wifiSsid.value ?? undefined,\n wifiLocalIp: this.wifiLocalIp.value ?? undefined,\n\n // Battery\n batteryLevel: this.batteryLevel.value ?? undefined,\n charging: this.charging.value ?? undefined,\n caseBatteryLevel: this.caseBatteryLevel.value ?? undefined,\n caseCharging: this.caseCharging.value ?? undefined,\n caseOpen: this.caseOpen.value ?? undefined,\n caseRemoved: this.caseRemoved.value ?? undefined,\n\n // Hotspot\n hotspotEnabled: this.hotspotEnabled.value ?? undefined,\n hotspotSsid: this.hotspotSsid.value ?? undefined,\n };\n }\n}\n",
40
- "// src/app/webview/index.ts\nimport axios from \"axios\";\nimport { Response, NextFunction } from \"express\";\nimport { AuthenticatedRequest } from \"../../types\";\n// Note: Your Express app needs to use cookie-parser middleware for this to work\n// Example: app.use(require('cookie-parser')());\nimport * as crypto from \"crypto\";\nimport { KEYUTIL, KJUR, RSAKey } from \"jsrsasign\";\nimport { AppSession } from \"../session\";\n\nconst userTokenPublicKey =\n process.env.MENTRAOS_CLOUD_USER_TOKEN_PUBLIC_KEY ||\n \"-----BEGIN PUBLIC KEY-----\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0Yt2RtNOdeKQxWMY0c84\\nADpY1Jy58YWZhaEgP2A5tBwFUKgy/TH9gQLWZjQ3dQ/6XXO8qq0kluoYFqM7ZDRF\\nzJ0E4Yi0WQncioLRcCx4q8pDmqY9vPKgv6PruJdFWca0l0s3gZ3BqSeWum/C23xK\\nFPHPwi8gvRdc6ALrkcHeciM+7NykU8c0EY8PSitNL+Tchti95kGu+j6APr5vNewi\\nzRpQGOdqaLWe+ahHmtj6KtUZjm8o6lan4f/o08C6litizguZXuw2Nn/Kd9fFI1xF\\nIVNJYMy9jgGaOi71+LpGw+vIpwAawp/7IvULDppvY3DdX5nt05P1+jvVJXPxMKzD\\nTQIDAQAB\\n-----END PUBLIC KEY-----\";\n\n/**\n * Extracts the temporary token from a URL string.\n * @param url The URL string, typically window.location.href.\n * @returns The token string or null if not found.\n */\nexport function extractTempToken(url: string): string | null {\n try {\n const parsedUrl = new URL(url);\n return parsedUrl.searchParams.get(\"aos_temp_token\");\n } catch (e) {\n console.error(\"Error parsing URL for temp token:\", e);\n return null;\n }\n}\n\n/**\n * Exchanges a temporary token for a user ID with the MentraOS Cloud.\n * This should be called from the App's backend server.\n * @param cloudApiUrl The base URL of the MentraOS Cloud API.\n * @param tempToken The temporary token obtained from the webview URL.\n * @param apiKey Your App's secret API key.\n * @returns A Promise that resolves with an object containing the userId.\n * @throws Throws an error if the exchange fails (e.g., invalid token, expired, network error).\n */\nexport async function exchangeToken(\n cloudApiUrl: string,\n tempToken: string,\n apiKey: string,\n packageName: string,\n): Promise<{ userId: string }> {\n const endpoint = `${cloudApiUrl}/api/auth/exchange-user-token`;\n console.log(`Exchanging token for user at ${endpoint}`);\n try {\n const response = await axios.post(\n endpoint,\n { aos_temp_token: tempToken, packageName: packageName },\n {\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${apiKey}`,\n },\n timeout: 10000, // 10 second timeout\n },\n );\n\n if (\n response.status === 200 &&\n response.data.success &&\n response.data.userId\n ) {\n return { userId: response.data.userId };\n } else {\n // Handle specific error messages from the server if available\n const errorMessage =\n response.data?.error || `Failed with status ${response.status}`;\n throw new Error(errorMessage);\n }\n } catch (error) {\n if (axios.isAxiosError(error)) {\n const status = error.response?.status;\n const data = error.response?.data;\n const message =\n data?.error || error.message || \"Unknown error during token exchange\";\n console.error(`Token exchange failed with status ${status}: ${message}`);\n throw new Error(`Token exchange failed: ${message}`);\n } else {\n console.error(\"Unexpected error during token exchange:\", error);\n throw new Error(\"An unexpected error occurred during token exchange.\");\n }\n }\n}\n\n/**\n * Signs a user ID to create a secure session token.\n * @param userId The user ID to sign\n * @param secret The secret key used for signing\n * @returns A signed session token string\n */\nfunction signSession(userId: string, secret: string): string {\n // Format: userId.timestamp.signature\n const timestamp = Date.now();\n const data = `${userId}|${timestamp}`;\n const signature = crypto\n .createHmac(\"sha256\", secret)\n .update(data)\n .digest(\"hex\");\n\n return `${data}|${signature}`;\n}\n\n/**\n * Verifies and extracts the user ID from a signed session token.\n * @param token The signed session token\n * @param secret The secret key used for verification\n * @param maxAge The maximum age of the token in milliseconds\n * @returns The extracted user ID if valid, or null if invalid\n */\nfunction verifySession(\n token: string,\n secret: string,\n maxAge?: number,\n): string | null {\n try {\n const parts = token.split(\"|\");\n if (parts.length !== 3) return null;\n\n const [userId, timestampStr, signature] = parts;\n const timestamp = parseInt(timestampStr, 10);\n\n // Check if token has expired\n if (maxAge && Date.now() - timestamp > maxAge) {\n console.log(\n `Session token expired: ${token}. Parsed date is ${timestamp}, meaning age is ${Date.now() - timestamp}, but maxAge is ${maxAge}`,\n );\n return null;\n }\n\n // Verify signature\n const data = `${userId}|${timestamp}`;\n const expectedSignature = crypto\n .createHmac(\"sha256\", secret)\n .update(data)\n .digest(\"hex\");\n\n if (signature !== expectedSignature) {\n console.log(\n `Session token signature mismatch: ${signature} !== ${expectedSignature}`,\n );\n return null;\n }\n\n return userId;\n } catch (error) {\n console.error(\"Session verification failed:\", error);\n return null;\n }\n}\n\n/**\n * Verifies a signed user token and extracts the user ID from it\n * @param signedUserToken The JWT token to verify\n * @returns The user ID (subject) from the token, or null if invalid\n */\nasync function verifySignedUserToken(\n signedUserToken: string,\n): Promise<string | null> {\n try {\n // 1. Parse the PEM public key into a jsrsasign key object\n const publicKeyObj = KEYUTIL.getKey(userTokenPublicKey) as RSAKey;\n // 2. Verify JWT signature + claims (issuer, exp, iat) with 2-min tolerance\n const isValid = KJUR.jws.JWS.verifyJWT(signedUserToken, publicKeyObj, {\n alg: [\"RS256\"],\n iss: [\"https://prod.augmentos.cloud\"],\n verifyAt: KJUR.jws.IntDate.get(\"now\"),\n gracePeriod: 120,\n });\n if (!isValid) return null;\n\n // 3. Decode payload and return the subject (user ID)\n const parsed = KJUR.jws.JWS.parse(signedUserToken);\n return (parsed.payloadObj as { sub: string }).sub || null;\n } catch (e) {\n console.error(\"[verifySignedUserToken] Error verifying token:\", e);\n return null;\n }\n}\n\n/**\n * Verifies a frontend token by comparing it to a secure hash of the API key\n * @param frontendToken The token to verify (should be a hash of the API key)\n * @param apiKey The API key to hash and compare against\n * @param userId Optional user ID that may be embedded in the token format\n * @returns The user ID if the token is valid, or null if invalid\n */\nfunction verifyFrontendToken(\n frontendToken: string,\n apiKey: string,\n): string | null {\n try {\n // Check if the token contains a user ID and hash separated by a colon\n const tokenParts = frontendToken.split(\":\");\n\n if (tokenParts.length === 2) {\n // Format: userId:hash\n const [tokenUserId, tokenHash] = tokenParts;\n\n // Align the hashing algorithm with server-side `hashWithApiKey`\n // 1. Hash the API key first (server only stores the hashed version)\n const hashedApiKey = crypto\n .createHash(\"sha256\")\n .update(apiKey)\n .digest(\"hex\");\n // 2. Create the expected hash using userId + hashedApiKey (same order & update calls)\n const expectedHash = crypto\n .createHash(\"sha256\")\n .update(tokenUserId)\n .update(hashedApiKey)\n .digest(\"hex\");\n\n if (tokenHash === expectedHash) {\n return tokenUserId;\n }\n } else {\n throw new Error(\"Invalid frontend token format\");\n }\n\n return null;\n } catch (error) {\n console.error(\"Frontend token verification failed:\", error);\n return null;\n }\n}\n\nfunction validateCloudApiUrlChecksum(\n checksum: string,\n cloudApiUrl: string,\n apiKey: string,\n): boolean {\n const hashedApiKey = crypto.createHash(\"sha256\").update(apiKey).digest(\"hex\");\n const expectedChecksum = crypto\n .createHash(\"sha256\")\n .update(cloudApiUrl)\n .update(hashedApiKey)\n .digest(\"hex\");\n\n return expectedChecksum === checksum;\n}\n/**\n * Express middleware for automatically handling the token exchange.\n * Assumes API key and Cloud URL are available (e.g., via environment variables).\n * Adds `req.authUserId` if successful.\n *\n * @param options Configuration options.\n * @param options.cloudApiUrl The base URL of the MentraOS Cloud API.\n * @param options.apiKey Your App's secret API key.\n * @param options.tokenQueryParam The name of the query parameter containing the token (default: 'aos_temp_token').\n * @param options.cookieName The name of the cookie to store the session token (default: 'aos_session').\n * @param options.cookieSecret Secret key used to sign the session cookie. MUST be provided and kept secure.\n * @param options.cookieOptions Options for the session cookie (default: { httpOnly: true, secure: process.env.NODE_ENV === 'production' }).\n */\nexport function createAuthMiddleware(options: {\n apiKey: string;\n packageName: string;\n cookieName?: string;\n cookieSecret: string;\n getAppSessionForUser?: (userId: string) => AppSession | null;\n cookieOptions?: {\n httpOnly?: boolean;\n secure?: boolean;\n maxAge?: number;\n sameSite?: boolean | \"lax\" | \"strict\" | \"none\";\n path?: string;\n };\n}) {\n const {\n apiKey,\n packageName,\n cookieName = \"aos_session\",\n cookieSecret,\n getAppSessionForUser,\n cookieOptions = {\n httpOnly: true,\n secure: process.env.NODE_ENV === \"production\",\n maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days by default\n sameSite: process.env.NODE_ENV === \"production\" ? \"none\" : \"lax\",\n path: \"/\",\n },\n } = options;\n\n if (!apiKey) {\n throw new Error(\"API Key are required for the auth middleware.\");\n }\n\n if (\n !cookieSecret ||\n typeof cookieSecret !== \"string\" ||\n cookieSecret.length < 8\n ) {\n throw new Error(\n \"A strong cookieSecret (at least 8 characters) is required for secure session management.\",\n );\n }\n\n return async (\n req: AuthenticatedRequest,\n res: Response,\n next: NextFunction,\n ) => {\n // First check for temporary token in the query string\n const tempToken = req.query[\"aos_temp_token\"] as string;\n const frontendToken =\n (req.headers.authorization?.replace(\"Bearer \", \"\") as string) ||\n (req.query[\"aos_frontend_token\"] as string);\n const signedUserToken = req.query[\"aos_signed_user_token\"] as string;\n\n // first check for signed user token\n if (signedUserToken) {\n const userId = await verifySignedUserToken(signedUserToken);\n if (userId) {\n // Set the user ID on the request\n req.authUserId = userId;\n if (getAppSessionForUser) {\n const appSession = getAppSessionForUser(userId);\n if (appSession) {\n req.activeSession = appSession;\n } else {\n req.activeSession = null;\n }\n }\n\n // Create a signed session token and store it in a cookie\n const signedSession = signSession(userId, cookieSecret);\n res.cookie(cookieName, signedSession, cookieOptions);\n\n console.log(\n \"[auth.middleware] User ID verified from signed user token: \",\n userId,\n );\n return next();\n } else {\n console.log(\"[auth.middleware] Signed user token invalid\");\n }\n }\n // If temporary token exists, authenticate with it\n if (tempToken) {\n try {\n let cloudApiUrl = `https://api.mentra.glass`;\n const cloudApiUrlFromQuery = req.query[\"cloudApiUrl\"] as string;\n if (cloudApiUrlFromQuery) {\n const cloudApiUrlChecksum = req.query[\n \"cloudApiUrlChecksum\"\n ] as string;\n\n if (\n validateCloudApiUrlChecksum(\n cloudApiUrlChecksum,\n cloudApiUrlFromQuery,\n apiKey,\n )\n ) {\n console.log(\n `Cloud API is being routed to alternate url at request of the server: ${cloudApiUrlFromQuery}`,\n );\n cloudApiUrl = cloudApiUrlFromQuery;\n } else {\n console.error(\n `Server requested alternate cloud url of ${cloudApiUrlFromQuery} but the checksum is invalid (checksum: ${cloudApiUrlChecksum}). Using default cloud url of ${cloudApiUrl} instead.`,\n );\n }\n }\n\n const { userId } = await exchangeToken(\n cloudApiUrl,\n tempToken,\n apiKey,\n packageName,\n );\n\n // Set the user ID on the request\n req.authUserId = userId;\n if (getAppSessionForUser) {\n const appSession = getAppSessionForUser(userId);\n if (appSession) {\n req.activeSession = appSession;\n }\n }\n\n // Create a signed session token and store it in a cookie\n const signedSession = signSession(userId, cookieSecret);\n res.cookie(cookieName, signedSession, cookieOptions);\n\n console.log(\n \"[auth.middleware] User ID verified from temporary token: \",\n userId,\n );\n\n return next();\n } catch (error) {\n console.error(\"Webview token exchange failed:\", error);\n // Temporary token is invalid\n }\n }\n\n if (frontendToken) {\n // Check for user ID in headers if not embedded in token\n const userId = verifyFrontendToken(frontendToken, apiKey);\n\n if (userId) {\n req.authUserId = userId;\n if (getAppSessionForUser) {\n const appSession = getAppSessionForUser(userId);\n if (appSession) {\n req.activeSession = appSession;\n }\n }\n // Create a signed session token and store it in a cookie\n const signedSession = signSession(userId, cookieSecret);\n res.cookie(cookieName, signedSession, cookieOptions);\n console.log(\n \"[auth.middleware] User ID verified from frontend user token: \",\n userId,\n );\n return next();\n } else {\n console.log(\"[auth.middleware] Frontend token invalid\");\n }\n }\n\n // No valid temporary token, check for existing session cookie\n const sessionCookie = req.cookies?.[cookieName];\n\n if (sessionCookie) {\n try {\n // Verify the signed session cookie and extract the user ID\n const userId = verifySession(\n sessionCookie,\n cookieSecret,\n cookieOptions.maxAge,\n );\n if (userId) {\n req.authUserId = userId;\n if (getAppSessionForUser) {\n const appSession = getAppSessionForUser(userId);\n if (appSession) {\n req.activeSession = appSession;\n }\n }\n return next();\n }\n\n // Invalid or expired session, clear the cookie\n res.clearCookie(cookieName, { path: cookieOptions.path });\n } catch (error) {\n console.error(\"Invalid session cookie:\", error);\n // Clear the invalid cookie\n res.clearCookie(cookieName, { path: cookieOptions.path });\n }\n }\n\n // No valid authentication method found, proceed without setting req.authUserId\n next();\n };\n}\n",
41
- "/**\n * updates.ts\n *\n * This file defines constant messages that should be displayed\n * in the terminal to notify developers about new SDK releases.\n *\n * Each function generates a stylized ASCII message (banner-style)\n * that highlights the latest SDK version and provides the npm install command.\n * https://patorjk.com/software/taag/\n *\n * These messages are intended to be logged to the console or shown in\n * terminal output so developers are aware of updates in a clear\n * and visually distinct way.\n *\n *\n */\n\nimport chalk from \"chalk\";\nimport boxen from \"boxen\";\nimport { mentraLogo_1, newUpdateText } from \"./logos\";\n\nconst createUpdateNotification = (versionNumb: string): string => {\n const line = chalk.bold.gray(\"-------------------------------\");\n const logo = chalk.cyan(mentraLogo_1);\n const title = chalk.bold.cyan(newUpdateText);\n const versionMessage = `Version ${chalk.bold.cyan(`SDK VERSION V${versionNumb} is out! 🎉`)}`;\n const currentNote = chalk.yellow(\"You are running an older version\");\n const instructions = chalk.yellow(\"Update to the latest version with:\");\n const command = chalk.green.bold(\"bun install @mentra/sdk@latest\");\n\n const content = [\n logo,\n title,\n line,\n versionMessage,\n currentNote,\n line,\n instructions,\n command,\n ].join(\"\\n\");\n\n return boxen(content, {\n padding: 1,\n margin: 1,\n borderStyle: \"round\",\n borderColor: \"cyan\",\n textAlignment: \"left\",\n });\n};\n\nexport const newSDKUpdate = (versionNumb: string): string => {\n return createUpdateNotification(versionNumb);\n};\n",
42
- "/**\n * 🔐 App Token Module\n *\n * Provides utilities for working with App tokens.\n */\nexport * from './utils';"
65
+ "// src/app/webview/index.ts\nimport * as crypto from \"crypto\";\n\nimport axios from \"axios\";\nimport { Hono } from \"hono\";\nimport type { Context, Next, MiddlewareHandler } from \"hono\";\nimport { getCookie, setCookie, deleteCookie } from \"hono/cookie\";\nimport type { CookieOptions } from \"hono/utils/cookie\";\nimport { KEYUTIL, KJUR, RSAKey } from \"jsrsasign\";\n\nimport { AuthVariables, AuthenticatedRequest, MentraAuthContext, MentraAuthHonoContext } from \"../../types\";\nimport { AppSession } from \"../session\";\n\nconst userTokenPublicKey =\n process.env.MENTRAOS_CLOUD_USER_TOKEN_PUBLIC_KEY ||\n \"-----BEGIN PUBLIC KEY-----\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0Yt2RtNOdeKQxWMY0c84\\nADpY1Jy58YWZhaEgP2A5tBwFUKgy/TH9gQLWZjQ3dQ/6XXO8qq0kluoYFqM7ZDRF\\nzJ0E4Yi0WQncioLRcCx4q8pDmqY9vPKgv6PruJdFWca0l0s3gZ3BqSeWum/C23xK\\nFPHPwi8gvRdc6ALrkcHeciM+7NykU8c0EY8PSitNL+Tchti95kGu+j6APr5vNewi\\nzRpQGOdqaLWe+ahHmtj6KtUZjm8o6lan4f/o08C6litizguZXuw2Nn/Kd9fFI1xF\\nIVNJYMy9jgGaOi71+LpGw+vIpwAawp/7IvULDppvY3DdX5nt05P1+jvVJXPxMKzD\\nTQIDAQAB\\n-----END PUBLIC KEY-----\";\n\n/**\n * Extracts the temporary token from a URL string.\n * @param url The URL string, typically window.location.href.\n * @returns The token string or null if not found.\n */\nexport function extractTempToken(url: string): string | null {\n try {\n const parsedUrl = new URL(url);\n return parsedUrl.searchParams.get(\"aos_temp_token\");\n } catch (e) {\n console.error(\"Error parsing URL for temp token:\", e);\n return null;\n }\n}\n\n/**\n * Exchanges a temporary token for a user ID with the MentraOS Cloud.\n * This should be called from the App's backend server.\n * @param cloudApiUrl The base URL of the MentraOS Cloud API.\n * @param tempToken The temporary token obtained from the webview URL.\n * @param apiKey Your App's secret API key.\n * @returns A Promise that resolves with an object containing the userId.\n * @throws Throws an error if the exchange fails (e.g., invalid token, expired, network error).\n */\nexport async function exchangeToken(\n cloudApiUrl: string,\n tempToken: string,\n apiKey: string,\n packageName: string,\n): Promise<{ userId: string }> {\n const endpoint = `${cloudApiUrl}/api/auth/exchange-user-token`;\n console.log(`Exchanging token for user at ${endpoint}`);\n try {\n const response = await axios.post(\n endpoint,\n { aos_temp_token: tempToken, packageName: packageName },\n {\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${apiKey}`,\n },\n timeout: 10000, // 10 second timeout\n },\n );\n\n if (response.status === 200 && response.data.success && response.data.userId) {\n return { userId: response.data.userId };\n } else {\n // Handle specific error messages from the server if available\n const errorMessage = response.data?.error || `Failed with status ${response.status}`;\n throw new Error(errorMessage);\n }\n } catch (error) {\n if (axios.isAxiosError(error)) {\n const status = error.response?.status;\n const data = error.response?.data;\n const message = data?.error || error.message || \"Unknown error during token exchange\";\n console.error(`Token exchange failed with status ${status}: ${message}`);\n throw new Error(`Token exchange failed: ${message}`);\n } else {\n console.error(\"Unexpected error during token exchange:\", error);\n throw new Error(\"An unexpected error occurred during token exchange.\");\n }\n }\n}\n\n/**\n * Signs a user ID to create a secure session token.\n * @param userId The user ID to sign\n * @param secret The secret key used for signing\n * @returns A signed session token string\n */\nfunction signSession(userId: string, secret: string): string {\n // Format: userId.timestamp.signature\n const timestamp = Date.now();\n const data = `${userId}|${timestamp}`;\n const signature = crypto.createHmac(\"sha256\", secret).update(data).digest(\"hex\");\n\n return `${data}|${signature}`;\n}\n\n/**\n * Verifies and extracts the user ID from a signed session token.\n * @param token The signed session token\n * @param secret The secret key used for verification\n * @param maxAge The maximum age of the token in milliseconds\n * @returns The extracted user ID if valid, or null if invalid\n */\nfunction verifySession(token: string, secret: string, maxAge?: number): string | null {\n try {\n const parts = token.split(\"|\");\n if (parts.length !== 3) return null;\n\n const [userId, timestampStr, signature] = parts;\n const timestamp = parseInt(timestampStr, 10);\n\n // Check if token has expired\n if (maxAge && Date.now() - timestamp > maxAge) {\n console.log(\n `Session token expired: ${token}. Parsed date is ${timestamp}, meaning age is ${Date.now() - timestamp}, but maxAge is ${maxAge}`,\n );\n return null;\n }\n\n // Verify signature\n const data = `${userId}|${timestamp}`;\n const expectedSignature = crypto.createHmac(\"sha256\", secret).update(data).digest(\"hex\");\n\n if (signature !== expectedSignature) {\n console.log(`Session token signature mismatch: ${signature} !== ${expectedSignature}`);\n return null;\n }\n\n return userId;\n } catch (error) {\n console.error(\"Session verification failed:\", error);\n return null;\n }\n}\n\n/**\n * Verifies a signed user token and extracts the user ID from it\n * @param signedUserToken The JWT token to verify\n * @returns The user ID (subject) from the token, or null if invalid\n */\nasync function verifySignedUserToken(signedUserToken: string): Promise<string | null> {\n try {\n // 1. Parse the PEM public key into a jsrsasign key object\n const publicKeyObj = KEYUTIL.getKey(userTokenPublicKey) as RSAKey;\n // 2. Verify JWT signature + claims (issuer, exp, iat) with 2-min tolerance\n const isValid = KJUR.jws.JWS.verifyJWT(signedUserToken, publicKeyObj, {\n alg: [\"RS256\"],\n iss: [\"https://prod.augmentos.cloud\"],\n verifyAt: KJUR.jws.IntDate.get(\"now\"),\n gracePeriod: 120,\n });\n if (!isValid) return null;\n\n // 3. Decode payload and return the subject (user ID)\n const parsed = KJUR.jws.JWS.parse(signedUserToken);\n return (parsed.payloadObj as { sub: string }).sub || null;\n } catch (e) {\n console.error(\"[verifySignedUserToken] Error verifying token:\", e);\n return null;\n }\n}\n\n/**\n * Verifies a frontend token by comparing it to a secure hash of the API key\n * @param frontendToken The token to verify (should be a hash of the API key)\n * @param apiKey The API key to hash and compare against\n * @param userId Optional user ID that may be embedded in the token format\n * @returns The user ID if the token is valid, or null if invalid\n */\nfunction verifyFrontendToken(frontendToken: string, apiKey: string): string | null {\n try {\n // Check if the token contains a user ID and hash separated by a colon\n const tokenParts = frontendToken.split(\":\");\n\n if (tokenParts.length === 2) {\n // Format: userId:hash\n const [tokenUserId, tokenHash] = tokenParts;\n\n // Align the hashing algorithm with server-side `hashWithApiKey`\n // 1. Hash the API key first (server only stores the hashed version)\n const hashedApiKey = crypto.createHash(\"sha256\").update(apiKey).digest(\"hex\");\n // 2. Create the expected hash using userId + hashedApiKey (same order & update calls)\n const expectedHash = crypto.createHash(\"sha256\").update(tokenUserId).update(hashedApiKey).digest(\"hex\");\n\n if (tokenHash === expectedHash) {\n return tokenUserId;\n }\n } else {\n throw new Error(\"Invalid frontend token format\");\n }\n\n return null;\n } catch (error) {\n console.error(\"Frontend token verification failed:\", error);\n return null;\n }\n}\n\nfunction validateCloudApiUrlChecksum(checksum: string, cloudApiUrl: string, apiKey: string): boolean {\n const hashedApiKey = crypto.createHash(\"sha256\").update(apiKey).digest(\"hex\");\n const expectedChecksum = crypto.createHash(\"sha256\").update(cloudApiUrl).update(hashedApiKey).digest(\"hex\");\n\n return expectedChecksum === checksum;\n}\n/**\n * Hono middleware for automatically handling the token exchange.\n * Assumes API key and Cloud URL are available (e.g., via environment variables).\n * Sets context variables `authUserId` and `activeSession` if successful.\n *\n * @param options Configuration options.\n * @param options.apiKey Your App's secret API key.\n * @param options.packageName Your App's package name.\n * @param options.cookieName The name of the cookie to store the session token (default: 'aos_session').\n * @param options.cookieSecret Secret key used to sign the session cookie. MUST be provided and kept secure.\n * @param options.cookieOptions Options for the session cookie.\n */\nexport function createAuthMiddleware(options: {\n apiKey: string;\n packageName: string;\n cookieName?: string;\n cookieSecret: string;\n getAppSessionForUser?: (userId: string) => AppSession | null;\n cookieOptions?: {\n httpOnly?: boolean;\n secure?: boolean;\n maxAge?: number;\n sameSite?: \"Lax\" | \"Strict\" | \"None\";\n path?: string;\n };\n}): MiddlewareHandler<{ Variables: AuthVariables }> {\n const {\n apiKey,\n packageName,\n // Default to packageName-session to match createMentraAuthRoutes.\n // \"aos_session\" was the old default — we fall back to it below for\n // backwards compatibility with 2.x apps that may have existing cookies.\n cookieName = `${packageName}-session`,\n cookieSecret,\n getAppSessionForUser,\n cookieOptions = {\n httpOnly: true,\n secure: process.env.NODE_ENV === \"production\",\n maxAge: 30 * 24 * 60 * 60, // 30 days in seconds for Hono\n sameSite: process.env.NODE_ENV === \"production\" ? \"None\" : \"Lax\",\n path: \"/\",\n },\n } = options;\n\n // Legacy cookie name used by 2.x Express SDK and early Hono SDK versions.\n // We check this as a fallback and silently migrate the user to the new name.\n const legacyCookieName = \"aos_session\";\n\n if (!apiKey) {\n throw new Error(\"API Key is required for the auth middleware.\");\n }\n\n if (!cookieSecret || typeof cookieSecret !== \"string\" || cookieSecret.length < 8) {\n throw new Error(\"A strong cookieSecret (at least 8 characters) is required for secure session management.\");\n }\n\n return async (c: Context<{ Variables: AuthVariables }>, next: Next) => {\n // Helper to set auth and continue\n const setAuthAndContinue = async (userId: string) => {\n c.set(\"authUserId\", userId);\n if (getAppSessionForUser) {\n const appSession = getAppSessionForUser(userId);\n c.set(\"activeSession\", appSession);\n } else {\n c.set(\"activeSession\", null);\n }\n\n // Create and set session cookie\n const signedSession = signSession(userId, cookieSecret);\n setCookie(c, cookieName, signedSession, cookieOptions as CookieOptions);\n await next();\n };\n\n // Get query params and headers\n const tempToken = c.req.query(\"aos_temp_token\");\n const signedUserToken = c.req.query(\"aos_signed_user_token\");\n const authHeader = c.req.header(\"authorization\");\n const frontendToken = authHeader?.replace(\"Bearer \", \"\") || c.req.query(\"aos_frontend_token\");\n\n // First check for signed user token\n if (signedUserToken) {\n const userId = await verifySignedUserToken(signedUserToken);\n if (userId) {\n console.log(\"[auth.middleware] User ID verified from signed user token:\", userId);\n return setAuthAndContinue(userId);\n } else {\n console.log(\"[auth.middleware] Signed user token invalid\");\n }\n }\n\n // If temporary token exists, authenticate with it\n if (tempToken) {\n try {\n let cloudApiUrl = `https://api.mentra.glass`;\n const cloudApiUrlFromQuery = c.req.query(\"cloudApiUrl\");\n if (cloudApiUrlFromQuery) {\n const cloudApiUrlChecksum = c.req.query(\"cloudApiUrlChecksum\");\n\n if (cloudApiUrlChecksum && validateCloudApiUrlChecksum(cloudApiUrlChecksum, cloudApiUrlFromQuery, apiKey)) {\n console.log(`Cloud API is being routed to alternate url at request of the server: ${cloudApiUrlFromQuery}`);\n cloudApiUrl = cloudApiUrlFromQuery;\n } else {\n console.error(\n `Server requested alternate cloud url of ${cloudApiUrlFromQuery} but the checksum is invalid. Using default cloud url.`,\n );\n }\n }\n\n const { userId } = await exchangeToken(cloudApiUrl, tempToken, apiKey, packageName);\n\n console.log(\"[auth.middleware] User ID verified from temporary token:\", userId);\n return setAuthAndContinue(userId);\n } catch (error) {\n console.error(\"Webview token exchange failed:\", error);\n // Continue to check other auth methods\n }\n }\n\n // Check frontend token\n if (frontendToken) {\n const userId = verifyFrontendToken(frontendToken, apiKey);\n\n if (userId) {\n console.log(\"[auth.middleware] User ID verified from frontend user token:\", userId);\n return setAuthAndContinue(userId);\n } else {\n console.log(\"[auth.middleware] Frontend token invalid\");\n }\n }\n\n // No valid temporary token, check for existing session cookie.\n // Check the current cookie name first, then fall back to the legacy\n // \"aos_session\" name used by 2.x apps and early Hono SDK versions.\n const sessionCookie =\n getCookie(c, cookieName) ?? (cookieName !== legacyCookieName ? getCookie(c, legacyCookieName) : undefined);\n\n if (sessionCookie) {\n try {\n // Verify the signed session cookie and extract the user ID\n // Convert maxAge from seconds to milliseconds for verifySession\n const userId = verifySession(\n sessionCookie,\n cookieSecret,\n cookieOptions.maxAge ? cookieOptions.maxAge * 1000 : undefined,\n );\n if (userId) {\n c.set(\"authUserId\", userId);\n if (getAppSessionForUser) {\n const appSession = getAppSessionForUser(userId);\n c.set(\"activeSession\", appSession);\n } else {\n c.set(\"activeSession\", null);\n }\n // If the cookie was found under the legacy name, silently migrate\n // it to the current name so future requests use the new cookie.\n if (!getCookie(c, cookieName)) {\n setCookie(c, cookieName, sessionCookie, cookieOptions as CookieOptions);\n deleteCookie(c, legacyCookieName, { path: cookieOptions.path });\n }\n return next();\n }\n\n // Invalid or expired session, clear both cookie names\n deleteCookie(c, cookieName, { path: cookieOptions.path });\n deleteCookie(c, legacyCookieName, { path: cookieOptions.path });\n } catch (error) {\n console.error(\"Invalid session cookie:\", error);\n // Clear the invalid cookie under both names\n deleteCookie(c, cookieName, { path: cookieOptions.path });\n deleteCookie(c, legacyCookieName, { path: cookieOptions.path });\n }\n }\n\n // No valid authentication method found, proceed without setting authUserId\n c.set(\"activeSession\", null);\n await next();\n };\n}\n\n/**\n * Read the authenticated Mentra context from a Hono request context.\n *\n * This helper intentionally hides the underlying context variable names so\n * app code does not depend on implementation details like `authUserId`.\n */\nexport function getMentraAuth(c: MentraAuthHonoContext): MentraAuthContext {\n return {\n userId: c.get(\"authUserId\") ?? null,\n session: c.get(\"activeSession\") ?? null,\n };\n}\n\n/**\n * Read the authenticated Mentra context and throw if no authenticated user\n * is present. Intended for SDK-owned middleware / helpers that want a strict\n * contract after authentication has been enforced.\n */\nexport function requireMentraAuth(c: MentraAuthHonoContext): {\n userId: string;\n session: AppSession | null;\n} {\n const auth = getMentraAuth(c);\n if (!auth.userId) {\n throw new Error(\"Unauthenticated Mentra request\");\n }\n\n return {\n userId: auth.userId,\n session: auth.session,\n };\n}\n\n/**\n * Generates a frontend token for client-side authentication.\n * The token format is userId:hash where hash = sha256(userId + sha256(apiKey))\n * @param userId The user ID to embed in the token\n * @param apiKey The app's API key\n * @returns A frontend token string\n */\nexport function generateFrontendToken(userId: string, apiKey: string): string {\n const hashedApiKey = crypto.createHash(\"sha256\").update(apiKey).digest(\"hex\");\n const hash = crypto.createHash(\"sha256\").update(userId).update(hashedApiKey).digest(\"hex\");\n return `${userId}:${hash}`;\n}\n\n/**\n * Creates a Hono sub-app with authentication routes for frontend token exchange.\n * Mount this at /api/mentra/auth to enable frontend-initiated authentication.\n *\n * This is required for apps using Bun's fullstack dev server where the HTML\n * is served directly by Bun's routes (bypassing Hono middleware).\n *\n * @example\n * ```typescript\n * import { createMentraAuthRoutes } from \"@mentra/sdk\";\n *\n * const app = new MyAppServer({...});\n *\n * app.route(\"/api/mentra/auth\", createMentraAuthRoutes({\n * apiKey: API_KEY,\n * packageName: PACKAGE_NAME,\n * cookieSecret: COOKIE_SECRET,\n * }));\n * ```\n */\nexport function createMentraAuthRoutes(options: {\n apiKey: string;\n packageName: string;\n cookieSecret: string;\n cookieOptions?: {\n httpOnly?: boolean;\n secure?: boolean;\n maxAge?: number;\n sameSite?: \"Lax\" | \"Strict\" | \"None\";\n path?: string;\n };\n}): Hono {\n const {\n apiKey,\n packageName,\n cookieSecret,\n cookieOptions = {\n httpOnly: true,\n secure: process.env.NODE_ENV === \"production\",\n maxAge: 30 * 24 * 60 * 60, // 30 days in seconds\n sameSite: process.env.NODE_ENV === \"production\" ? \"None\" : \"Lax\",\n path: \"/\",\n },\n } = options;\n\n if (!apiKey) {\n throw new Error(\"API Key is required for Mentra auth routes.\");\n }\n\n if (!cookieSecret || typeof cookieSecret !== \"string\" || cookieSecret.length < 8) {\n throw new Error(\"A strong cookieSecret (at least 8 characters) is required.\");\n }\n\n const authRouter = new Hono();\n const cookieName = `${packageName}-session`; // Must match createAuthMiddleware\n\n /**\n * GET /init - Exchange aos_temp_token for session\n * Query params:\n * - aos_temp_token: The temporary token from MentraOS Cloud\n * - cloudApiUrl: (optional) Custom cloud API URL\n * - cloudApiUrlChecksum: (optional) Checksum for custom cloud URL\n */\n authRouter.get(\"/init\", async (c) => {\n const tempToken = c.req.query(\"aos_temp_token\");\n const signedUserToken = c.req.query(\"aos_signed_user_token\");\n\n // First try signed user token (JWT)\n if (signedUserToken) {\n const userId = await verifySignedUserToken(signedUserToken);\n if (userId) {\n const frontendToken = generateFrontendToken(userId, apiKey);\n const signedSession = signSession(userId, cookieSecret);\n setCookie(c, cookieName, signedSession, cookieOptions as CookieOptions);\n\n console.log(\"[mentra/auth/init] User authenticated via signed user token:\", userId);\n return c.json({ success: true, userId, frontendToken });\n }\n return c.json({ success: false, error: \"Invalid signed user token\" }, 401);\n }\n\n // Try temp token exchange\n if (tempToken) {\n try {\n let cloudApiUrl = \"https://api.mentra.glass\";\n const cloudApiUrlFromQuery = c.req.query(\"cloudApiUrl\");\n if (cloudApiUrlFromQuery) {\n const checksum = c.req.query(\"cloudApiUrlChecksum\");\n if (checksum && validateCloudApiUrlChecksum(checksum, cloudApiUrlFromQuery, apiKey)) {\n cloudApiUrl = cloudApiUrlFromQuery;\n }\n }\n\n const { userId } = await exchangeToken(cloudApiUrl, tempToken, apiKey, packageName);\n const frontendToken = generateFrontendToken(userId, apiKey);\n const signedSession = signSession(userId, cookieSecret);\n setCookie(c, cookieName, signedSession, cookieOptions as CookieOptions);\n\n console.log(\"[mentra/auth/init] User authenticated via temp token:\", userId);\n return c.json({ success: true, userId, frontendToken });\n } catch (error) {\n console.error(\"[mentra/auth/init] Token exchange failed:\", error);\n return c.json({ success: false, error: \"Token exchange failed\" }, 401);\n }\n }\n\n return c.json({ success: false, error: \"No token provided\" }, 400);\n });\n\n return authRouter;\n}\n",
66
+ "/**\n * 🔐 App Token Module\n *\n * Provides utilities for working with App tokens.\n */\nexport * from './utils';",
67
+ "/**\n * WebSocketTransport\n *\n * Transport implementation that wraps the `ws` library for cloud/server apps.\n * Used by MentraApp to connect MentraSession instances to the cloud.\n *\n * This is the ONLY file in the SDK that imports `ws` — the session layer\n * and all managers are transport-agnostic and never touch WebSocket directly.\n *\n * @example\n * ```ts\n * const transport = new WebSocketTransport(\"wss://cloud.mentra.glass/ws/miniapp\", {\n * headers: { Authorization: `Bearer ${jwt}` },\n * timeoutMs: 10000,\n * });\n *\n * transport.onMessage((data) => console.log(\"received:\", data));\n * transport.onClose((code, reason) => console.log(\"closed:\", code, reason));\n *\n * await transport.connect();\n * transport.send(JSON.stringify({ type: \"connection_init\", ... }));\n * ```\n */\n\nimport { WebSocket } from \"ws\";\nimport { Transport, TransportState, TransportOptions } from \"./Transport\";\n\n// ─── Configuration ──────────────────────────────────────────────────────────\n\nexport interface WebSocketTransportOptions extends TransportOptions {\n /** WebSocket URL to connect to (wss:// or ws://) */\n url: string;\n\n /** Headers sent during the WebSocket upgrade request */\n headers?: Record<string, string>;\n\n /** Connection timeout in milliseconds (default: 10000) */\n timeoutMs?: number;\n\n /**\n * If true, the transport will attempt to connect immediately on construction.\n * If false (default), call connect() manually.\n */\n connectOnCreate?: boolean;\n}\n\n// ─── Implementation ─────────────────────────────────────────────────────────\n\nexport class WebSocketTransport implements Transport {\n private ws: WebSocket | null = null;\n private _readyState: TransportState = TransportState.CLOSED;\n\n // Handler slots — only one handler per event type (last registration wins)\n private _onMessage: ((data: string) => void) | null = null;\n private _onBinary: ((data: ArrayBuffer) => void) | null = null;\n private _onClose: ((code: number, reason: string) => void) | null = null;\n private _onError: ((error: Error) => void) | null = null;\n\n private readonly url: string;\n private readonly headers: Record<string, string>;\n private readonly timeoutMs: number;\n\n constructor(options: WebSocketTransportOptions) {\n this.url = options.url;\n this.headers = options.headers ?? {};\n this.timeoutMs = options.timeoutMs ?? 10000;\n\n if (options.connectOnCreate) {\n this.connect();\n }\n }\n\n // ─── Transport Interface ────────────────────────────────────────────────\n\n get readyState(): TransportState {\n // Prefer the real WebSocket readyState if available\n if (this.ws) {\n return this.ws.readyState as TransportState;\n }\n return this._readyState;\n }\n\n send(data: string): void {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\n // Silently drop — callers should check readyState if they care\n return;\n }\n try {\n this.ws.send(data);\n } catch {\n // Send failures on a closing socket are expected; swallow them\n }\n }\n\n sendBinary(data: ArrayBuffer | Uint8Array): void {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\n return;\n }\n try {\n this.ws.send(data);\n } catch {\n // Swallow send errors on closing sockets\n }\n }\n\n onMessage(handler: (data: string) => void): void {\n this._onMessage = handler;\n }\n\n onBinary(handler: (data: ArrayBuffer) => void): void {\n this._onBinary = handler;\n }\n\n onClose(handler: (code: number, reason: string) => void): void {\n this._onClose = handler;\n }\n\n onError(handler: (error: Error) => void): void {\n this._onError = handler;\n }\n\n close(code?: number, reason?: string): void {\n if (!this.ws) {\n this._readyState = TransportState.CLOSED;\n return;\n }\n\n if (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING) {\n this._readyState = TransportState.CLOSING;\n try {\n this.ws.close(code ?? 1000, reason ?? \"\");\n } catch {\n // Already closing or closed\n this._readyState = TransportState.CLOSED;\n }\n }\n }\n\n // ─── Connection Management ──────────────────────────────────────────────\n\n /**\n * Establish the WebSocket connection.\n * Resolves when the connection is open, rejects on error or timeout.\n */\n connect(): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n if (this.ws) {\n // Clean up previous connection\n this.detachListeners();\n if (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING) {\n try {\n this.ws.close();\n } catch {\n // Ignore\n }\n }\n this.ws = null;\n }\n\n this._readyState = TransportState.CONNECTING;\n\n // Connection timeout\n let settled = false;\n const timeout = setTimeout(() => {\n if (!settled) {\n settled = true;\n this._readyState = TransportState.CLOSED;\n if (this.ws) {\n try {\n this.ws.close();\n } catch {\n // Ignore\n }\n }\n reject(new Error(`WebSocket connection timed out after ${this.timeoutMs}ms`));\n }\n }, this.timeoutMs);\n\n try {\n this.ws = new WebSocket(this.url, {\n headers: this.headers,\n });\n\n // Handle binary data as ArrayBuffer (not Buffer)\n this.ws.binaryType = \"arraybuffer\";\n\n this.ws.addEventListener(\"open\", () => {\n if (!settled) {\n settled = true;\n clearTimeout(timeout);\n this._readyState = TransportState.OPEN;\n resolve();\n }\n });\n\n this.ws.addEventListener(\"message\", (event) => {\n this.handleIncomingMessage(event.data);\n });\n\n this.ws.addEventListener(\"close\", (event) => {\n this._readyState = TransportState.CLOSED;\n\n if (!settled) {\n settled = true;\n clearTimeout(timeout);\n reject(new Error(`WebSocket closed before open: code=${event.code} reason=${event.reason}`));\n }\n\n if (this._onClose) {\n try {\n this._onClose(event.code, event.reason || \"\");\n } catch {\n // Don't let handler errors propagate\n }\n }\n });\n\n this.ws.addEventListener(\"error\", (event) => {\n const error = event instanceof Error ? event : new Error(\"WebSocket error\");\n\n if (!settled) {\n settled = true;\n clearTimeout(timeout);\n this._readyState = TransportState.CLOSED;\n reject(error);\n }\n\n if (this._onError) {\n try {\n this._onError(error);\n } catch {\n // Don't let handler errors propagate\n }\n }\n });\n } catch (err) {\n settled = true;\n clearTimeout(timeout);\n this._readyState = TransportState.CLOSED;\n reject(err instanceof Error ? err : new Error(String(err)));\n }\n });\n }\n\n /**\n * Whether the transport is currently connected and ready to send.\n */\n get isOpen(): boolean {\n return this.readyState === TransportState.OPEN;\n }\n\n // ─── Internal ───────────────────────────────────────────────────────────\n\n private handleIncomingMessage(data: unknown): void {\n // Binary message (ArrayBuffer from binaryType: \"arraybuffer\")\n if (data instanceof ArrayBuffer) {\n if (this._onBinary) {\n try {\n this._onBinary(data);\n } catch {\n // Don't let handler errors kill the transport\n }\n }\n return;\n }\n\n // Node Buffer (ws may deliver Buffer even with binaryType: \"arraybuffer\" in some paths)\n if (Buffer.isBuffer(data)) {\n if (this._onBinary) {\n const ab = new Uint8Array(data).slice().buffer;\n try {\n this._onBinary(ab);\n } catch {\n // Swallow handler errors\n }\n }\n return;\n }\n\n // Text message\n if (typeof data === \"string\") {\n if (this._onMessage) {\n try {\n this._onMessage(data);\n } catch {\n // Swallow handler errors\n }\n }\n return;\n }\n\n // Blob (unlikely in Node/Bun, but handle defensively)\n if (typeof Blob !== \"undefined\" && data instanceof Blob) {\n // Convert Blob to text — async but necessary\n data.text().then((text) => {\n if (this._onMessage) {\n try {\n this._onMessage(text);\n } catch {\n // Swallow handler errors\n }\n }\n });\n return;\n }\n\n // Unknown type — try to stringify\n if (this._onMessage) {\n try {\n this._onMessage(String(data));\n } catch {\n // Swallow handler errors\n }\n }\n }\n\n /**\n * Remove all event listeners from the current WebSocket instance.\n * Called before replacing the socket on reconnect.\n */\n private detachListeners(): void {\n if (this.ws) {\n // Remove all listeners to prevent memory leaks.\n // The `ws` library supports removeAllListeners().\n try {\n this.ws.removeAllListeners();\n } catch {\n // Some environments may not support removeAllListeners\n }\n }\n }\n}\n",
68
+ "import { EventEmitter } from \"events\";\nimport type { AudioOutputStream } from \"../managers/SpeakerManager\";\n\nexport class _V2AudioStreamShim extends EventEmitter {\n readonly streamId: string;\n\n constructor(private readonly stream: AudioOutputStream) {\n super();\n this.streamId = stream.id;\n\n this.stream.onStateChange((state) => {\n if (state === \"ended\") {\n this.emit(\"close\");\n }\n\n if (state === \"error\") {\n this.emit(\"error\", new Error(\"Audio output stream entered error state\"));\n }\n });\n }\n\n get state(): string {\n return this.stream.state;\n }\n\n write(chunk: Uint8Array): void {\n this.stream.write(chunk);\n }\n\n async end(): Promise<void> {\n await this.stream.end();\n }\n\n async flush(): Promise<void> {\n this.stream.flush();\n }\n}\n",
69
+ "import type { PhotoData as LegacyPhotoData } from \"../../types/photo-data\";\nimport { AppToCloudMessageType } from \"../../types\";\nimport type { MentraSession } from \"../MentraSession\";\nimport type {\n ExistingStreamInfo,\n ManagedStreamOptions,\n ManagedStreamResult,\n PhotoOptions,\n RtmpStreamOptions,\n} from \"../managers/CameraManager\";\n\nexport interface _V2PhotoRequestOptions {\n saveToGallery?: boolean;\n customWebhookUrl?: string;\n authToken?: string;\n size?: \"small\" | \"medium\" | \"large\" | \"full\";\n compress?: \"none\" | \"medium\" | \"heavy\";\n sound?: boolean;\n}\n\nexport interface _V2PhotoRequestBridge {\n registerPhotoRequest(\n requestId: string,\n request: {\n userId: string;\n sessionId: string;\n session: unknown;\n resolve: (photo: LegacyPhotoData) => void;\n reject: (error: Error) => void;\n timestamp: number;\n },\n ): void;\n completePhotoRequest(requestId: string):\n | {\n resolve: (photo: LegacyPhotoData) => void;\n reject: (error: Error) => void;\n }\n | undefined;\n}\n\ninterface _V2CameraShimConfig {\n photoRequestBridge?: _V2PhotoRequestBridge;\n getV2Session?: () => unknown;\n}\n\nfunction generateRequestId(): string {\n if (typeof crypto !== \"undefined\" && typeof crypto.randomUUID === \"function\") {\n return `photo_req_${crypto.randomUUID()}`;\n }\n\n return `photo_req_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;\n}\n\nexport class _V2CameraShim {\n private readonly session: MentraSession;\n private readonly config: _V2CameraShimConfig;\n\n constructor(session: MentraSession, config: _V2CameraShimConfig = {}) {\n this.session = session;\n this.config = config;\n }\n\n get hasPermission(): boolean {\n return this.session.camera.hasPermission;\n }\n\n takePhoto(options?: PhotoOptions) {\n return this.session.camera.takePhoto(options);\n }\n\n onPhotoTaken(handler: (photo: any) => void): () => void {\n return this.session.camera.onPhotoTaken(handler);\n }\n\n async requestPhoto(options?: _V2PhotoRequestOptions): Promise<LegacyPhotoData> {\n const bridge = this.config.photoRequestBridge;\n const userId = this.session.userId;\n\n if (!bridge || !userId) {\n throw new Error(\"requestPhoto() requires the MiniAppServer photo upload bridge\");\n }\n\n return new Promise<LegacyPhotoData>((resolve, reject) => {\n const requestId = generateRequestId();\n\n bridge.registerPhotoRequest(requestId, {\n userId,\n sessionId: this.session.sessionId,\n session: this.config.getV2Session?.() ?? this.session,\n resolve,\n reject,\n timestamp: Date.now(),\n });\n\n this.session.sendMessage({\n type: AppToCloudMessageType.PHOTO_REQUEST,\n packageName: this.session.packageName,\n sessionId: this.session.sessionId,\n requestId,\n timestamp: new Date(),\n saveToGallery: options?.saveToGallery ?? false,\n customWebhookUrl: options?.customWebhookUrl,\n authToken: options?.authToken,\n size: options?.size ?? \"medium\",\n compress: options?.compress ?? \"none\",\n sound: options?.sound,\n });\n\n if (options?.customWebhookUrl) {\n const pending = bridge.completePhotoRequest(requestId);\n pending?.resolve({\n buffer: Buffer.from([]),\n mimeType: \"image/jpeg\",\n filename: \"photo.jpg\",\n requestId,\n size: 0,\n timestamp: new Date(),\n });\n }\n });\n }\n\n startStream(options: RtmpStreamOptions): Promise<void> {\n return this.session.camera.startDirectStream(options);\n }\n\n stopStream(): Promise<void> {\n return this.session.camera.stopStream();\n }\n\n onStreamStatus(handler: (status: any) => void): () => void {\n return this.session.camera.onStreamStatus(handler);\n }\n\n isCurrentlyStreaming(): boolean {\n return this.session.camera.isCurrentlyStreaming();\n }\n\n getCurrentStreamUrl(): string | undefined {\n return this.session.camera.getCurrentStreamUrl();\n }\n\n getStreamStatus() {\n return this.session.camera.getStreamStatus();\n }\n\n startManagedStream(options?: ManagedStreamOptions): Promise<ManagedStreamResult> {\n return this.session.camera.startManagedStream(options);\n }\n\n stopManagedStream(): Promise<void> {\n return this.session.camera.stopManagedStream();\n }\n\n onManagedStreamStatus(handler: (status: any) => void): () => void {\n return this.session.camera.onManagedStreamStatus(handler);\n }\n\n isManagedStreamActive(): boolean {\n return this.session.camera.isManagedStreamActive();\n }\n\n getManagedStreamUrls(): ManagedStreamResult | undefined {\n return this.session.camera.getManagedStreamUrls();\n }\n\n checkExistingStream(): Promise<ExistingStreamInfo> {\n return this.session.camera.checkExistingStream();\n }\n}\n",
70
+ "import { MentraSession } from \"../MentraSession\";\n\ntype LegacyEventName =\n | \"transcription\"\n | \"head_position\"\n | \"button_press\"\n | \"touch_event\"\n | \"phone_notification\"\n | \"phone_notification_dismissed\"\n | \"vps_coordinates\"\n | \"photo_taken\";\n\n/**\n * V2 Event Manager Shim\n *\n * Maps every `session.events.*` method from the v2 API to the\n * corresponding v3 manager method. This is the compatibility layer\n * for code that does `session.events.onTranscription(handler)` etc.\n *\n * Note on `ontranslationForLanguage` (lowercase 'on'):\n * The v2 EventManager had this as a lowercase method name — it was a bug\n * in the original codebase that shipped and became part of the public API.\n * We preserve the lowercase version for backward compat AND add a\n * correctly-cased `onTranslationForLanguage` alias so both work.\n *\n * Removed in v3.1.\n *\n * @internal\n */\nexport class _V2EventManagerShim {\n private readonly session: MentraSession;\n\n constructor(session: MentraSession) {\n this.session = session;\n }\n\n onTranscription(handler: (data: any) => void): () => void {\n return this.session.transcription.on(handler);\n }\n\n onTranscriptionForLanguage(language: string, handler: (data: any) => void): () => void {\n return this.session.transcription.forLanguage(language, handler);\n }\n\n /**\n * Original v2 method name (lowercase 'on' — was a bug, shipped as public API).\n * Preserved for backward compat with code that calls `events.ontranslationForLanguage()`.\n */\n ontranslationForLanguage(source: string, target: string, handler: (data: any) => void): () => void {\n return this.session.translation.fromTo(source, target, handler);\n }\n\n /**\n * Correctly-cased alias. Both `onTranslationForLanguage` and `ontranslationForLanguage` work.\n */\n onTranslationForLanguage(source: string, target: string, handler: (data: any) => void): () => void {\n return this.session.translation.fromTo(source, target, handler);\n }\n\n onHeadPosition(handler: (data: any) => void): () => void {\n return this.session.device.onHeadPosition(handler);\n }\n\n onButtonPress(handler: (data: any) => void): () => void {\n return this.session.device.onButtonPress(handler);\n }\n\n onTouchEvent(gestureOrHandler: string | ((data: any) => void), handler?: (data: any) => void): () => void {\n if (typeof gestureOrHandler === \"string\") {\n return this.session.device.onTouchEvent(gestureOrHandler, handler!);\n }\n\n return this.session.device.onTouchEvent(gestureOrHandler);\n }\n\n onPhoneNotifications(handler: (data: any) => void): () => void {\n return this.session.phone.notifications.on(handler);\n }\n\n onPhoneNotificationDismissed(handler: (data: any) => void): () => void {\n return this.session.phone.notifications.onDismissed(handler);\n }\n\n onVpsCoordinates(handler: (data: any) => void): () => void {\n return this.session.device.onVpsCoordinates(handler);\n }\n\n onPhotoTaken(handler: (data: any) => void): () => void {\n return this.session.camera.onPhotoTaken(handler);\n }\n\n onAudioChunk(handler: (data: any) => void): () => void {\n return this.session.mic.onChunk((chunk) => {\n handler({\n type: \"audio_chunk\",\n arrayBuffer: chunk.data,\n sampleRate: chunk.sampleRate,\n timestamp: new Date(chunk.timestamp),\n });\n });\n }\n\n onLocation(handler: (data: any) => void): () => void {\n return this.session.location.onUpdate(handler);\n }\n\n onCalendarEvent(handler: (data: any) => void): () => void {\n return this.session.phone.calendar.on(handler);\n }\n\n onConnected(handler: (data: any) => void): () => void {\n return this.session.onConnected(handler);\n }\n\n onDisconnected(handler: (data: any) => void): () => void {\n return this.session.onDisconnected(handler);\n }\n\n onError(handler: (data: any) => void): () => void {\n return this.session.onError(handler);\n }\n\n onSettingsUpdate(handler: (data: any) => void): () => void {\n return this.session.onSettings(handler);\n }\n\n onCapabilitiesUpdate(handler: (data: any) => void): () => void {\n return this.session.device.onCapabilitiesChange((capabilities) => {\n handler({\n capabilities,\n modelName: capabilities?.modelName ?? null,\n });\n });\n }\n\n on(event: LegacyEventName, handler: (data: any) => void): () => void {\n switch (event) {\n case \"transcription\":\n return this.onTranscription(handler);\n case \"head_position\":\n return this.onHeadPosition(handler);\n case \"button_press\":\n return this.onButtonPress(handler);\n case \"touch_event\":\n return this.onTouchEvent(handler);\n case \"phone_notification\":\n return this.onPhoneNotifications(handler);\n case \"phone_notification_dismissed\":\n return this.onPhoneNotificationDismissed(handler);\n case \"vps_coordinates\":\n return this.onVpsCoordinates(handler);\n case \"photo_taken\":\n return this.onPhotoTaken(handler);\n default:\n return () => {};\n }\n }\n}\n",
71
+ "import type { AppSettings } from \"../../types\";\nimport { MentraSession } from \"../MentraSession\";\n\ntype SettingsChangeMap = Record<\n string,\n {\n oldValue: any;\n newValue: any;\n }\n>;\n\nexport class _V2SettingsShim {\n private readonly session: MentraSession;\n\n constructor(session: MentraSession) {\n this.session = session;\n }\n\n has(key: string): boolean {\n return this.session.settingsData.some((setting) => setting.key === key);\n }\n\n getAll(): AppSettings {\n return [...this.session.settingsData];\n }\n\n get<T = any>(key: string, defaultValue?: T): T {\n const setting = this.session.settingsData.find((candidate) => candidate.key === key);\n if (setting && setting.value !== undefined) {\n return setting.value as T;\n }\n\n return defaultValue as T;\n }\n\n onChange(handler: (changes: SettingsChangeMap) => void): () => void {\n let previous = this.getAll();\n\n return this.session.onSettings((settings) => {\n const changes: SettingsChangeMap = {};\n\n for (const nextSetting of settings) {\n const oldSetting = previous.find((candidate) => candidate.key === nextSetting.key);\n if (oldSetting?.value !== nextSetting.value) {\n changes[nextSetting.key] = {\n oldValue: oldSetting?.value,\n newValue: nextSetting.value,\n };\n }\n }\n\n for (const oldSetting of previous) {\n if (!settings.some((candidate) => candidate.key === oldSetting.key)) {\n changes[oldSetting.key] = {\n oldValue: oldSetting.value,\n newValue: undefined,\n };\n }\n }\n\n previous = [...settings];\n\n if (Object.keys(changes).length > 0) {\n handler(changes);\n }\n });\n }\n\n onValueChange<T = any>(key: string, handler: (newValue: T, oldValue: T) => void): () => void {\n let previous = this.get<T>(key);\n\n return this.session.onSettings(() => {\n const next = this.get<T>(key);\n if (next !== previous) {\n const oldValue = previous;\n previous = next;\n handler(next, oldValue as T);\n }\n });\n }\n}\n",
72
+ "import type { AppSettings, AppConfig, AppSetting, Capabilities, ExtendedStreamType } from \"../../types\";\nimport { MentraSession } from \"../MentraSession\";\nimport { _V2AudioStreamShim } from \"./_V2AudioStreamShim\";\nimport { _V2CameraShim, type _V2PhotoRequestBridge } from \"./_V2CameraShim\";\nimport { _V2EventManagerShim } from \"./_V2EventManagerShim\";\nimport { _V2SettingsShim } from \"./_V2SettingsShim\";\n\n/**\n * V2 Session Shim\n *\n * Wraps a v3 MentraSession in the v2 AppSession-shaped API so that\n * existing mini apps continue to work without code changes.\n *\n * This is the primary backward-compatibility surface. It exposes:\n * - session.layouts.* → delegates to session.display\n * - session.audio.* → delegates to session.speaker\n * - session.simpleStorage → delegates to session.storage\n * - session.camera.* → delegates via _V2CameraShim\n * - session.events.* → delegates via _V2EventManagerShim\n * - session.settings.* → delegates via _V2SettingsShim\n * - All deprecated top-level on*() convenience methods\n * - All v2 utility methods (getSettings, getWifiStatus, subscribe, etc.)\n *\n * Removed in v3.1.\n *\n * @internal\n */\nexport class _V2SessionShim {\n readonly session: MentraSession;\n\n // ─── V2 Module Surfaces ─────────────────────────────────────────────────\n\n readonly layouts: {\n showText: MentraSession[\"display\"][\"showText\"];\n showTextWall: MentraSession[\"display\"][\"showTextWall\"];\n showDoubleTextWall: MentraSession[\"display\"][\"showDoubleTextWall\"];\n showReferenceCard: MentraSession[\"display\"][\"showReferenceCard\"];\n showDashboardCard: MentraSession[\"display\"][\"showDashboardCard\"];\n showBitmap: MentraSession[\"display\"][\"showBitmap\"];\n clear: MentraSession[\"display\"][\"clear\"];\n updateText: (payload: { text: string }) => void;\n };\n readonly simpleStorage: MentraSession[\"storage\"];\n readonly audio: {\n speak: MentraSession[\"speaker\"][\"speak\"];\n playAudio: (options: {\n url: string;\n volume?: number;\n trackId?: 0 | 1 | 2;\n stopOtherAudio?: boolean;\n }) => Promise<any>;\n stopAudio: (trackId?: 0 | 1 | 2) => Promise<void>;\n createOutputStream: (options?: Record<string, any>) => Promise<_V2AudioStreamShim>;\n };\n readonly camera: _V2CameraShim;\n readonly led: MentraSession[\"led\"];\n readonly location: MentraSession[\"location\"];\n readonly device: MentraSession[\"device\"];\n readonly dashboard: MentraSession[\"dashboard\"];\n readonly settings: _V2SettingsShim;\n readonly events: _V2EventManagerShim;\n\n constructor(\n session: MentraSession,\n options?: {\n photoRequestBridge?: _V2PhotoRequestBridge;\n },\n ) {\n this.session = session;\n this.layouts = {\n showText: session.display.showText.bind(session.display),\n showTextWall: session.display.showTextWall.bind(session.display),\n showDoubleTextWall: session.display.showDoubleTextWall.bind(session.display),\n showReferenceCard: session.display.showReferenceCard.bind(session.display),\n showDashboardCard: session.display.showDashboardCard.bind(session.display),\n showBitmap: session.display.showBitmap.bind(session.display),\n clear: session.display.clear.bind(session.display),\n updateText: ({ text }) => session.display.showTextWall(text),\n };\n this.simpleStorage = session.storage;\n this.audio = {\n speak: session.speaker.speak.bind(session.speaker),\n playAudio: (options) =>\n session.speaker.play({\n url: options.url,\n volume: options.volume,\n trackId: options.trackId,\n stopOtherAudio: options.stopOtherAudio,\n }),\n stopAudio: (trackId) => session.speaker.stop(trackId),\n createOutputStream: async (options?: Record<string, any>) =>\n new _V2AudioStreamShim(await session.speaker.createStream(options)),\n };\n this.camera = new _V2CameraShim(session, {\n photoRequestBridge: options?.photoRequestBridge,\n getV2Session: () => this,\n });\n this.led = session.led;\n this.location = session.location;\n this.device = session.device;\n this.dashboard = session.dashboard;\n this.settings = new _V2SettingsShim(session);\n this.events = new _V2EventManagerShim(session);\n }\n\n // ─── Identity ───────────────────────────────────────────────────────────\n\n get userId(): string | undefined {\n return this.session.userId;\n }\n\n get packageName(): string {\n return this.session.packageName;\n }\n\n getSessionId(): string {\n return this.session.sessionId;\n }\n\n getPackageName(): string {\n return this.session.packageName;\n }\n\n // ─── Capabilities ───────────────────────────────────────────────────────\n\n get capabilities(): Capabilities | null {\n return this.session.capabilities;\n }\n\n // ─── Settings Utility Methods ───────────────────────────────────────────\n\n /** @deprecated Use session.settings or session.settingsData */\n getSettings(): AppSettings {\n return this.session.settingsData;\n }\n\n /** @deprecated Use session.settings.get(key) */\n getSetting<T>(key: string): T | undefined {\n return this.settings.get<T>(key);\n }\n\n /** @deprecated Use session.appConfig */\n getConfig(): AppConfig | null {\n return this.session.appConfig;\n }\n\n /** @deprecated Use session.getServerUrl() */\n getServerUrl(): string | null {\n return this.session.getServerUrl();\n }\n\n /** @deprecated Convert WS URL to HTTPS */\n getHttpsServerUrl(): string | null {\n const serverUrl = this.session.getServerUrl();\n if (!serverUrl) return null;\n // Remove ws:// or wss://\n let url = serverUrl.replace(/^wss?:\\/\\//, \"\");\n // Remove trailing /app-ws\n url = url.replace(/\\/app-ws$/, \"\");\n return `https://${url}`;\n }\n\n // ─── WiFi ───────────────────────────────────────────────────────────────\n\n /** @deprecated Use session.device.state.wifiConnected */\n getWifiStatus(): { connected: boolean; ssid?: string | null } | null {\n return {\n connected: this.session.device.state.wifiConnected.value,\n ssid: this.session.device.state.wifiSsid.value,\n };\n }\n\n /** @deprecated Use session.device.state.wifiConnected.value */\n isWifiConnected(): boolean {\n return this.session.device.state.wifiConnected.value === true;\n }\n\n /** @deprecated Use session.device.requestWifiSetup() */\n requestWifiSetup(reason?: string): void {\n this.session.device.requestWifiSetup(reason);\n }\n\n // ─── Subscription Methods ───────────────────────────────────────────────\n\n /**\n * @deprecated Subscriptions are now derived from handler registrations.\n * Prefer using manager methods (e.g., session.transcription.on()) which\n * handle subscriptions automatically.\n */\n subscribe(sub: string | { stream: string; rate?: string }): void {\n const stream = typeof sub === \"string\" ? sub : sub.stream;\n // Delegate to the internal subscription manager via MentraSession.\n // This is a manual override — the developer is explicitly asking to subscribe.\n (this.session as any)._subscriptions?.add(stream);\n }\n\n /**\n * @deprecated Subscriptions are now derived from handler registrations.\n */\n unsubscribe(sub: string | { stream: string }): void {\n const stream = typeof sub === \"string\" ? sub : sub.stream;\n (this.session as any)._subscriptions?.remove(stream);\n }\n\n // ─── Generic Event Listener ─────────────────────────────────────────────\n\n /** @deprecated Use the specific manager methods instead. */\n on(event: string, handler: (data: any) => void): () => void {\n return this.events.on(event as any, handler);\n }\n\n // ─── Gesture Subscription ───────────────────────────────────────────────\n\n /** @deprecated Use session.device.subscribeToGestures() */\n subscribeToGestures(gestures: string[]): () => void {\n return this.session.device.subscribeToGestures(gestures);\n }\n\n // ─── Connection State ───────────────────────────────────────────────────\n\n /** @deprecated Use session.device.state.connected.onChange() */\n onGlassesConnectionState(handler: (state: any) => void): () => void {\n return this.session.device.state.connected.onChange((connected) => {\n handler({\n connected,\n modelName: this.session.device.state.modelName.value,\n wifi: {\n connected: this.session.device.state.wifiConnected.value,\n ssid: this.session.device.state.wifiSsid.value,\n },\n });\n });\n }\n\n // ─── Direct Event Handling (deprecated on* methods) ─────────────────────\n\n /** @deprecated Use session.transcription.on() */\n onTranscription(handler: (data: any) => void): () => void {\n return this.events.onTranscription(handler);\n }\n\n /** @deprecated Use session.transcription.forLanguage() */\n onTranscriptionForLanguage(language: string, handler: (data: any) => void): () => void {\n return this.events.onTranscriptionForLanguage(language, handler);\n }\n\n /** @deprecated Use session.translation.fromTo() */\n onTranslationForLanguage(source: string, target: string, handler: (data: any) => void): () => void {\n return this.session.translation.fromTo(source, target, handler);\n }\n\n /** @deprecated Use session.device.onHeadPosition() */\n onHeadPosition(handler: (data: any) => void): () => void {\n return this.events.onHeadPosition(handler);\n }\n\n /** @deprecated Use session.device.onButtonPress() */\n onButtonPress(handler: (data: any) => void): () => void {\n return this.events.onButtonPress(handler);\n }\n\n /** @deprecated Use session.device.onTouchEvent() */\n onTouchEvent(gestureOrHandler: string | ((data: any) => void), handler?: (data: any) => void): () => void {\n return this.events.onTouchEvent(gestureOrHandler, handler);\n }\n\n /** @deprecated Use session.phone.notifications.on() */\n onPhoneNotifications(handler: (data: any) => void): () => void {\n return this.events.onPhoneNotifications(handler);\n }\n\n /** @deprecated Use session.phone.notifications.onDismissed() */\n onPhoneNotificationDismissed(handler: (data: any) => void): () => void {\n return this.events.onPhoneNotificationDismissed(handler);\n }\n\n /** @deprecated Use session.device.onVpsCoordinates() */\n onVpsCoordinates(handler: (data: any) => void): () => void {\n return this.events.onVpsCoordinates(handler);\n }\n\n /** @deprecated Use session.camera.onPhotoTaken() */\n onPhotoTaken(handler: (data: any) => void): () => void {\n return this.events.onPhotoTaken(handler);\n }\n\n // ─── Low-Level Message Sending ──────────────────────────────────────────\n\n // ─── Settings-Based Subscriptions ───────────────────────────────────────\n\n /**\n * @deprecated Use v3 manager-based subscriptions instead.\n *\n * Configure automatic subscription updates when settings change.\n * The handler function receives the current settings and returns\n * the desired subscription list. When any of the `updateOnChange`\n * keys change, the handler is re-evaluated and subscriptions updated.\n */\n private _subscriptionSettingsHandler?: (settings: AppSettings) => ExtendedStreamType[];\n private _subscriptionUpdateTriggers: string[] = [];\n\n setSubscriptionSettings(options: {\n updateOnChange: string[];\n handler: (settings: AppSettings) => ExtendedStreamType[];\n }): void {\n this._subscriptionUpdateTriggers = options.updateOnChange;\n this._subscriptionSettingsHandler = options.handler;\n\n // If we already have settings, evaluate immediately\n if (this.session.settingsData.length > 0) {\n this._updateSubscriptionsFromSettings();\n }\n\n // Listen for settings changes to re-evaluate\n this.session.onSettings((settings) => {\n const shouldUpdate = this._subscriptionUpdateTriggers.some((key) => {\n return settings.some((s: any) => s.key === key);\n });\n if (shouldUpdate) {\n this._updateSubscriptionsFromSettings();\n }\n });\n }\n\n private _updateSubscriptionsFromSettings(): void {\n if (!this._subscriptionSettingsHandler) return;\n try {\n this._subscriptionSettingsHandler(this.session.settingsData);\n // Note: with Bug 007 fix, subscriptions are derived from handlers.\n // The handler should register event handlers that correspond to the\n // desired subscriptions. This call just triggers the re-evaluation.\n } catch (error) {\n this.session.logger.error(error, \"Error updating subscriptions from settings\");\n }\n }\n\n // ─── Config Loading ─────────────────────────────────────────────────────\n\n /**\n * @deprecated Use appConfig property directly.\n * Load app configuration from a JSON string.\n */\n loadConfigFromJson(jsonData: string): AppConfig {\n try {\n const parsed = JSON.parse(jsonData);\n // Basic validation\n if (!parsed || typeof parsed !== \"object\") {\n throw new Error(\"Invalid App configuration format\");\n }\n this.session.appConfig = parsed as AppConfig;\n return parsed as AppConfig;\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n throw new Error(`Failed to load App configuration: ${msg}`);\n }\n }\n\n /**\n * @deprecated Read from appConfig.settings directly.\n * Get default settings from the loaded app configuration.\n */\n getDefaultSettings(): AppSettings {\n if (!this.session.appConfig) {\n throw new Error(\"App configuration not loaded. Call loadConfigFromJson first.\");\n }\n return (this.session.appConfig.settings || [])\n .filter((s: any) => s.type !== \"group\" && \"key\" in s)\n .map((s: any) => ({ ...s, value: s.defaultValue }));\n }\n\n /**\n * @deprecated Read from appConfig.settings directly.\n * Get the schema for a specific setting key.\n */\n getSettingSchema(key: string): AppSetting | undefined {\n if (!this.session.appConfig) return undefined;\n return (this.session.appConfig.settings || []).find(\n (s: any) => s.type !== \"group\" && \"key\" in s && s.key === key,\n ) as AppSetting | undefined;\n }\n\n // ─── Messaging ──────────────────────────────────────────────────────────\n\n /** Send an arbitrary JSON message over the WebSocket. */\n sendMessage(message: unknown): void {\n this.session.sendMessage(message);\n }\n\n /** Send binary data over the WebSocket (e.g., audio stream frames). */\n sendBinary(data: ArrayBuffer | Uint8Array): void {\n this.session.sendBinary(data);\n }\n\n // ─── Lifecycle ──────────────────────────────────────────────────────────\n\n async releaseOwnership(reason: \"switching_clouds\" | \"clean_shutdown\" | \"user_logout\"): Promise<void> {\n await this.session.releaseOwnership(reason);\n }\n\n async disconnect(_options?: {\n releaseOwnership?: boolean;\n reason?: \"switching_clouds\" | \"clean_shutdown\" | \"user_logout\";\n }): Promise<void> {\n if (_options?.releaseOwnership && _options.reason) {\n await this.releaseOwnership(_options.reason);\n }\n\n await this.session.disconnect();\n }\n\n updateSettingsForTesting(newSettings: AppSettings): void {\n this.session.updateSettingsForTesting(newSettings);\n }\n}\n",
73
+ "/**\n * _SessionManager\n *\n * Consolidated server-level session orchestrator. Merges the responsibilities\n * of the previous four separate classes into one:\n *\n * _MentraSessionServerFactory → createSession()\n * _MiniAppSessionRegistry → session tracking (bySessionId, byUserId maps)\n * _MiniAppServerCallbackBridge → callback storage (onSession, onStop, onToolCall)\n * _MiniAppServerRuntime → webhook handling (handleSessionRequest, handleStopRequest)\n *\n * This is an internal class — never exported to developers.\n * MiniAppServer is the only consumer.\n *\n * See decisions.md D-007 for rationale on the consolidation.\n *\n * @internal\n */\n\nimport type { Logger } from \"pino\";\nimport type { WebhookResponse, SessionWebhookRequest, StopWebhookRequest, ToolCall } from \"../types\";\nimport type { MentraSessionConfig } from \"../session\";\nimport { MentraSession } from \"../session\";\nimport { WebSocketTransport } from \"../transport/WebSocketTransport\";\nimport { _V2SessionShim } from \"../session/internal/_V2SessionShim\";\nimport type { _V2PhotoRequestBridge } from \"../session/internal/_V2CameraShim\";\n\n// ─── Types ──────────────────────────────────────────────────────────────────\n\nexport interface _SessionManagerConfig {\n packageName: string;\n apiKey: string;\n logger: Logger;\n serverUrl?: string;\n logLevel?: MentraSessionConfig[\"logLevel\"];\n verbose?: MentraSessionConfig[\"verbose\"];\n photoRequestBridge?: _V2PhotoRequestBridge;\n}\n\nexport type _SessionHandler = (session: _V2SessionShim) => void | Promise<void>;\nexport type _StopHandler = (session: _V2SessionShim | null, reason: string) => void | Promise<void>;\nexport type _ToolCallHandler = (toolCall: ToolCall) => string | undefined | Promise<string | undefined>;\n\ninterface SessionRecord {\n session: MentraSession;\n compatSession: _V2SessionShim;\n userId: string;\n sessionId: string;\n}\n\n// ─── _SessionManager ────────────────────────────────────────────────────────\n\nexport class _SessionManager {\n private readonly config: _SessionManagerConfig;\n private readonly logger: Logger;\n\n // ─── Registry (was _MiniAppSessionRegistry) ───────────────────────────\n private readonly bySessionId = new Map<string, SessionRecord>();\n private readonly byUserId = new Map<string, SessionRecord>();\n\n // ─── Callbacks (was _MiniAppServerCallbackBridge) ─────────────────────\n private sessionHandler?: _SessionHandler;\n private stopHandler?: _StopHandler;\n private toolCallHandler?: _ToolCallHandler;\n private readonly callbackSessionCache = new Map<string, _V2SessionShim>();\n\n // ─── Lifecycle ────────────────────────────────────────────────────────\n private readonly stopSuppression = new Set<string>();\n\n constructor(config: _SessionManagerConfig) {\n this.config = config;\n this.logger = config.logger;\n }\n\n // ─── Callback Registration ────────────────────────────────────────────\n\n onSession(handler: _SessionHandler): void {\n this.sessionHandler = handler;\n }\n\n onStop(handler: _StopHandler): void {\n this.stopHandler = handler;\n }\n\n onToolCall(handler: _ToolCallHandler): void {\n this.toolCallHandler = handler;\n }\n\n // ─── Registry Queries ─────────────────────────────────────────────────\n\n getBySessionId(sessionId: string): SessionRecord | null {\n return this.bySessionId.get(sessionId) ?? null;\n }\n\n getByUserId(userId: string): SessionRecord | null {\n return this.byUserId.get(userId) ?? null;\n }\n\n get size(): number {\n return this.bySessionId.size;\n }\n\n // ─── Webhook Handlers ─────────────────────────────────────────────────\n\n async handleSessionRequest(request: SessionWebhookRequest): Promise<WebhookResponse> {\n // Tear down any existing session for this sessionId\n const existing = this.bySessionId.get(request.sessionId);\n if (existing) {\n this.stopSuppression.add(request.sessionId);\n try {\n await existing.compatSession.releaseOwnership(\"switching_clouds\");\n } catch (error) {\n this.logger.warn({ error, sessionId: request.sessionId }, \"Failed to release ownership on existing session\");\n }\n\n await existing.session.disconnect();\n this.deleteIfSameSession(request.sessionId, existing.session);\n this.stopSuppression.delete(request.sessionId);\n }\n\n // Create new session (was _MentraSessionServerFactory.create)\n const created = this.createSession(request);\n\n // Register in maps\n this.registrySet({\n session: created.session,\n compatSession: created.compatSession,\n userId: request.userId,\n sessionId: request.sessionId,\n });\n\n // Wire up permanent-disconnect → stop handler\n created.compatSession.events.onDisconnected(async (info) => {\n if (!info?.permanent) return;\n\n const removed = this.deleteIfSameSession(request.sessionId, created.session);\n if (!removed) return;\n if (this.stopSuppression.has(request.sessionId)) return;\n\n try {\n await this.invokeStopHandler(request.sessionId, info.reason || \"Session disconnected\");\n } catch (error) {\n this.logger.error(error, \"Stop handler failed after permanent disconnect\");\n }\n });\n\n // Wire up error logging\n created.compatSession.events.onError((error) => {\n this.logger.error(error, \"Session runtime error\");\n });\n\n // Connect the WebSocket\n try {\n await created.session.connect();\n } catch (error) {\n this.deleteIfSameSession(request.sessionId, created.session);\n throw error;\n }\n\n // Invoke the developer's onSession callback\n await this.invokeSessionHandler(created.compatSession, request.sessionId);\n\n return { status: \"success\" };\n }\n\n async handleStopRequest(request: StopWebhookRequest): Promise<WebhookResponse> {\n this.stopSuppression.add(request.sessionId);\n const existing = this.registryDeleteBySessionId(request.sessionId);\n if (existing) {\n await existing.session.disconnect();\n }\n\n try {\n await this.invokeStopHandler(request.sessionId, request.reason);\n } finally {\n this.stopSuppression.delete(request.sessionId);\n }\n return { status: \"success\" };\n }\n\n async handleToolCall(toolCall: ToolCall): Promise<{ status: \"success\"; reply: string | null }> {\n const activeSession = this.byUserId.get(toolCall.userId)?.compatSession ?? null;\n const response = await this.invokeToolCallHandler({\n ...toolCall,\n activeSession: activeSession as any,\n });\n\n return {\n status: \"success\",\n reply: response ?? null,\n };\n }\n\n async shutdown(): Promise<void> {\n for (const record of Array.from(this.bySessionId.values())) {\n this.stopSuppression.add(record.sessionId);\n await record.session.disconnect();\n }\n this.bySessionId.clear();\n this.byUserId.clear();\n this.callbackSessionCache.clear();\n this.stopSuppression.clear();\n }\n\n // ─── Session Factory (was _MentraSessionServerFactory) ────────────────\n\n private createSession(request: SessionWebhookRequest): {\n session: MentraSession;\n compatSession: _V2SessionShim;\n } {\n const websocketUrl = request.websocketUrl || request.mentraOSWebsocketUrl || request.augmentOSWebsocketUrl;\n if (!websocketUrl) {\n throw new Error(\"Session webhook is missing websocketUrl/mentraOSWebsocketUrl/augmentOSWebsocketUrl\");\n }\n\n const transport = new WebSocketTransport({\n url: websocketUrl,\n headers: {\n \"x-user-id\": request.userId,\n \"x-session-id\": request.sessionId,\n \"x-package-name\": this.config.packageName,\n \"x-api-key\": this.config.apiKey,\n },\n });\n\n const session = new MentraSession({\n packageName: this.config.packageName,\n apiKey: this.config.apiKey,\n sessionId: request.sessionId,\n userId: request.userId,\n serverUrl: this.config.serverUrl,\n transport,\n logLevel: this.config.logLevel,\n verbose: this.config.verbose,\n });\n\n const compatSession = new _V2SessionShim(session, {\n photoRequestBridge: this.config.photoRequestBridge,\n });\n\n return { session, compatSession };\n }\n\n // ─── Registry Operations (was _MiniAppSessionRegistry) ────────────────\n\n private registrySet(record: SessionRecord): void {\n this.bySessionId.set(record.sessionId, record);\n this.byUserId.set(record.userId, record);\n }\n\n private registryDeleteBySessionId(sessionId: string): SessionRecord | null {\n const record = this.bySessionId.get(sessionId) ?? null;\n if (!record) return null;\n\n this.bySessionId.delete(sessionId);\n if (this.byUserId.get(record.userId)?.sessionId === sessionId) {\n this.byUserId.delete(record.userId);\n }\n\n return record;\n }\n\n private deleteIfSameSession(sessionId: string, session: MentraSession): SessionRecord | null {\n const record = this.bySessionId.get(sessionId) ?? null;\n if (!record || record.session !== session) return null;\n return this.registryDeleteBySessionId(sessionId);\n }\n\n // ─── Callback Invocation (was _MiniAppServerCallbackBridge) ───────────\n\n private async invokeSessionHandler(session: _V2SessionShim, sessionId: string): Promise<void> {\n this.callbackSessionCache.set(sessionId, session);\n\n if (this.sessionHandler) {\n await this.sessionHandler(session);\n }\n }\n\n private async invokeStopHandler(sessionId: string, reason: string): Promise<void> {\n const session = this.callbackSessionCache.get(sessionId) ?? null;\n this.callbackSessionCache.delete(sessionId);\n\n if (this.stopHandler) {\n await this.stopHandler(session, reason);\n }\n }\n\n private async invokeToolCallHandler(toolCall: ToolCall): Promise<string | undefined> {\n if (this.toolCallHandler) {\n return this.toolCallHandler(toolCall);\n }\n return undefined;\n }\n}\n",
74
+ "import type { Context } from \"hono\";\nimport type { ToolCall } from \"./types\";\nimport type { AuthVariables, SessionWebhookRequest, StopWebhookRequest, WebhookResponse } from \"./types\";\nimport { AppServer, type AppServerConfig } from \"./app/server\";\nimport { AppSession } from \"./app/session\";\nimport { MentraSession } from \"./session\";\nimport { _SessionManager, type _ToolCallHandler } from \"./internal/_SessionManager\";\n\n/**\n * Internal bridge session type used by the v2 override path.\n * When a developer subclasses MiniAppServer and overrides onSession(session, sessionId, userId),\n * the call goes through the old AppServer path which needs an AppSession instance.\n */\nclass V2BridgeSession extends AppSession {}\n\nexport type MiniAppServerConfig = AppServerConfig;\n\nexport type SessionHandler = (session: MentraSession) => void | Promise<void>;\nexport type StopHandler = (session: MentraSession | null, reason: string) => void | Promise<void>;\nexport type ToolCallHandler = _ToolCallHandler;\n\n/**\n * v3 cloud/server host for Mentra mini apps.\n *\n * `MiniAppServer` is the cloud-only entry point. It handles HTTP endpoints\n * (webhooks, tools, settings, health, photo-upload) and creates MentraSession\n * instances for each connected user.\n *\n * Naming:\n * - `MiniAppServer` is cloud/server-specific (not needed for local apps).\n * - `MentraSession` is the per-user session abstraction (same everywhere).\n *\n * This class extends the v2 `AppServer` during the transition period.\n * When a v3-style callback is registered via `app.onSession((session) => {...})`,\n * webhook lifecycle flows through `_SessionManager` → `MentraSession` → v3 runtime.\n * When a v2-style subclass overrides `onSession(session, sessionId, userId)`,\n * it goes through the old `AppServer` path entirely.\n *\n * @example\n * ```ts\n * const app = new MiniAppServer({ packageName: \"com.example.myapp\", apiKey: \"...\" })\n *\n * app.onSession((session) => {\n * session.transcription.on((data) => {\n * session.display.showText(data.text)\n * })\n * })\n *\n * await app.start()\n * ```\n */\nexport class MiniAppServer extends AppServer {\n private readonly _sessions: _SessionManager;\n\n constructor(config: MiniAppServerConfig) {\n super(config);\n this._sessions = new _SessionManager({\n packageName: config.packageName,\n apiKey: config.apiKey,\n logger: this.logger,\n serverUrl: config.cloudApiUrl,\n photoRequestBridge: {\n registerPhotoRequest: this.registerPhotoRequest.bind(this),\n completePhotoRequest: this.completePhotoRequest.bind(this),\n },\n });\n }\n\n // ─── Callback Registration (v3 pattern) ─────────────────────────────────\n\n public onSession(handler: SessionHandler): this;\n public override onSession(session: AppSession, sessionId: string, userId: string): Promise<void>;\n public override onSession(\n arg1: SessionHandler | AppSession,\n sessionId?: string,\n userId?: string,\n ): this | Promise<void> {\n if (typeof arg1 === \"function\") {\n // v3 path: developer passes a callback, we route through _SessionManager\n this._sessions.onSession((compatSession) => arg1(compatSession.session));\n return this;\n }\n\n // v2 path: developer subclassed and overrode onSession(session, sessionId, userId)\n const mentraSession = arg1 as V2BridgeSession;\n return super.onSession(mentraSession, sessionId!, userId!);\n }\n\n public onStop(handler: StopHandler): this;\n public override onStop(sessionId: string, userId: string, reason: string): Promise<void>;\n public override onStop(arg1: StopHandler | string, userId?: string, reason?: string): this | Promise<void> {\n if (typeof arg1 === \"function\") {\n this._sessions.onStop((session, stopReason) => arg1(session?.session ?? null, stopReason));\n return this;\n }\n\n return super.onStop(arg1, userId!, reason!);\n }\n\n public onToolCall(handler: ToolCallHandler): this;\n public override onToolCall(toolCall: ToolCall): Promise<string | undefined>;\n public override onToolCall(arg1: ToolCallHandler | ToolCall): this | Promise<string | undefined> {\n if (typeof arg1 === \"function\") {\n this._sessions.onToolCall(arg1);\n return this;\n }\n\n return super.onToolCall(arg1);\n }\n\n // ─── Webhook Handlers (override AppServer) ──────────────────────────────\n\n protected override async handleSessionWebhookRequest(\n request: SessionWebhookRequest,\n c: Context<{ Variables: AuthVariables }>,\n ): Promise<Response> {\n try {\n const response = await this._sessions.handleSessionRequest(request);\n const record = this._sessions.getBySessionId(request.sessionId);\n if (record) {\n // Register in AppServer's active session tracking (for photo upload correlation, etc.)\n this.setActiveSession(request.sessionId, request.userId, record.compatSession as unknown as AppSession);\n\n record.compatSession.events.onDisconnected((info) => {\n if (!info?.permanent) return;\n\n if (this.getActiveSessionById(request.sessionId) === (record.compatSession as unknown as AppSession)) {\n this.removeActiveSession(request.sessionId, request.userId);\n }\n\n this.cleanupPhotoRequestsForSession(request.sessionId);\n });\n }\n\n return c.json(response as WebhookResponse);\n } catch (error) {\n this.logger.error(error, \"Failed to connect MiniAppServer runtime session\");\n return c.json(\n {\n status: \"error\",\n message: \"Failed to connect\",\n } as WebhookResponse,\n 500,\n );\n }\n }\n\n protected override async handleStopWebhookRequest(\n request: StopWebhookRequest,\n c: Context<{ Variables: AuthVariables }>,\n ): Promise<Response> {\n try {\n const response = await this._sessions.handleStopRequest(request);\n this.removeActiveSession(request.sessionId, request.userId);\n return c.json(response as WebhookResponse);\n } catch (error) {\n this.logger.error(error, \"Failed to stop MiniAppServer runtime session\");\n return c.json(\n {\n status: \"error\",\n message: \"Failed to process stop request\",\n } as WebhookResponse,\n 500,\n );\n }\n }\n}\n"
43
75
  ],
44
- "mappings": "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",
45
- "debugId": "062D061B6931421164756E2164756E21",
76
+ "mappings": "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",
77
+ "debugId": "11051307E83217F164756E2164756E21",
46
78
  "names": []
47
79
  }