@mentra/sdk 2.1.31-beta.6 → 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 +152 -88
  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 +108 -50
  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 +8824 -4229
  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
@@ -0,0 +1,44 @@
1
+ {
2
+ "version": 3,
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/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"],
4
+ "sourcesContent": [
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",
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
+ "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",
19
+ "/**\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",
20
+ "/**\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",
21
+ "/**\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",
22
+ "/**\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",
23
+ "/**\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",
24
+ "/**\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",
25
+ "/**\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",
26
+ "/**\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",
27
+ "/**\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",
28
+ "/**\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",
29
+ "/**\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",
30
+ "/**\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",
31
+ "/**\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",
32
+ "/**\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",
33
+ "/**\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",
34
+ "/**\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",
35
+ "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",
36
+ "/**\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",
37
+ "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",
38
+ "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",
39
+ "/**\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"
40
+ ],
41
+ "mappings": "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",
42
+ "debugId": "8AE847F1727E65C164756E2164756E21",
43
+ "names": []
44
+ }