@mentra/sdk 2.1.27 → 2.1.29-beta.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.
- package/dist/app/session/api-client.d.ts.map +1 -1
- package/dist/app/session/dashboard.d.ts +5 -8
- package/dist/app/session/dashboard.d.ts.map +1 -1
- package/dist/app/session/events.d.ts +10 -4
- package/dist/app/session/events.d.ts.map +1 -1
- package/dist/app/session/index.d.ts +64 -4
- package/dist/app/session/index.d.ts.map +1 -1
- package/dist/app/session/modules/audio.d.ts +33 -4
- package/dist/app/session/modules/audio.d.ts.map +1 -1
- package/dist/app/session/modules/camera-managed-extension.d.ts +2 -3
- package/dist/app/session/modules/camera-managed-extension.d.ts.map +1 -1
- package/dist/app/session/modules/camera.d.ts +11 -10
- package/dist/app/session/modules/camera.d.ts.map +1 -1
- package/dist/app/session/modules/led.d.ts +141 -0
- package/dist/app/session/modules/led.d.ts.map +1 -0
- package/dist/app/session/modules/location.d.ts +1 -2
- package/dist/app/session/modules/location.d.ts.map +1 -1
- package/dist/app/session/modules/simple-storage.d.ts +22 -1
- package/dist/app/session/modules/simple-storage.d.ts.map +1 -1
- package/dist/display-utils.d.ts +989 -0
- package/dist/display-utils.d.ts.map +1 -0
- package/dist/display-utils.js +1197 -0
- package/dist/display-utils.js.map +17 -0
- package/dist/index.d.ts +7 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5427 -112
- package/dist/index.js.map +45 -0
- package/dist/logging/logger.d.ts +1 -1
- package/dist/logging/logger.d.ts.map +1 -1
- package/dist/types/capabilities.d.ts +3 -0
- package/dist/types/capabilities.d.ts.map +1 -1
- package/dist/types/index.d.ts +4 -14
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/message-types.d.ts +8 -1
- package/dist/types/message-types.d.ts.map +1 -1
- package/dist/types/messages/app-to-cloud.d.ts +49 -3
- package/dist/types/messages/app-to-cloud.d.ts.map +1 -1
- package/dist/types/messages/cloud-to-app.d.ts +18 -6
- package/dist/types/messages/cloud-to-app.d.ts.map +1 -1
- package/dist/types/messages/cloud-to-glasses.d.ts +30 -2
- package/dist/types/messages/cloud-to-glasses.d.ts.map +1 -1
- package/dist/types/messages/glasses-to-cloud.d.ts +24 -1
- package/dist/types/messages/glasses-to-cloud.d.ts.map +1 -1
- package/dist/types/rtmp-stream.d.ts +1 -1
- package/dist/types/rtmp-stream.d.ts.map +1 -1
- package/dist/types/streams.d.ts +31 -2
- package/dist/types/streams.d.ts.map +1 -1
- package/package.json +34 -11
- package/dist/app/index.js +0 -24
- package/dist/app/server/index.js +0 -658
- package/dist/app/session/api-client.js +0 -101
- package/dist/app/session/dashboard.js +0 -149
- package/dist/app/session/events.js +0 -315
- package/dist/app/session/index.js +0 -1573
- package/dist/app/session/layouts.js +0 -372
- package/dist/app/session/modules/audio.js +0 -321
- package/dist/app/session/modules/camera-managed-extension.js +0 -310
- package/dist/app/session/modules/camera.js +0 -607
- package/dist/app/session/modules/index.js +0 -19
- package/dist/app/session/modules/location.js +0 -61
- package/dist/app/session/modules/simple-storage.js +0 -232
- package/dist/app/session/settings.js +0 -358
- package/dist/app/token/index.js +0 -22
- package/dist/app/token/utils.js +0 -144
- package/dist/app/webview/index.js +0 -382
- package/dist/constants/index.js +0 -16
- package/dist/constants/log-messages/color.js +0 -14
- package/dist/constants/log-messages/logos.js +0 -48
- package/dist/constants/log-messages/updates.js +0 -55
- package/dist/constants/log-messages/warning.js +0 -89
- package/dist/examples/managed-rtmp-streaming-example.js +0 -158
- package/dist/examples/managed-rtmp-streaming-with-restream-example.js +0 -124
- package/dist/examples/rtmp-streaming-example.js +0 -102
- package/dist/logging/logger.js +0 -79
- package/dist/types/capabilities.js +0 -9
- package/dist/types/dashboard/index.js +0 -12
- package/dist/types/enums.js +0 -75
- package/dist/types/index.js +0 -101
- package/dist/types/layouts.js +0 -3
- package/dist/types/message-types.js +0 -212
- package/dist/types/messages/app-to-cloud.js +0 -95
- package/dist/types/messages/base.js +0 -3
- package/dist/types/messages/cloud-to-app.js +0 -78
- package/dist/types/messages/cloud-to-glasses.js +0 -68
- package/dist/types/messages/glasses-to-cloud.js +0 -140
- package/dist/types/models.js +0 -101
- package/dist/types/photo-data.js +0 -2
- package/dist/types/rtmp-stream.js +0 -3
- package/dist/types/streams.js +0 -306
- package/dist/types/token.js +0 -7
- package/dist/types/webhooks.js +0 -28
- package/dist/utils/animation-utils.js +0 -340
- package/dist/utils/bitmap-utils.js +0 -475
- package/dist/utils/permissions-utils.js +0 -263
- package/dist/utils/resource-tracker.js +0 -153
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/types/streams.ts", "../src/types/message-types.ts", "../src/types/dashboard/index.ts", "../src/app/session/dashboard.ts", "../src/app/token/utils.ts", "../src/index.ts", "../src/types/messages/glasses-to-cloud.ts", "../src/types/messages/cloud-to-glasses.ts", "../src/types/messages/app-to-cloud.ts", "../src/utils/bitmap-utils.ts", "../src/utils/animation-utils.ts", "../src/types/messages/cloud-to-app.ts", "../src/types/enums.ts", "../src/types/models.ts", "../src/types/webhooks.ts", "../src/app/server/index.ts", "../src/app/session/index.ts", "../src/app/session/events.ts", "../src/types/index.ts", "../src/constants/log-messages/warning.ts", "../src/constants/log-messages/logos.ts", "../src/utils/permissions-utils.ts", "../src/app/session/layouts.ts", "../src/app/session/settings.ts", "../src/app/session/api-client.ts", "../src/logging/logger.ts", "../src/app/session/modules/location.ts", "../src/app/session/modules/camera.ts", "../src/app/session/modules/camera-managed-extension.ts", "../src/app/session/modules/led.ts", "../src/app/session/modules/audio.ts", "../src/utils/resource-tracker.ts", "../src/app/session/modules/simple-storage.ts", "../src/app/webview/index.ts", "../src/constants/log-messages/updates.ts", "../src/app/token/index.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"// src/streams.ts\n\n/**\n * Types of streams that Apps can subscribe to\n *\n * These are events and data that Apps can receive from the cloud.\n * Not all message types can be subscribed to as streams.\n */\nexport enum StreamType {\n // Hardware streams\n BUTTON_PRESS = \"button_press\",\n HEAD_POSITION = \"head_position\",\n TOUCH_EVENT = \"touch_event\",\n GLASSES_BATTERY_UPDATE = \"glasses_battery_update\",\n PHONE_BATTERY_UPDATE = \"phone_battery_update\",\n GLASSES_CONNECTION_STATE = \"glasses_connection_state\",\n LOCATION_UPDATE = \"location_update\",\n LOCATION_STREAM = \"location_stream\",\n VPS_COORDINATES = \"vps_coordinates\",\n\n // Audio streams\n TRANSCRIPTION = \"transcription\",\n TRANSLATION = \"translation\",\n VAD = \"VAD\",\n AUDIO_CHUNK = \"audio_chunk\",\n\n // Phone streams\n PHONE_NOTIFICATION = \"phone_notification\",\n PHONE_NOTIFICATION_DISMISSED = \"phone_notification_dismissed\",\n CALENDAR_EVENT = \"calendar_event\",\n\n // System streams\n START_APP = \"start_app\",\n STOP_APP = \"stop_app\",\n OPEN_DASHBOARD = \"open_dashboard\",\n CORE_STATUS_UPDATE = \"core_status_update\",\n\n // Video streams\n VIDEO = \"video\",\n PHOTO_REQUEST = \"photo_request\",\n PHOTO_RESPONSE = \"photo_response\",\n RTMP_STREAM_STATUS = \"rtmp_stream_status\",\n MANAGED_STREAM_STATUS = \"managed_stream_status\",\n\n // Special subscription types\n ALL = \"all\",\n WILDCARD = \"*\",\n\n // New stream type\n MENTRAOS_SETTINGS_UPDATE_REQUEST = \"settings_update_request\",\n CUSTOM_MESSAGE = \"custom_message\",\n PHOTO_TAKEN = \"photo_taken\",\n}\n\n/**\n * Extended StreamType to support language-specific streams\n * This allows us to treat language-specific strings as StreamType values\n */\nexport type ExtendedStreamType = StreamType | string\n\n/**\n * Categories of stream data\n */\nexport enum StreamCategory {\n /** Data from hardware sensors */\n HARDWARE = \"hardware\",\n\n /** Audio processing results */\n AUDIO = \"audio\",\n\n /** Phone-related events */\n PHONE = \"phone\",\n\n /** System-level events */\n SYSTEM = \"system\",\n}\n\n/**\n * Map of stream categories for each stream type\n */\nexport const STREAM_CATEGORIES: Record<StreamType, StreamCategory> = {\n [StreamType.BUTTON_PRESS]: StreamCategory.HARDWARE,\n [StreamType.HEAD_POSITION]: StreamCategory.HARDWARE,\n [StreamType.TOUCH_EVENT]: StreamCategory.HARDWARE,\n [StreamType.GLASSES_BATTERY_UPDATE]: StreamCategory.HARDWARE,\n [StreamType.PHONE_BATTERY_UPDATE]: StreamCategory.HARDWARE,\n [StreamType.GLASSES_CONNECTION_STATE]: StreamCategory.HARDWARE,\n [StreamType.LOCATION_UPDATE]: StreamCategory.HARDWARE,\n [StreamType.LOCATION_STREAM]: StreamCategory.HARDWARE,\n [StreamType.VPS_COORDINATES]: StreamCategory.HARDWARE,\n\n [StreamType.TRANSCRIPTION]: StreamCategory.AUDIO,\n [StreamType.TRANSLATION]: StreamCategory.AUDIO,\n [StreamType.VAD]: StreamCategory.AUDIO,\n [StreamType.AUDIO_CHUNK]: StreamCategory.AUDIO,\n\n [StreamType.PHONE_NOTIFICATION]: StreamCategory.PHONE,\n [StreamType.PHONE_NOTIFICATION_DISMISSED]: StreamCategory.PHONE,\n [StreamType.CALENDAR_EVENT]: StreamCategory.PHONE,\n [StreamType.START_APP]: StreamCategory.SYSTEM,\n [StreamType.STOP_APP]: StreamCategory.SYSTEM,\n [StreamType.OPEN_DASHBOARD]: StreamCategory.SYSTEM,\n [StreamType.CORE_STATUS_UPDATE]: StreamCategory.SYSTEM,\n\n [StreamType.VIDEO]: StreamCategory.HARDWARE,\n [StreamType.PHOTO_REQUEST]: StreamCategory.HARDWARE,\n [StreamType.PHOTO_RESPONSE]: StreamCategory.HARDWARE,\n [StreamType.RTMP_STREAM_STATUS]: StreamCategory.HARDWARE,\n [StreamType.MANAGED_STREAM_STATUS]: StreamCategory.HARDWARE,\n [StreamType.ALL]: StreamCategory.SYSTEM,\n [StreamType.WILDCARD]: StreamCategory.SYSTEM,\n\n [StreamType.MENTRAOS_SETTINGS_UPDATE_REQUEST]: StreamCategory.SYSTEM,\n [StreamType.CUSTOM_MESSAGE]: StreamCategory.SYSTEM,\n [StreamType.PHOTO_TAKEN]: StreamCategory.HARDWARE,\n}\n\n/**\n * Branded type for TypeScript to recognize language-specific stream types\n * This helps maintain type safety when using language-specific streams\n */\nexport type LanguageStreamType<T extends string> = T & {\n __languageStreamBrand: never\n}\n\n/**\n * Create a language-branded stream type\n * This is a type helper to ensure type safety for language-specific streams\n */\nfunction createLanguageStream<T extends string>(type: T): LanguageStreamType<T> {\n return type as LanguageStreamType<T>\n}\n\n/**\n * Structure of a parsed language stream subscription\n */\nexport interface LanguageStreamInfo {\n type: StreamType // Base stream type (e.g., TRANSCRIPTION)\n baseType: string // String representation of base type (e.g., \"transcription\")\n transcribeLanguage: string // Source language code (e.g., \"en-US\")\n translateLanguage?: string // Target language code for translations (e.g., \"es-ES\")\n options?: Record<string, string | boolean> // Query parameters/options\n original: ExtendedStreamType // Original subscription string\n}\n\n/**\n * Check if a string is a valid language code\n * Simple validation for language code format: xx-XX (e.g., en-US)\n */\nexport function isValidLanguageCode(code: string): boolean {\n return /^[a-z]{2,3}-[A-Z]{2}$/.test(code)\n}\n\n/**\n * Parse a subscription string to extract language information\n *\n * @param subscription Subscription string (e.g., \"transcription:en-US\" or \"translation:es-ES-to-en-US\" or \"transcription:en-US?no-language-identification=true\")\n * @returns Parsed language stream info or null if not a language-specific subscription\n */\nexport function parseLanguageStream(subscription: ExtendedStreamType): LanguageStreamInfo | null {\n // console.log(`🎤 Parsing language stream: ${subscription}`);\n\n if (typeof subscription !== \"string\") {\n return null\n }\n\n // Handle transcription format (transcription:en-US or transcription:en-US?options)\n if (subscription.startsWith(`${StreamType.TRANSCRIPTION}:`)) {\n const [baseType, rest] = subscription.split(\":\")\n const [languageCode, queryString] = rest?.split(\"?\") ?? []\n\n if (languageCode && isValidLanguageCode(languageCode)) {\n const options: Record<string, string | boolean> = {}\n\n // Parse query parameters if present\n if (queryString) {\n const params = new URLSearchParams(queryString)\n for (const [key, value] of params.entries()) {\n // Convert string values to boolean when appropriate\n if (value === \"true\") {\n options[key] = true\n } else if (value === \"false\") {\n options[key] = false\n } else {\n options[key] = value\n }\n }\n }\n\n return {\n type: StreamType.TRANSCRIPTION,\n baseType,\n transcribeLanguage: languageCode,\n options: Object.keys(options).length > 0 ? options : undefined,\n original: subscription,\n }\n }\n }\n\n // Handle translation format (translation:es-ES-to-en-US, translation:all-to-en-US, or with ?options)\n if (subscription.startsWith(`${StreamType.TRANSLATION}:`)) {\n const [baseType, rest] = subscription.split(\":\")\n const [languagePair, queryString] = rest?.split(\"?\") ?? []\n const [sourceLanguage, targetLanguage] = languagePair?.split(\"-to-\") ?? []\n\n // Check for \"all-to-LANG\" format (one-way translation from any language)\n const isAllToFormat = sourceLanguage === \"all\" && targetLanguage && isValidLanguageCode(targetLanguage)\n\n // Check for \"LANG-to-LANG\" format (two-way translation between specific languages)\n const isSpecificPairFormat =\n sourceLanguage && targetLanguage && isValidLanguageCode(sourceLanguage) && isValidLanguageCode(targetLanguage)\n\n if (isAllToFormat || isSpecificPairFormat) {\n const options: Record<string, string | boolean> = {}\n\n // Parse query parameters if present\n if (queryString) {\n const params = new URLSearchParams(queryString)\n for (const [key, value] of params.entries()) {\n // Convert string values to boolean when appropriate\n if (value === \"true\") {\n options[key] = true\n } else if (value === \"false\") {\n options[key] = false\n } else {\n options[key] = value\n }\n }\n }\n\n return {\n type: StreamType.TRANSLATION,\n baseType,\n transcribeLanguage: sourceLanguage, // \"all\" for all-to-LANG, or specific language code\n translateLanguage: targetLanguage,\n options: Object.keys(options).length > 0 ? options : undefined,\n original: subscription,\n }\n }\n }\n\n return null\n}\n\n/**\n * Create a transcription stream identifier for a specific language\n * Returns a type-safe stream type that can be used like a StreamType\n *\n * @param language Language code (e.g., \"en-US\") or \"auto\" for automatic detection\n * @param options Optional configuration options\n * @returns Typed stream identifier\n */\nexport function createTranscriptionStream(\n language: string,\n options?: {\n disableLanguageIdentification?: boolean\n hints?: string[]\n },\n): ExtendedStreamType {\n console.log(`🎤 Creating transcription stream for language: ${language}`)\n console.log(`🎤 Options: ${JSON.stringify(options)}`)\n\n // Defensively remove any query string from the language parameter\n const languageCode = language.split(\"?\")[0]\n\n if (languageCode !== \"auto\" && !isValidLanguageCode(languageCode)) {\n throw new Error(`Invalid language code: ${languageCode}`)\n }\n\n const base = `${StreamType.TRANSCRIPTION}:${languageCode}`\n const params = new URLSearchParams()\n\n if (options?.disableLanguageIdentification) {\n params.set(\"no-language-identification\", \"true\")\n }\n\n if (options?.hints && options.hints.length > 0) {\n params.set(\"hints\", options.hints.join(\",\"))\n }\n\n const queryString = params.toString()\n return queryString ? (`${base}?${queryString}` as ExtendedStreamType) : (base as ExtendedStreamType)\n}\n\n/**\n * Create a translation stream identifier for a language pair\n * Returns a type-safe stream type that can be used like a StreamType\n *\n * @param sourceLanguage Source language code (e.g., \"es-ES\") or \"all\" for one-way translation\n * @param targetLanguage Target language code (e.g., \"en-US\")\n * @param options Optional configuration options\n * @returns Typed stream identifier\n */\nexport function createTranslationStream(\n sourceLanguage: string,\n targetLanguage: string,\n options?: {disableLanguageIdentification?: boolean},\n): ExtendedStreamType {\n // Defensively remove any query string from the language parameters\n const cleanSourceLanguage = sourceLanguage.split(\"?\")[0]\n const cleanTargetLanguage = targetLanguage.split(\"?\")[0]\n\n // Support \"all\" as source for one-way translation (all languages → target)\n const isAllToFormat = cleanSourceLanguage === \"all\"\n\n if ((!isAllToFormat && !isValidLanguageCode(cleanSourceLanguage)) || !isValidLanguageCode(cleanTargetLanguage)) {\n throw new Error(`Invalid language code(s): ${cleanSourceLanguage}, ${cleanTargetLanguage}`)\n }\n const base = `${StreamType.TRANSLATION}:${cleanSourceLanguage}-to-${cleanTargetLanguage}`\n if (options?.disableLanguageIdentification) {\n return `${base}?no-language-identification=true` as ExtendedStreamType\n }\n return createLanguageStream(base)\n}\n\n/**\n * Parse a touch event subscription to extract gesture information\n * @param subscription Subscription string (e.g., \"touch_event:forward_swipe\")\n * @returns Gesture name or null if not a gesture-specific subscription\n */\nexport function parseTouchEventStream(subscription: ExtendedStreamType): string | null {\n if (typeof subscription !== \"string\") {\n return null\n }\n\n if (subscription.startsWith(`${StreamType.TOUCH_EVENT}:`)) {\n const [, gestureName] = subscription.split(\":\")\n const validGestures = [\n \"single_tap\",\n \"double_tap\",\n \"triple_tap\",\n \"long_press\",\n \"forward_swipe\",\n \"backward_swipe\",\n \"up_swipe\",\n \"down_swipe\",\n ]\n\n if (gestureName && validGestures.includes(gestureName)) {\n return gestureName\n }\n }\n\n return null\n}\n\n/**\n * Create a touch event subscription for a specific gesture\n * @param gesture Gesture name (e.g., \"forward_swipe\")\n * @returns Typed stream identifier\n */\nexport function createTouchEventStream(gesture: string): ExtendedStreamType {\n const validGestures = [\n \"single_tap\",\n \"double_tap\",\n \"triple_tap\",\n \"long_press\",\n \"forward_swipe\",\n \"backward_swipe\",\n \"up_swipe\",\n \"down_swipe\",\n ]\n\n if (!validGestures.includes(gesture)) {\n throw new Error(`Invalid gesture: ${gesture}`)\n }\n\n return `${StreamType.TOUCH_EVENT}:${gesture}` as ExtendedStreamType\n}\n\n/**\n * Create a universal translation stream identifier that translates from any language to target\n * This is useful when you want to support all languages translating to a single target\n * Returns a type-safe stream type that can be used like a StreamType\n *\n * Example: createUniversalTranslationStream('es-ES') creates \"translation:all-to-es-ES\"\n *\n * @param targetLanguage Target language code (e.g., \"es-ES\")\n * @param options Optional configuration options\n * @returns Typed stream identifier\n */\nexport function createUniversalTranslationStream(\n targetLanguage: string,\n options?: {disableLanguageIdentification?: boolean},\n): ExtendedStreamType {\n const cleanTargetLanguage = targetLanguage.split(\"?\")[0]\n\n if (!isValidLanguageCode(cleanTargetLanguage)) {\n throw new Error(`Invalid target language code: ${cleanTargetLanguage}`)\n }\n\n const base = `${StreamType.TRANSLATION}:all-to-${cleanTargetLanguage}`\n if (options?.disableLanguageIdentification) {\n return `${base}?no-language-identification=true` as ExtendedStreamType\n }\n return createLanguageStream(base)\n}\n\n/**\n * Check if a subscription is a valid stream type\n * This handles both enum-based StreamType values and language-specific stream formats\n *\n * @param subscription Subscription to validate\n * @returns True if valid, false otherwise\n */\nexport function isValidStreamType(subscription: ExtendedStreamType): boolean {\n // Check if it's a standard StreamType\n if (Object.values(StreamType).includes(subscription as StreamType)) {\n return true\n }\n\n // Check if it's a valid language-specific stream\n const languageStream = parseLanguageStream(subscription)\n if (languageStream !== null) {\n return true\n }\n\n // Check if it's a valid gesture-specific stream\n const gestureStream = parseTouchEventStream(subscription)\n if (gestureStream !== null) {\n return true\n }\n\n return false\n}\n\n/**\n * Helper function to check if a stream type is of a particular category\n * Works with both standard and language-specific stream types\n */\nexport function isStreamCategory(streamType: ExtendedStreamType, category: StreamCategory): boolean {\n const baseType = getBaseStreamType(streamType)\n return baseType ? STREAM_CATEGORIES[baseType] === category : false\n}\n\n/**\n * Helper function to get all stream types in a category\n */\nexport function getStreamTypesByCategory(category: StreamCategory): StreamType[] {\n return Object.entries(STREAM_CATEGORIES)\n .filter(([_, cat]) => cat === category)\n .map(([type]) => type as StreamType)\n}\n\n/**\n * Get the base StreamType for a subscription\n * Works with both standard StreamType values and language-specific formats\n *\n * @param subscription Subscription string or StreamType\n * @returns The base StreamType enum value\n */\nexport function getBaseStreamType(subscription: ExtendedStreamType): StreamType | null {\n // Check if it's already a standard StreamType\n if (Object.values(StreamType).includes(subscription as StreamType)) {\n return subscription as StreamType\n }\n\n // Check if it's a language-specific stream\n const languageStream = parseLanguageStream(subscription)\n if (languageStream) {\n return languageStream.type\n }\n\n // Check if it's a gesture-specific stream\n const gestureStream = parseTouchEventStream(subscription)\n if (gestureStream) {\n return StreamType.TOUCH_EVENT\n }\n\n return null\n}\n\n/**\n * Check if a stream is a language-specific stream\n */\nexport function isLanguageStream(subscription: ExtendedStreamType): boolean {\n return parseLanguageStream(subscription) !== null\n}\n\n/**\n * Get language information from a stream type\n * Returns null for regular stream types\n */\nexport function getLanguageInfo(subscription: ExtendedStreamType): LanguageStreamInfo | null {\n return parseLanguageStream(subscription)\n}\n\n// this is the blueprint for our new rich subscription object\n// it allows a developer to specify a rate for the location stream\nexport interface LocationStreamRequest {\n stream: \"location_stream\"\n rate: \"standard\" | \"high\" | \"realtime\" | \"tenMeters\" | \"hundredMeters\" | \"kilometer\" | \"threeKilometers\" | \"reduced\"\n}\n",
|
|
6
|
+
"// src/message-types.ts\n\nimport {StreamType} from \"./streams\"\n\n/**\n * Types of messages from glasses to cloud\n */\nexport enum GlassesToCloudMessageType {\n // Control actions\n CONNECTION_INIT = \"connection_init\",\n REQUEST_SETTINGS = \"request_settings\",\n\n START_APP = StreamType.START_APP,\n STOP_APP = StreamType.STOP_APP,\n\n DASHBOARD_STATE = \"dashboard_state\",\n OPEN_DASHBOARD = StreamType.OPEN_DASHBOARD,\n\n // Mentra Live\n PHOTO_RESPONSE = StreamType.PHOTO_RESPONSE,\n\n // Local Transcription\n LOCAL_TRANSCRIPTION = \"local_transcription\",\n\n // RTMP streaming\n RTMP_STREAM_STATUS = StreamType.RTMP_STREAM_STATUS,\n KEEP_ALIVE_ACK = \"keep_alive_ack\",\n\n BUTTON_PRESS = StreamType.BUTTON_PRESS,\n HEAD_POSITION = StreamType.HEAD_POSITION,\n TOUCH_EVENT = StreamType.TOUCH_EVENT,\n GLASSES_BATTERY_UPDATE = StreamType.GLASSES_BATTERY_UPDATE,\n PHONE_BATTERY_UPDATE = StreamType.PHONE_BATTERY_UPDATE,\n GLASSES_CONNECTION_STATE = StreamType.GLASSES_CONNECTION_STATE,\n LOCATION_UPDATE = StreamType.LOCATION_UPDATE,\n\n // TODO(isaiah): Remove VPS_COORDINATES once confirmed we don't use this system.\n VPS_COORDINATES = StreamType.VPS_COORDINATES,\n VAD = StreamType.VAD,\n\n // TODO(isaiah): Remove PHONE_NOTIFICATION, and PHONE_NOTIFICATION_DISMISSED after moving to REST request.\n PHONE_NOTIFICATION = StreamType.PHONE_NOTIFICATION,\n PHONE_NOTIFICATION_DISMISSED = StreamType.PHONE_NOTIFICATION_DISMISSED,\n\n // TODO(isaiah): Remove CALENDAR_EVENT after moving to REST request.\n CALENDAR_EVENT = StreamType.CALENDAR_EVENT,\n MENTRAOS_SETTINGS_UPDATE_REQUEST = StreamType.MENTRAOS_SETTINGS_UPDATE_REQUEST,\n\n // TODO(isaiah): Remove CORE_STATUS_UPDATE after moving to REST request.\n CORE_STATUS_UPDATE = StreamType.CORE_STATUS_UPDATE,\n\n PHOTO_TAKEN = StreamType.PHOTO_TAKEN,\n AUDIO_PLAY_RESPONSE = \"audio_play_response\",\n\n // RGB LED control\n RGB_LED_CONTROL_RESPONSE = \"rgb_led_control_response\",\n\n // LiveKit handshake\n LIVEKIT_INIT = \"livekit_init\",\n}\n\n/**\n * Types of messages from cloud to glasses\n */\nexport enum CloudToGlassesMessageType {\n // Responses\n CONNECTION_ACK = \"connection_ack\",\n CONNECTION_ERROR = \"connection_error\",\n AUTH_ERROR = \"auth_error\",\n\n // Updates\n DISPLAY_EVENT = \"display_event\",\n APP_STATE_CHANGE = \"app_state_change\",\n MICROPHONE_STATE_CHANGE = \"microphone_state_change\",\n SETTINGS_UPDATE = \"settings_update\",\n\n // Requests\n PHOTO_REQUEST = \"photo_request\",\n AUDIO_PLAY_REQUEST = \"audio_play_request\",\n AUDIO_STOP_REQUEST = \"audio_stop_request\",\n RGB_LED_CONTROL = \"rgb_led_control\",\n SHOW_WIFI_SETUP = \"show_wifi_setup\",\n\n // RTMP streaming\n START_RTMP_STREAM = \"start_rtmp_stream\",\n STOP_RTMP_STREAM = \"stop_rtmp_stream\",\n KEEP_RTMP_STREAM_ALIVE = \"keep_rtmp_stream_alive\",\n\n // Dashboard updates\n DASHBOARD_MODE_CHANGE = \"dashboard_mode_change\",\n DASHBOARD_ALWAYS_ON_CHANGE = \"dashboard_always_on_change\",\n\n // Location Service\n SET_LOCATION_TIER = \"set_location_tier\",\n REQUEST_SINGLE_LOCATION = \"request_single_location\",\n\n WEBSOCKET_ERROR = \"websocket_error\",\n\n // LiveKit info (URL, room, token)\n LIVEKIT_INFO = \"livekit_info\",\n}\n\n/**\n * Types of messages from Apps to cloud\n */\nexport enum AppToCloudMessageType {\n // Commands\n CONNECTION_INIT = \"tpa_connection_init\",\n SUBSCRIPTION_UPDATE = \"subscription_update\",\n LOCATION_POLL_REQUEST = \"location_poll_request\",\n\n // Requests\n DISPLAY_REQUEST = \"display_event\",\n PHOTO_REQUEST = \"photo_request\",\n AUDIO_PLAY_REQUEST = \"audio_play_request\",\n AUDIO_STOP_REQUEST = \"audio_stop_request\",\n RGB_LED_CONTROL = \"rgb_led_control\",\n REQUEST_WIFI_SETUP = \"request_wifi_setup\",\n\n // RTMP streaming\n RTMP_STREAM_REQUEST = \"rtmp_stream_request\",\n RTMP_STREAM_STOP = \"rtmp_stream_stop\",\n\n // Managed RTMP streaming\n MANAGED_STREAM_REQUEST = \"managed_stream_request\",\n MANAGED_STREAM_STOP = \"managed_stream_stop\",\n\n // Stream status check (both managed and unmanaged)\n STREAM_STATUS_CHECK = \"stream_status_check\",\n\n // Dashboard requests\n DASHBOARD_CONTENT_UPDATE = \"dashboard_content_update\",\n DASHBOARD_MODE_CHANGE = \"dashboard_mode_change\",\n DASHBOARD_SYSTEM_UPDATE = \"dashboard_system_update\",\n\n // TODO(isaiah): Remove after confirming not in use.\n // App-to-App Communication\n APP_BROADCAST_MESSAGE = \"app_broadcast_message\",\n APP_DIRECT_MESSAGE = \"app_direct_message\",\n APP_USER_DISCOVERY = \"app_user_discovery\",\n APP_ROOM_JOIN = \"app_room_join\",\n APP_ROOM_LEAVE = \"app_room_leave\",\n}\n\n/**\n * Types of messages from cloud to Apps\n */\nexport enum CloudToAppMessageType {\n // Responses\n CONNECTION_ACK = \"tpa_connection_ack\",\n CONNECTION_ERROR = \"tpa_connection_error\",\n\n // Updates\n APP_STOPPED = \"app_stopped\",\n SETTINGS_UPDATE = \"settings_update\",\n CAPABILITIES_UPDATE = \"capabilities_update\",\n\n // Dashboard updates\n DASHBOARD_MODE_CHANGED = \"dashboard_mode_changed\",\n DASHBOARD_ALWAYS_ON_CHANGED = \"dashboard_always_on_changed\",\n\n // Stream data\n DATA_STREAM = \"data_stream\",\n\n // Media responses\n PHOTO_RESPONSE = \"photo_response\",\n AUDIO_PLAY_RESPONSE = \"audio_play_response\",\n RGB_LED_CONTROL_RESPONSE = \"rgb_led_control_response\",\n RTMP_STREAM_STATUS = \"rtmp_stream_status\",\n MANAGED_STREAM_STATUS = \"managed_stream_status\",\n STREAM_STATUS_CHECK_RESPONSE = \"stream_status_check_response\",\n\n WEBSOCKET_ERROR = \"websocket_error\",\n\n // Permissions\n PERMISSION_ERROR = \"permission_error\",\n\n // General purpose messaging\n CUSTOM_MESSAGE = \"custom_message\",\n\n // TODO(isaiah): Remove after confirming not in use.\n // App-to-App Communication Responses\n APP_MESSAGE_RECEIVED = \"app_message_received\",\n APP_USER_JOINED = \"app_user_joined\",\n APP_USER_LEFT = \"app_user_left\",\n APP_ROOM_UPDATED = \"app_room_updated\",\n APP_DIRECT_MESSAGE_RESPONSE = \"app_direct_message_response\",\n}\n\n/**\n * Control action message types (subset of GlassesToCloudMessageType)\n */\nexport const ControlActionTypes = [\n GlassesToCloudMessageType.CONNECTION_INIT,\n GlassesToCloudMessageType.START_APP,\n GlassesToCloudMessageType.STOP_APP,\n GlassesToCloudMessageType.DASHBOARD_STATE,\n GlassesToCloudMessageType.OPEN_DASHBOARD,\n] as const\n\n/**\n * Event message types (subset of GlassesToCloudMessageType)\n */\nexport const EventTypes = [\n GlassesToCloudMessageType.BUTTON_PRESS,\n GlassesToCloudMessageType.HEAD_POSITION,\n GlassesToCloudMessageType.GLASSES_BATTERY_UPDATE,\n GlassesToCloudMessageType.PHONE_BATTERY_UPDATE,\n GlassesToCloudMessageType.GLASSES_CONNECTION_STATE,\n GlassesToCloudMessageType.LOCATION_UPDATE,\n GlassesToCloudMessageType.VPS_COORDINATES,\n GlassesToCloudMessageType.VAD,\n GlassesToCloudMessageType.PHONE_NOTIFICATION,\n GlassesToCloudMessageType.PHONE_NOTIFICATION_DISMISSED,\n GlassesToCloudMessageType.CALENDAR_EVENT,\n GlassesToCloudMessageType.MENTRAOS_SETTINGS_UPDATE_REQUEST,\n GlassesToCloudMessageType.CORE_STATUS_UPDATE,\n GlassesToCloudMessageType.LOCAL_TRANSCRIPTION,\n] as const\n\n/**\n * Response message types (subset of CloudToGlassesMessageType)\n */\nexport const ResponseTypes = [\n CloudToGlassesMessageType.CONNECTION_ACK,\n CloudToGlassesMessageType.CONNECTION_ERROR,\n CloudToGlassesMessageType.AUTH_ERROR,\n] as const\n\n/**\n * Update message types (subset of CloudToGlassesMessageType)\n */\nexport const UpdateTypes = [\n CloudToGlassesMessageType.DISPLAY_EVENT,\n CloudToGlassesMessageType.APP_STATE_CHANGE,\n CloudToGlassesMessageType.MICROPHONE_STATE_CHANGE,\n CloudToGlassesMessageType.PHOTO_REQUEST,\n CloudToGlassesMessageType.AUDIO_PLAY_REQUEST,\n CloudToGlassesMessageType.AUDIO_STOP_REQUEST,\n CloudToGlassesMessageType.RGB_LED_CONTROL,\n CloudToGlassesMessageType.SETTINGS_UPDATE,\n CloudToGlassesMessageType.DASHBOARD_MODE_CHANGE,\n CloudToGlassesMessageType.DASHBOARD_ALWAYS_ON_CHANGE,\n CloudToGlassesMessageType.START_RTMP_STREAM,\n CloudToGlassesMessageType.STOP_RTMP_STREAM,\n CloudToGlassesMessageType.KEEP_RTMP_STREAM_ALIVE,\n CloudToGlassesMessageType.LIVEKIT_INFO,\n] as const\n\n/**\n * Dashboard message types\n */\nexport const DashboardMessageTypes = [\n AppToCloudMessageType.DASHBOARD_CONTENT_UPDATE,\n AppToCloudMessageType.DASHBOARD_MODE_CHANGE,\n AppToCloudMessageType.DASHBOARD_SYSTEM_UPDATE,\n CloudToAppMessageType.DASHBOARD_MODE_CHANGED,\n CloudToAppMessageType.DASHBOARD_ALWAYS_ON_CHANGED,\n] as const\n",
|
|
7
|
+
"// 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;",
|
|
8
|
+
"/**\n * Dashboard API Implementation\n *\n * Provides dashboard functionality for Apps, allowing them to write content\n * to the dashboard and respond to dashboard mode changes.\n */\n// import { systemApps } from '../../constants';\nimport {\n DashboardAPI,\n DashboardContentAPI,\n DashboardMode,\n DashboardSystemAPI,\n DashboardContentUpdate,\n DashboardModeChange,\n DashboardSystemUpdate,\n} from \"../../types/dashboard\"\nimport {AppToCloudMessageType} from \"../../types/message-types\"\nimport {EventManager} from \"./events\"\n\n// Import AppSession interface for typing\nimport type {AppSession} from \"./index\"\nimport dotenv from \"dotenv\"\n// Load environment variables from .env file\ndotenv.config()\n\nconst SYSTEM_DASHBOARD_PACKAGE_NAME = process.env.SYSTEM_DASHBOARD_PACKAGE_NAME || \"system.augmentos.dashboard\"\n\n/**\n * Implementation of DashboardSystemAPI interface for system dashboard App\n */\nexport class DashboardSystemManager implements DashboardSystemAPI {\n private session: AppSession\n private packageName: string\n\n constructor(session: AppSession, packageName: string) {\n this.session = session\n this.packageName = packageName\n }\n\n setTopLeft(content: string): void {\n this.updateSystemSection(\"topLeft\", content)\n }\n\n setTopRight(content: string): void {\n this.updateSystemSection(\"topRight\", content)\n }\n\n setBottomLeft(content: string): void {\n this.updateSystemSection(\"bottomLeft\", content)\n }\n\n setBottomRight(content: string): void {\n this.updateSystemSection(\"bottomRight\", content)\n }\n\n setViewMode(mode: DashboardMode): void {\n const message: DashboardModeChange = {\n type: AppToCloudMessageType.DASHBOARD_MODE_CHANGE,\n packageName: this.packageName,\n sessionId: `${this.session.getSessionId()}-${this.packageName}`,\n mode,\n timestamp: new Date(),\n }\n this.session.sendMessage(message)\n }\n\n private updateSystemSection(section: \"topLeft\" | \"topRight\" | \"bottomLeft\" | \"bottomRight\", content: string): void {\n const message: DashboardSystemUpdate = {\n type: AppToCloudMessageType.DASHBOARD_SYSTEM_UPDATE,\n packageName: this.packageName,\n sessionId: `${this.session.getSessionId()}-${this.packageName}`,\n section,\n content,\n timestamp: new Date(),\n }\n this.session.sendMessage(message)\n }\n}\n\n/**\n * Implementation of DashboardContentAPI interface for all Apps\n */\nexport class DashboardContentManager implements DashboardContentAPI {\n private session: AppSession\n private packageName: string\n private events: EventManager\n private currentMode: DashboardMode | \"none\" = \"none\"\n // private alwaysOnEnabled: boolean = false;\n\n constructor(session: AppSession, packageName: string, events: EventManager) {\n this.session = session\n this.packageName = packageName\n this.events = events\n }\n\n write(content: string, targets: DashboardMode[] = [DashboardMode.MAIN]): void {\n const message: DashboardContentUpdate = {\n type: AppToCloudMessageType.DASHBOARD_CONTENT_UPDATE,\n packageName: this.packageName,\n sessionId: `${this.session.getSessionId()}-${this.packageName}`,\n content,\n modes: targets,\n timestamp: new Date(),\n }\n this.session.sendMessage(message)\n }\n\n writeToMain(content: string): void {\n this.write(content, [DashboardMode.MAIN])\n }\n\n writeToExpanded(content: string): void {\n const message: DashboardContentUpdate = {\n type: AppToCloudMessageType.DASHBOARD_CONTENT_UPDATE,\n packageName: this.packageName,\n sessionId: `${this.session.getSessionId()}-${this.packageName}`,\n content,\n modes: [DashboardMode.EXPANDED],\n timestamp: new Date(),\n }\n this.session.sendMessage(message)\n }\n\n // writeToAlwaysOn(content: string): void {\n // this.write(content, [DashboardMode.ALWAYS_ON]);\n // }\n\n async getCurrentMode(): Promise<DashboardMode | \"none\"> {\n return this.currentMode\n }\n\n // async isAlwaysOnEnabled(): Promise<boolean> {\n // return this.alwaysOnEnabled;\n // }\n\n onModeChange(callback: (mode: DashboardMode | \"none\") => void): () => void {\n return this.events.onDashboardModeChange((data) => {\n this.currentMode = data.mode\n callback(data.mode)\n })\n }\n\n // onAlwaysOnChange(callback: (enabled: boolean) => void): () => void {\n // return this.events.onDashboardAlwaysOnChange((data) => {\n // this.alwaysOnEnabled = data.enabled;\n // callback(data.enabled);\n // });\n // }\n\n // Internal methods to update state\n setCurrentMode(mode: DashboardMode | \"none\"): void {\n this.currentMode = mode\n this.events.emit(\"dashboard_mode_change\", {mode})\n }\n\n // setAlwaysOnEnabled(enabled: boolean): void {\n // this.alwaysOnEnabled = enabled;\n // this.events.emit('dashboard_always_on_change', { enabled });\n // }\n}\n\n/**\n * Dashboard Manager - Main class that manages dashboard functionality\n * Each AppSession instance gets its own DashboardManager instance\n */\nexport class DashboardManager implements DashboardAPI {\n public content: DashboardContentAPI\n public system?: DashboardSystemAPI\n\n constructor(session: AppSession) {\n const packageName = session.getPackageName()\n const events = session.events\n\n // Create content API (available to all Apps)\n this.content = new DashboardContentManager(session, packageName, events)\n\n // Add system API if this is the system dashboard App\n if (packageName === SYSTEM_DASHBOARD_PACKAGE_NAME) {\n session.logger.info({service: \"SDK:DashboardManager\"}, \"Initializing system dashboard manager\")\n this.system = new DashboardSystemManager(session, packageName)\n } else {\n session.logger.info({service: \"SDK:DashboardManager\"}, `Not the system dashboard: ${packageName}`)\n }\n }\n}\n",
|
|
9
|
+
"/**\n * 🔐 App Token Utilities\n *\n * Provides utilities for working with App tokens.\n */\nimport * as jwt from 'jsonwebtoken';\nimport { AppTokenPayload, TokenValidationResult, TokenConfig } from '../../types/token';\n\n/**\n * Default token expiration (1 day)\n */\nconst DEFAULT_EXPIRATION = 60 * 60 * 24; // seconds * minutes * hours (1 day).\n\n/**\n * Create a App token for a user session\n *\n * @param payload - Token payload data\n * @param config - Token configuration\n * @returns JWT token string\n *\n * @example\n * ```typescript\n * const token = createToken(\n * {\n * userId: 'user123',\n * packageName: 'org.example.myapp',\n * sessionId: 'session789'\n * },\n * { secretKey: 'your_secret_key' }\n * );\n * ```\n */\nexport function createToken(\n payload: Omit<AppTokenPayload, 'iat' | 'exp'>,\n config: TokenConfig\n): string {\n return jwt.sign(\n payload,\n config.secretKey,\n { expiresIn: config.expiresIn || DEFAULT_EXPIRATION }\n );\n}\n\n/**\n * Validate and decode a App token\n *\n * @param token - JWT token string\n * @param secretKey - Secret key used for validation\n * @returns Token validation result\n *\n * @example\n * ```typescript\n * const result = validateToken('eyJhbGciOiJIUzI1...', 'your_secret_key');\n * if (result.valid) {\n * // Use result.payload\n * }\n * ```\n */\nexport function validateToken(\n token: string,\n secretKey: string\n): TokenValidationResult {\n try {\n const decoded = jwt.verify(token, secretKey) as AppTokenPayload;\n return {\n valid: true,\n payload: decoded\n };\n } catch (error) {\n return {\n valid: false,\n error: error instanceof Error ? error.message : 'Unknown error'\n };\n }\n}\n\n/**\n * Generate a webview URL with an embedded App token\n *\n * @param baseUrl - Base URL of the webview\n * @param token - JWT token string\n * @returns Full URL with token parameter\n *\n * @example\n * ```typescript\n * const url = generateWebviewUrl(\n * 'https://example.com/webview',\n * 'eyJhbGciOiJIUzI1...'\n * );\n * // Returns: https://example.com/webview?token=eyJhbGciOiJIUzI1...\n * ```\n */\nexport function generateWebviewUrl(baseUrl: string, token: string): string {\n const url = new URL(baseUrl);\n url.searchParams.append('token', token);\n return url.toString();\n}\n\n/**\n * Extract a App token from a URL\n *\n * @param url - URL string containing a token parameter\n * @returns Token string or null if not found\n *\n * @example\n * ```typescript\n * const token = extractTokenFromUrl(\n * 'https://example.com/webview?token=eyJhbGciOiJIUzI1...'\n * );\n * ```\n */\nexport function extractTokenFromUrl(url: string): string | null {\n try {\n const parsedUrl = new URL(url);\n return parsedUrl.searchParams.get('token');\n } catch (error) {\n return null;\n }\n}",
|
|
10
|
+
"// src/index.ts\nexport * from \"./types/token\";\n\n// Message type enums\nexport * from \"./types/message-types\";\n\n// Base message type\nexport * from \"./types/messages/base\";\n\n// Messages by direction - export everything except the conflicting type guards\nexport * from \"./types/messages/glasses-to-cloud\";\nexport * from \"./types/messages/cloud-to-glasses\";\nexport * from \"./types/messages/app-to-cloud\";\n\n// Utility exports\nexport * from \"./utils/bitmap-utils\";\nexport * from \"./utils/animation-utils\";\n\n// Export cloud-to-app but exclude the conflicting type guards\nexport {\n // Types\n AppConnectionAck,\n AppConnectionError,\n AppStopped,\n SettingsUpdate as AppSettingsUpdate, // Alias to avoid conflict with cloud-to-glasses SettingsUpdate\n CapabilitiesUpdate,\n DataStream,\n CloudToAppMessage,\n TranslationData,\n ToolCall,\n StandardConnectionError,\n CustomMessage,\n ManagedStreamStatus,\n StreamStatusCheckResponse,\n OutputStatus,\n MentraosSettingsUpdate,\n TranscriptionData,\n TranscriptionMetadata,\n SonioxToken,\n AudioChunk,\n PermissionError,\n PermissionErrorDetail,\n AudioPlayResponse,\n // Type guards (excluding isPhotoResponse and isRtmpStreamStatus which conflict)\n isAppConnectionAck,\n isAppConnectionError,\n isAppStopped,\n isSettingsUpdate,\n isCapabilitiesUpdate,\n isDataStream,\n isAudioChunk,\n isStreamStatusCheckResponse,\n isDashboardModeChanged,\n isDashboardAlwaysOnChanged,\n isManagedStreamStatus,\n // Re-export the cloud-to-app versions of these type guards since they're the ones\n // that should be used when dealing with CloudToAppMessage types\n isPhotoResponse as isPhotoResponseFromCloud,\n isRgbLedControlResponse as isRgbLedControlResponseFromCloud,\n isRtmpStreamStatus as isRtmpStreamStatusFromCloud,\n} from \"./types/messages/cloud-to-app\";\n\n// Stream types\nexport * from \"./types/streams\";\n\n// Layout types\nexport * from \"./types/layouts\";\n\n// Dashboard types\nexport * from \"./types/dashboard\";\n\n// RTMP streaming types\nexport * from \"./types/rtmp-stream\";\n\n// Other system enums\nexport {\n AppType,\n LayoutType,\n ViewType,\n AppSettingType,\n HardwareType,\n HardwareRequirementLevel,\n} from \"./types/enums\";\n\n// Core model interfaces\nexport * from \"./types/models\";\n\n// Webhook interfaces\nexport * from \"./types/webhooks\";\n\n// Capability Discovery types\nexport * from \"./types/capabilities\";\n\n// App session and server exports\nexport * from \"./app/index\";\n\n// Logging exports\nexport * from \"./logging/logger\";\n\n// Re-export common types for convenience\n// This allows developers to import commonly used types directly from the package root\n// without having to know exactly which file they come from\n\n// From messages/glasses-to-cloud.ts\nexport {\n ButtonPress,\n HeadPosition,\n TouchEvent,\n GlassesBatteryUpdate,\n PhoneBatteryUpdate,\n GlassesConnectionState,\n LocationUpdate,\n CalendarEvent,\n Vad,\n PhoneNotification,\n PhoneNotificationDismissed,\n StartApp,\n StopApp,\n ConnectionInit,\n DashboardState,\n OpenDashboard,\n GlassesToCloudMessage,\n PhotoResponse,\n RgbLedControlResponse,\n PhotoErrorCode,\n PhotoStage,\n ConnectionState,\n PhotoErrorDetails,\n RtmpStreamStatus,\n KeepAliveAck,\n} from \"./types/messages/glasses-to-cloud\";\n\n// From messages/cloud-to-glasses.ts\nexport {\n ConnectionAck,\n ConnectionError,\n AuthError,\n DisplayEvent,\n AppStateChange,\n MicrophoneStateChange,\n CloudToGlassesMessage,\n PhotoRequestToGlasses,\n RgbLedControlToGlasses,\n SettingsUpdate,\n StartRtmpStream,\n StopRtmpStream,\n KeepRtmpStreamAlive,\n LedColor,\n} from \"./types/messages/cloud-to-glasses\";\n\n// From messages/app-to-cloud.ts\nexport {\n AppConnectionInit,\n AppSubscriptionUpdate,\n RtmpStreamRequest,\n RtmpStreamStopRequest,\n AppToCloudMessage,\n PhotoRequest,\n RgbLedControlRequest,\n} from \"./types/messages/app-to-cloud\";\n\n// From layout.ts\nexport {\n TextWall,\n DoubleTextWall,\n DashboardCard,\n ReferenceCard,\n Layout,\n DisplayRequest,\n BitmapView,\n ClearView,\n} from \"./types/layouts\";\n\n// Type guards - re-export the most commonly used ones for convenience\nexport {\n isButtonPress,\n isHeadPosition,\n isConnectionInit,\n isStartApp,\n isStopApp,\n isPhotoResponse as isPhotoResponseFromGlasses,\n isRgbLedControlResponse as isRgbLedControlResponseFromGlasses,\n isRtmpStreamStatus as isRtmpStreamStatusFromGlasses,\n isKeepAliveAck,\n isPhoneNotificationDismissed,\n} from \"./types/messages/glasses-to-cloud\";\n\nexport {\n isConnectionAck,\n isDisplayEvent,\n isAppStateChange,\n isPhotoRequest,\n isSettingsUpdate as isSettingsUpdateToGlasses,\n isStartRtmpStream,\n isStopRtmpStream,\n isKeepRtmpStreamAlive,\n isRgbLedControl,\n} from \"./types/messages/cloud-to-glasses\";\n\nexport {\n isAppConnectionInit,\n isAppSubscriptionUpdate,\n isDisplayRequest,\n isRtmpStreamRequest,\n isRtmpStreamStopRequest,\n isPhotoRequest as isPhotoRequestFromApp,\n isRgbLedControlRequest,\n} from \"./types/messages/app-to-cloud\";\n\n// Export setting-related types\nexport {\n BaseAppSetting,\n AppSetting,\n AppSettings,\n AppConfig,\n validateAppConfig,\n ToolSchema,\n ToolParameterSchema,\n HardwareRequirement,\n} from \"./types/models\";\n\n// Export RTMP streaming types\nexport {\n VideoConfig,\n AudioConfig,\n StreamConfig,\n StreamStatusHandler,\n} from \"./types/rtmp-stream\";\n\n// Export app session modules\nexport * from \"./app/session/modules\";\n\n// Export photo data types\nexport { PhotoData } from \"./types/photo-data\";\n\n/**\n * WebSocket error information\n */\nexport interface WebSocketError {\n code: string;\n message: string;\n details?: unknown;\n}\n\nexport { AuthenticatedRequest } from \"./types/index\";\n",
|
|
11
|
+
"// src/messages/glasses-to-cloud.ts\n\nimport {BaseMessage} from \"./base\"\nimport {GlassesToCloudMessageType, ControlActionTypes, EventTypes} from \"../message-types\"\nimport {StreamType} from \"../streams\"\n\n//===========================================================\n// Control actions\n//===========================================================\n\n/**\n * Connection initialization from glasses\n */\nexport interface ConnectionInit extends BaseMessage {\n type: GlassesToCloudMessageType.CONNECTION_INIT\n userId?: string\n coreToken?: string\n}\n\n/**\n * Client requests LiveKit info (url, room, token)\n */\nexport interface LiveKitInit extends BaseMessage {\n type: GlassesToCloudMessageType.LIVEKIT_INIT\n mode?: \"publish\" | \"subscribe\" // Optional mode - defaults to 'publish' for backward compatibility\n}\n\nexport interface RequestSettings extends BaseMessage {\n type: GlassesToCloudMessageType.REQUEST_SETTINGS\n sessionId: string\n}\n\n/**\n * Start app request from glasses\n */\nexport interface StartApp extends BaseMessage {\n type: GlassesToCloudMessageType.START_APP\n packageName: string\n}\n\n/**\n * Stop app request from glasses\n */\nexport interface StopApp extends BaseMessage {\n type: GlassesToCloudMessageType.STOP_APP\n packageName: string\n}\n\n/**\n * Dashboard state update from glasses\n */\nexport interface DashboardState extends BaseMessage {\n type: GlassesToCloudMessageType.DASHBOARD_STATE\n isOpen: boolean\n}\n\n/**\n * Open dashboard request from glasses\n */\nexport interface OpenDashboard extends BaseMessage {\n type: GlassesToCloudMessageType.OPEN_DASHBOARD\n}\n\n//===========================================================\n// Events and data\n//===========================================================\n\n/**\n * Button press event from glasses\n */\nexport interface ButtonPress extends BaseMessage {\n type: GlassesToCloudMessageType.BUTTON_PRESS\n buttonId: string\n pressType: \"short\" | \"long\"\n}\n\n/**\n * Head position event from glasses\n */\nexport interface HeadPosition extends BaseMessage {\n type: GlassesToCloudMessageType.HEAD_POSITION\n position: \"up\" | \"down\"\n}\n\n/**\n * Touch gesture event from glasses\n */\nexport interface TouchEvent extends BaseMessage {\n type: GlassesToCloudMessageType.TOUCH_EVENT\n device_model: string\n gesture_name: string\n timestamp: Date\n}\n\n/**\n * Glasses battery update from glasses\n */\nexport interface GlassesBatteryUpdate extends BaseMessage {\n type: GlassesToCloudMessageType.GLASSES_BATTERY_UPDATE\n level: number // 0-100\n charging: boolean\n timeRemaining?: number // minutes\n}\n\n/**\n * Phone battery update from glasses\n */\nexport interface PhoneBatteryUpdate extends BaseMessage {\n type: GlassesToCloudMessageType.PHONE_BATTERY_UPDATE\n level: number // 0-100\n charging: boolean\n timeRemaining?: number // minutes\n}\n\n/**\n * Glasses connection state from glasses\n */\nexport interface GlassesConnectionState extends BaseMessage {\n type: GlassesToCloudMessageType.GLASSES_CONNECTION_STATE\n modelName: string\n status: string\n\n // Optional WiFi details (only present for WiFi-capable glasses)\n wifi?: {\n connected: boolean\n ssid?: string | null\n }\n}\n\n/**\n * Location update from glasses\n */\nexport interface LocationUpdate extends BaseMessage {\n type: GlassesToCloudMessageType.LOCATION_UPDATE | StreamType.LOCATION_UPDATE\n lat: number\n lng: number\n accuracy?: number // Accuracy in meters\n correlationId?: string // for poll responses\n}\n\n/**\n * VPS coordinates update from glasses\n */\nexport interface VpsCoordinates extends BaseMessage {\n type: GlassesToCloudMessageType.VPS_COORDINATES | StreamType.VPS_COORDINATES\n deviceModel: string\n requestId: string\n x: number\n y: number\n z: number\n qx: number\n qy: number\n qz: number\n qw: number\n confidence: number\n}\n\nexport interface LocalTranscription extends BaseMessage {\n type: GlassesToCloudMessageType.LOCAL_TRANSCRIPTION\n text: string\n isFinal: boolean\n startTime: number\n endTime: number\n speakerId: number\n transcribeLanguage: string\n provider: string\n}\n\nexport interface CalendarEvent extends BaseMessage {\n type: GlassesToCloudMessageType.CALENDAR_EVENT | StreamType.CALENDAR_EVENT\n eventId: string\n title: string\n dtStart: string\n dtEnd: string\n timezone: string\n timeStamp: string\n}\n\n/**\n * Voice activity detection from glasses\n */\nexport interface Vad extends BaseMessage {\n type: GlassesToCloudMessageType.VAD\n status: boolean | \"true\" | \"false\"\n}\n\n/**\n * Phone notification from glasses\n */\nexport interface PhoneNotification extends BaseMessage {\n type: GlassesToCloudMessageType.PHONE_NOTIFICATION\n notificationId: string\n app: string\n title: string\n content: string\n priority: \"low\" | \"normal\" | \"high\"\n}\n\n/**\n * Notification dismissed from glasses\n */\nexport interface PhoneNotificationDismissed extends BaseMessage {\n type: GlassesToCloudMessageType.PHONE_NOTIFICATION_DISMISSED\n notificationId: string\n app: string\n title: string\n content: string\n notificationKey: string\n}\n\n/**\n * MentraOS settings update from glasses\n */\nexport interface MentraosSettingsUpdateRequest extends BaseMessage {\n type: GlassesToCloudMessageType.MENTRAOS_SETTINGS_UPDATE_REQUEST\n}\nexport interface MentraosSettingsUpdateRequest extends BaseMessage {\n type: GlassesToCloudMessageType.MENTRAOS_SETTINGS_UPDATE_REQUEST\n}\n\n/**\n * Core status update from glasses\n */\nexport interface CoreStatusUpdate extends BaseMessage {\n type: GlassesToCloudMessageType.CORE_STATUS_UPDATE\n status: string\n details?: Record<string, any>\n}\n\n// ===========================================================\n// Mentra Live\n// ===========================================================\n\n/**\n * Photo error codes for detailed error reporting\n */\nexport enum PhotoErrorCode {\n CAMERA_INIT_FAILED = \"CAMERA_INIT_FAILED\",\n CAMERA_CAPTURE_FAILED = \"CAMERA_CAPTURE_FAILED\",\n CAMERA_TIMEOUT = \"CAMERA_TIMEOUT\",\n CAMERA_BUSY = \"CAMERA_BUSY\",\n UPLOAD_FAILED = \"UPLOAD_FAILED\",\n UPLOAD_TIMEOUT = \"UPLOAD_TIMEOUT\",\n BLE_TRANSFER_FAILED = \"BLE_TRANSFER_FAILED\",\n BLE_TRANSFER_BUSY = \"BLE_TRANSFER_BUSY\",\n BLE_TRANSFER_FAILED_TO_START = \"BLE_TRANSFER_FAILED_TO_START\",\n BLE_TRANSFER_TIMEOUT = \"BLE_TRANSFER_TIMEOUT\",\n COMPRESSION_FAILED = \"COMPRESSION_FAILED\",\n PERMISSION_DENIED = \"PERMISSION_DENIED\",\n STORAGE_FULL = \"STORAGE_FULL\",\n NETWORK_ERROR = \"NETWORK_ERROR\",\n // Phone-side error codes\n PHONE_GLASSES_NOT_CONNECTED = \"PHONE_GLASSES_NOT_CONNECTED\",\n PHONE_BLE_TRANSFER_FAILED = \"PHONE_BLE_TRANSFER_FAILED\",\n PHONE_UPLOAD_FAILED = \"PHONE_UPLOAD_FAILED\",\n PHONE_TIMEOUT = \"PHONE_TIMEOUT\",\n UNKNOWN_ERROR = \"UNKNOWN_ERROR\",\n}\n\n/**\n * Photo processing stages for error context\n */\nexport enum PhotoStage {\n REQUEST_RECEIVED = \"REQUEST_RECEIVED\",\n CAMERA_INIT = \"CAMERA_INIT\",\n PHOTO_CAPTURE = \"PHOTO_CAPTURE\",\n COMPRESSION = \"COMPRESSION\",\n UPLOAD_START = \"UPLOAD_START\",\n UPLOAD_PROGRESS = \"UPLOAD_PROGRESS\",\n BLE_TRANSFER = \"BLE_TRANSFER\",\n RESPONSE_SENT = \"RESPONSE_SENT\",\n}\n\n/**\n * Connection state information for error diagnostics\n */\nexport interface ConnectionState {\n wifi: {\n connected: boolean\n ssid?: string\n hasInternet: boolean\n }\n ble: {\n connected: boolean\n transferInProgress: boolean\n }\n camera: {\n available: boolean\n initialized: boolean\n }\n storage: {\n availableSpace: number\n totalSpace: number\n }\n}\n\n/**\n * Detailed error information for photo failures\n */\nexport interface PhotoErrorDetails {\n stage: PhotoStage\n connectionState?: ConnectionState\n retryable: boolean\n suggestedAction?: string\n diagnosticInfo?: {\n timestamp: number\n duration: number\n retryCount: number\n lastSuccessfulStage?: PhotoStage\n }\n}\n\n/**\n * Enhanced photo response with error support\n */\nexport interface PhotoResponse extends BaseMessage {\n type: GlassesToCloudMessageType.PHOTO_RESPONSE\n requestId: string // Unique ID for the photo request\n success: boolean // Explicit success/failure flag\n\n // Success fields (only present when success = true)\n photoUrl?: string // URL of the uploaded photo\n savedToGallery?: boolean // Whether the photo was saved to gallery\n\n // Error fields (only present when success = false)\n error?: {\n code: PhotoErrorCode\n message: string\n details?: PhotoErrorDetails\n }\n}\n\n/**\n * RGB LED control response from glasses\n */\nexport interface RgbLedControlResponse extends BaseMessage {\n type: GlassesToCloudMessageType.RGB_LED_CONTROL_RESPONSE\n requestId: string\n success: boolean\n error?: string\n}\n\n/**\n * RTMP stream status update from glasses\n */\nexport interface RtmpStreamStatus extends BaseMessage {\n type: GlassesToCloudMessageType.RTMP_STREAM_STATUS\n streamId?: string // Unique identifier for the stream\n status:\n | \"initializing\"\n | \"connecting\"\n | \"reconnecting\"\n | \"streaming\"\n | \"error\"\n | \"stopped\"\n | \"active\"\n | \"stopping\"\n | \"disconnected\"\n | \"timeout\"\n | \"reconnected\"\n | \"reconnect_failed\"\n errorDetails?: string\n appId?: string // ID of the app that requested the stream\n stats?: {\n bitrate: number\n fps: number\n droppedFrames: number\n duration: number\n }\n}\n\n/**\n * Keep-alive acknowledgment from glasses\n */\nexport interface KeepAliveAck extends BaseMessage {\n type: GlassesToCloudMessageType.KEEP_ALIVE_ACK\n streamId: string // ID of the stream being kept alive\n ackId: string // Acknowledgment ID that was sent by cloud\n}\n\n/**\n * Photo taken event from glasses\n */\nexport interface PhotoTaken extends BaseMessage {\n type: GlassesToCloudMessageType.PHOTO_TAKEN\n photoData: ArrayBuffer\n mimeType: string\n timestamp: Date\n}\n\n/**\n * Audio play response from glasses/core\n */\nexport interface AudioPlayResponse extends BaseMessage {\n type: GlassesToCloudMessageType.AUDIO_PLAY_RESPONSE\n requestId: string\n success: boolean\n error?: string\n duration?: number\n}\n\n/**\n * Union type for all messages from glasses to cloud\n */\nexport type GlassesToCloudMessage =\n | ConnectionInit\n | LiveKitInit\n | RequestSettings\n | StartApp\n | StopApp\n | DashboardState\n | OpenDashboard\n | ButtonPress\n | HeadPosition\n | TouchEvent\n | GlassesBatteryUpdate\n | PhoneBatteryUpdate\n | GlassesConnectionState\n | LocationUpdate\n | VpsCoordinates\n | CalendarEvent\n | Vad\n | PhoneNotification\n | PhoneNotificationDismissed\n | MentraosSettingsUpdateRequest\n | CoreStatusUpdate\n | RtmpStreamStatus\n | KeepAliveAck\n | PhotoResponse\n | RgbLedControlResponse\n | PhotoTaken\n | AudioPlayResponse\n | LocalTranscription\n\n//===========================================================\n// Type guards\n//===========================================================\n\nexport function isControlAction(message: GlassesToCloudMessage): boolean {\n return ControlActionTypes.includes(message.type as any)\n}\n\nexport function isEvent(message: GlassesToCloudMessage): boolean {\n return EventTypes.includes(message.type as any)\n}\n\n// Individual type guards\nexport function isConnectionInit(message: GlassesToCloudMessage): message is ConnectionInit {\n return message.type === GlassesToCloudMessageType.CONNECTION_INIT\n}\n\nexport function isRequestSettings(message: GlassesToCloudMessage): message is RequestSettings {\n return message.type === GlassesToCloudMessageType.REQUEST_SETTINGS\n}\n\nexport function isStartApp(message: GlassesToCloudMessage): message is StartApp {\n return message.type === GlassesToCloudMessageType.START_APP\n}\n\nexport function isStopApp(message: GlassesToCloudMessage): message is StopApp {\n return message.type === GlassesToCloudMessageType.STOP_APP\n}\n\nexport function isButtonPress(message: GlassesToCloudMessage): message is ButtonPress {\n return message.type === GlassesToCloudMessageType.BUTTON_PRESS\n}\n\nexport function isHeadPosition(message: GlassesToCloudMessage): message is HeadPosition {\n return message.type === GlassesToCloudMessageType.HEAD_POSITION\n}\n\nexport function isGlassesBatteryUpdate(message: GlassesToCloudMessage): message is GlassesBatteryUpdate {\n return message.type === GlassesToCloudMessageType.GLASSES_BATTERY_UPDATE\n}\n\nexport function isPhoneBatteryUpdate(message: GlassesToCloudMessage): message is PhoneBatteryUpdate {\n return message.type === GlassesToCloudMessageType.PHONE_BATTERY_UPDATE\n}\n\nexport function isGlassesConnectionState(message: GlassesToCloudMessage): message is GlassesConnectionState {\n return message.type === GlassesToCloudMessageType.GLASSES_CONNECTION_STATE\n}\n\nexport function isLocationUpdate(message: GlassesToCloudMessage): message is LocationUpdate {\n return message.type === GlassesToCloudMessageType.LOCATION_UPDATE\n}\n\nexport function isCalendarEvent(message: GlassesToCloudMessage): message is CalendarEvent {\n return message.type === GlassesToCloudMessageType.CALENDAR_EVENT\n}\n\nexport function isVad(message: GlassesToCloudMessage): message is Vad {\n return message.type === GlassesToCloudMessageType.VAD\n}\n\nexport function isPhoneNotification(message: GlassesToCloudMessage): message is PhoneNotification {\n return message.type === GlassesToCloudMessageType.PHONE_NOTIFICATION\n}\n\nexport function isPhoneNotificationDismissed(message: GlassesToCloudMessage): message is PhoneNotificationDismissed {\n return message.type === GlassesToCloudMessageType.PHONE_NOTIFICATION_DISMISSED\n}\n\nexport function isRtmpStreamStatus(message: GlassesToCloudMessage): message is RtmpStreamStatus {\n return message.type === GlassesToCloudMessageType.RTMP_STREAM_STATUS\n}\n\nexport function isPhotoResponse(message: GlassesToCloudMessage): message is PhotoResponse {\n return message.type === GlassesToCloudMessageType.PHOTO_RESPONSE\n}\n\nexport function isRgbLedControlResponse(message: GlassesToCloudMessage): message is RgbLedControlResponse {\n return message.type === GlassesToCloudMessageType.RGB_LED_CONTROL_RESPONSE\n}\n\nexport function isKeepAliveAck(message: GlassesToCloudMessage): message is KeepAliveAck {\n return message.type === GlassesToCloudMessageType.KEEP_ALIVE_ACK\n}\n\nexport function isPhotoTaken(message: GlassesToCloudMessage): message is PhotoTaken {\n return message.type === GlassesToCloudMessageType.PHOTO_TAKEN\n}\n\nexport function isAudioPlayResponse(message: GlassesToCloudMessage): message is AudioPlayResponse {\n return message.type === GlassesToCloudMessageType.AUDIO_PLAY_RESPONSE\n}\n\nexport function isLocalTranscription(message: GlassesToCloudMessage): message is LocalTranscription {\n return message.type === GlassesToCloudMessageType.LOCAL_TRANSCRIPTION\n}\n",
|
|
12
|
+
"// src/messages/cloud-to-glasses.ts\n\nimport {BaseMessage} from \"./base\"\nimport {CloudToGlassesMessageType, ResponseTypes, UpdateTypes} from \"../message-types\"\nimport {Layout} from \"../layouts\"\n// import { UserSession } from \"../user-session\";\n\n//===========================================================\n// Responses\n//===========================================================\n\n/**\n * Connection acknowledgment to glasses\n */\nexport interface ConnectionAck extends BaseMessage {\n type: CloudToGlassesMessageType.CONNECTION_ACK\n // userSession: Partial<UserSession>;\n sessionId: string\n livekit?: {\n url: string\n roomName: string\n token: string\n }\n}\n\n/**\n * Connection error to glasses\n */\nexport interface ConnectionError extends BaseMessage {\n type: CloudToGlassesMessageType.CONNECTION_ERROR\n code?: string\n message: string\n}\n\n/**\n * Authentication error to glasses\n */\nexport interface AuthError extends BaseMessage {\n type: CloudToGlassesMessageType.AUTH_ERROR\n message: string\n}\n\n//===========================================================\n// Updates\n//===========================================================\n\n/**\n * Display update to glasses\n */\nexport interface DisplayEvent extends BaseMessage {\n type: CloudToGlassesMessageType.DISPLAY_EVENT\n layout: Layout\n durationMs?: number\n}\n\n/**\n * App state change to glasses\n */\nexport interface AppStateChange extends BaseMessage {\n type: CloudToGlassesMessageType.APP_STATE_CHANGE\n // userSession: Partial<UserSession>;\n error?: string\n}\n\n/**\n * Microphone state change to glasses\n */\nexport interface MicrophoneStateChange extends BaseMessage {\n type: CloudToGlassesMessageType.MICROPHONE_STATE_CHANGE\n // userSession: Partial<UserSession>;\n isMicrophoneEnabled: boolean\n requiredData: Array<\"pcm\" | \"transcription\" | \"pcm_or_transcription\">\n bypassVad?: boolean // NEW: PCM subscription bypass\n}\n\n/**\n * Photo request to glasses\n */\nexport interface PhotoRequestToGlasses extends BaseMessage {\n type: CloudToGlassesMessageType.PHOTO_REQUEST\n // userSession: Partial<UserSession>;\n requestId: string\n appId: string\n saveToGallery?: boolean\n webhookUrl?: string // URL where ASG should send the photo directly\n authToken?: string // Auth token for webhook authentication\n /** Desired capture size to guide device resolution selection */\n size?: \"small\" | \"medium\" | \"large\" | \"full\"\n /** Image compression level: none, medium, or heavy */\n compress?: \"none\" | \"medium\" | \"heavy\"\n}\n\n/**\n * LED color type for RGB LED control\n */\nexport type LedColor = \"red\" | \"green\" | \"blue\" | \"orange\" | \"white\"\n\n/**\n * RGB LED control request to glasses\n */\nexport interface RgbLedControlToGlasses extends BaseMessage {\n type: CloudToGlassesMessageType.RGB_LED_CONTROL\n requestId: string\n appId: string\n action: \"on\" | \"off\" // Only low-level on/off actions\n color?: LedColor // LED color name\n ontime?: number\n offtime?: number\n count?: number\n}\n\n// TODO(isaiah): Deprecated, remove this after new mobile client refactor complete, and we migrate to SettingsStateChange.\n/**\n * Settings update to glasses\n */\nexport interface SettingsUpdate extends BaseMessage {\n type: CloudToGlassesMessageType.SETTINGS_UPDATE\n sessionId: string\n settings: {\n useOnboardMic: boolean\n contextualDashboard: boolean\n metricSystemEnabled: boolean\n headUpAngle: number\n brightness: number\n autoBrightness: boolean\n sensingEnabled: boolean\n alwaysOnStatusBar: boolean\n bypassVad: boolean\n bypassAudioEncoding: boolean\n }\n}\n\n/**\n * LiveKit info for client to connect & publish\n */\nexport interface LiveKitInfo extends BaseMessage {\n type: CloudToGlassesMessageType.LIVEKIT_INFO\n url: string\n roomName: string\n token: string\n}\n\n//===========================================================\n// RTMP Streaming Commands\n//===========================================================\n\n/**\n * Start RTMP stream command to glasses\n */\nexport interface StartRtmpStream extends BaseMessage {\n type: CloudToGlassesMessageType.START_RTMP_STREAM\n rtmpUrl: string\n appId: string\n streamId?: string\n video?: any // Video configuration\n audio?: any // Audio configuration\n stream?: any // Stream configuration\n}\n\n/**\n * Stop RTMP stream command to glasses\n */\nexport interface StopRtmpStream extends BaseMessage {\n type: CloudToGlassesMessageType.STOP_RTMP_STREAM\n appId: string\n streamId?: string\n}\n\n/**\n * Keep RTMP stream alive command to glasses\n */\nexport interface KeepRtmpStreamAlive extends BaseMessage {\n type: CloudToGlassesMessageType.KEEP_RTMP_STREAM_ALIVE\n streamId: string\n ackId: string\n}\n\n//===========================================================\n// Location Service Commands\n//===========================================================\n\n/**\n * Sets the continuous location update tier on the device.\n */\nexport interface SetLocationTier extends BaseMessage {\n type: CloudToGlassesMessageType.SET_LOCATION_TIER\n tier: \"realtime\" | \"high\" | \"tenMeters\" | \"hundredMeters\" | \"kilometer\" | \"threeKilometers\" | \"reduced\" | \"standard\"\n}\n\n/**\n * Requests a single, on-demand location fix from the device.\n */\nexport interface RequestSingleLocation extends BaseMessage {\n type: CloudToGlassesMessageType.REQUEST_SINGLE_LOCATION\n accuracy: string // The accuracy tier requested by the app\n correlationId: string // To match the response with the poll request\n}\n\n/**\n * Audio play request to glasses\n */\nexport interface AudioPlayRequestToGlasses extends BaseMessage {\n type: CloudToGlassesMessageType.AUDIO_PLAY_REQUEST\n // userSession: Partial<UserSession>;\n requestId: string\n appId: string\n audioUrl: string // URL to audio file for download and play\n volume?: number // Volume level 0.0-1.0, defaults to 1.0\n stopOtherAudio?: boolean // Whether to stop other audio playback, defaults to true\n}\n\n/**\n * Audio stop request to glasses\n */\nexport interface AudioStopRequestToGlasses extends BaseMessage {\n type: CloudToGlassesMessageType.AUDIO_STOP_REQUEST\n // userSession: Partial<UserSession>;\n appId: string\n}\n\n/**\n * WiFi setup request to glasses/mobile\n */\nexport interface ShowWifiSetup extends BaseMessage {\n type: CloudToGlassesMessageType.SHOW_WIFI_SETUP\n reason?: string\n appPackageName?: string\n}\n\n/**\n * Union type for all messages from cloud to glasses\n */\nexport type CloudToGlassesMessage =\n | ConnectionAck\n | ConnectionError\n | AuthError\n | DisplayEvent\n | AppStateChange\n | MicrophoneStateChange\n | PhotoRequestToGlasses\n | RgbLedControlToGlasses\n | AudioPlayRequestToGlasses\n | AudioStopRequestToGlasses\n | SettingsUpdate\n | StartRtmpStream\n | StopRtmpStream\n | KeepRtmpStreamAlive\n | SetLocationTier\n | RequestSingleLocation\n | LiveKitInfo\n | ShowWifiSetup\n\n//===========================================================\n// Type guards\n//===========================================================\n\nexport function isResponse(message: CloudToGlassesMessage): boolean {\n return ResponseTypes.includes(message.type as any)\n}\n\nexport function isUpdate(message: CloudToGlassesMessage): boolean {\n return UpdateTypes.includes(message.type as any)\n}\n\n// Individual type guards\nexport function isConnectionAck(message: CloudToGlassesMessage): message is ConnectionAck {\n return message.type === CloudToGlassesMessageType.CONNECTION_ACK\n}\n\nexport function isConnectionError(message: CloudToGlassesMessage): message is ConnectionError {\n return message.type === CloudToGlassesMessageType.CONNECTION_ERROR\n}\n\nexport function isAuthError(message: CloudToGlassesMessage): message is AuthError {\n return message.type === CloudToGlassesMessageType.AUTH_ERROR\n}\n\nexport function isDisplayEvent(message: CloudToGlassesMessage): message is DisplayEvent {\n return message.type === CloudToGlassesMessageType.DISPLAY_EVENT\n}\n\nexport function isAppStateChange(message: CloudToGlassesMessage): message is AppStateChange {\n return message.type === CloudToGlassesMessageType.APP_STATE_CHANGE\n}\n\nexport function isMicrophoneStateChange(message: CloudToGlassesMessage): message is MicrophoneStateChange {\n return message.type === CloudToGlassesMessageType.MICROPHONE_STATE_CHANGE\n}\n\nexport function isPhotoRequest(message: CloudToGlassesMessage): message is PhotoRequestToGlasses {\n return message.type === CloudToGlassesMessageType.PHOTO_REQUEST\n}\n\nexport function isRgbLedControl(message: CloudToGlassesMessage): message is RgbLedControlToGlasses {\n return message.type === CloudToGlassesMessageType.RGB_LED_CONTROL\n}\n\nexport function isSettingsUpdate(message: CloudToGlassesMessage): message is SettingsUpdate {\n return message.type === CloudToGlassesMessageType.SETTINGS_UPDATE\n}\n\nexport function isStartRtmpStream(message: CloudToGlassesMessage): message is StartRtmpStream {\n return message.type === CloudToGlassesMessageType.START_RTMP_STREAM\n}\n\nexport function isStopRtmpStream(message: CloudToGlassesMessage): message is StopRtmpStream {\n return message.type === CloudToGlassesMessageType.STOP_RTMP_STREAM\n}\n\nexport function isKeepRtmpStreamAlive(message: CloudToGlassesMessage): message is KeepRtmpStreamAlive {\n return message.type === CloudToGlassesMessageType.KEEP_RTMP_STREAM_ALIVE\n}\n\nexport function isAudioPlayRequestToGlasses(message: CloudToGlassesMessage): message is AudioPlayRequestToGlasses {\n return message.type === CloudToGlassesMessageType.AUDIO_PLAY_REQUEST\n}\n\nexport function isAudioStopRequestToGlasses(message: CloudToGlassesMessage): message is AudioStopRequestToGlasses {\n return message.type === CloudToGlassesMessageType.AUDIO_STOP_REQUEST\n}\n",
|
|
13
|
+
"// src/messages/app-to-cloud.ts\n\nimport {BaseMessage} from \"./base\"\nimport {AppToCloudMessageType} from \"../message-types\"\nimport {ExtendedStreamType, LocationStreamRequest} from \"../streams\"\nimport {DisplayRequest} from \"../layouts\"\nimport {DashboardContentUpdate, DashboardModeChange, DashboardSystemUpdate} from \"../dashboard\"\nimport type {VideoConfig, AudioConfig, StreamConfig} from \"../rtmp-stream\"\nimport {LedColor} from \"./cloud-to-glasses\"\n\n// a subscription can now be either a simple string or our new rich object\nexport type SubscriptionRequest = ExtendedStreamType | LocationStreamRequest\n\n/**\n * Connection initialization from App\n */\nexport interface AppConnectionInit extends BaseMessage {\n type: AppToCloudMessageType.CONNECTION_INIT\n packageName: string\n sessionId: string\n apiKey: string\n}\n\n/**\n * Subscription update from App\n */\nexport interface AppSubscriptionUpdate extends BaseMessage {\n type: AppToCloudMessageType.SUBSCRIPTION_UPDATE\n packageName: string\n subscriptions: SubscriptionRequest[]\n}\n\n/**\n * Photo request from App\n */\nexport interface PhotoRequest extends BaseMessage {\n type: AppToCloudMessageType.PHOTO_REQUEST\n packageName: string\n requestId: string // SDK-generated request ID to track the request\n saveToGallery?: boolean\n customWebhookUrl?: string // Custom webhook URL to override TPA's default\n authToken?: string // Auth token for custom webhook authentication\n /** Desired photo size sent by App. Defaults to 'medium' if omitted. */\n size?: \"small\" | \"medium\" | \"large\" | \"full\"\n /** Image compression level: none, medium, or heavy. Defaults to none. */\n compress?: \"none\" | \"medium\" | \"heavy\"\n}\n\n/**\n * RGB LED control request from App\n */\nexport interface RgbLedControlRequest extends BaseMessage {\n type: AppToCloudMessageType.RGB_LED_CONTROL\n packageName: string\n requestId: string // SDK-generated request ID to track the request\n action: \"on\" | \"off\" // Only low-level on/off actions\n color?: LedColor // LED color name\n ontime?: number // LED on duration in ms\n offtime?: number // LED off duration in ms\n count?: number // Number of on/off cycles\n}\n\n// Video, Audio and Stream configuration interfaces are imported from '../rtmp-stream'\n\n/**\n * RTMP stream request from App\n */\nexport interface RtmpStreamRequest extends BaseMessage {\n type: AppToCloudMessageType.RTMP_STREAM_REQUEST\n packageName: string\n rtmpUrl: string\n video?: VideoConfig\n audio?: AudioConfig\n stream?: StreamConfig\n}\n\n/**\n * RTMP stream stop request from App\n */\nexport interface RtmpStreamStopRequest extends BaseMessage {\n type: AppToCloudMessageType.RTMP_STREAM_STOP\n packageName: string\n streamId?: string // Optional stream ID to specify which stream to stop\n}\n\n// defines the structure for our new on-demand location poll command\nexport interface AppLocationPollRequest extends BaseMessage {\n type: AppToCloudMessageType.LOCATION_POLL_REQUEST\n packageName: string\n sessionId: string\n accuracy: string\n correlationId: string\n}\n\n/**\n * Re-stream destination for managed streams\n */\nexport interface RestreamDestination {\n /** RTMP URL like rtmp://youtube.com/live/STREAM-KEY */\n url: string\n /** Optional friendly name like \"YouTube\" or \"Twitch\" */\n name?: string\n}\n\n/**\n * Managed RTMP stream request from App\n * The cloud handles the RTMP endpoint and returns HLS/DASH URLs\n */\nexport interface ManagedStreamRequest extends BaseMessage {\n type: AppToCloudMessageType.MANAGED_STREAM_REQUEST\n packageName: string\n quality?: \"720p\" | \"1080p\"\n enableWebRTC?: boolean\n video?: VideoConfig\n audio?: AudioConfig\n stream?: StreamConfig\n /** Optional RTMP destinations to re-stream to (YouTube, Twitch, etc) */\n restreamDestinations?: RestreamDestination[]\n}\n\n/**\n * Managed RTMP stream stop request from App\n */\nexport interface ManagedStreamStopRequest extends BaseMessage {\n type: AppToCloudMessageType.MANAGED_STREAM_STOP\n packageName: string\n}\n\n/**\n * Stream status check request from App\n * Checks if there are any existing streams (managed or unmanaged) for the current user\n */\nexport interface StreamStatusCheckRequest extends BaseMessage {\n type: AppToCloudMessageType.STREAM_STATUS_CHECK\n packageName: string\n sessionId: string\n}\n\n/**\n * Audio play request from App\n */\nexport interface AudioPlayRequest extends BaseMessage {\n type: AppToCloudMessageType.AUDIO_PLAY_REQUEST\n packageName: string\n requestId: string // SDK-generated request ID to track the request\n audioUrl: string // URL to audio file for download and play\n volume?: number // Volume level 0.0-1.0, defaults to 1.0\n stopOtherAudio?: boolean // Whether to stop other audio playback, defaults to true\n /**\n * Track ID for audio playback (defaults to 0)\n * - 0: speaker (default audio playback)\n * - 1: app_audio (app-specific audio)\n * - 2: tts (text-to-speech audio)\n * Use different track IDs to play multiple audio streams simultaneously (mixing)\n */\n trackId?: number\n}\n\n/**\n * Audio stop request from App\n */\nexport interface AudioStopRequest extends BaseMessage {\n type: AppToCloudMessageType.AUDIO_STOP_REQUEST\n packageName: string\n /**\n * Track ID to stop (optional)\n * 0: speaker (default audio playback)\n * 1: app_audio (app-specific audio)\n * 2: tts (text-to-speech audio)\n * If omitted, stops all tracks\n */\n trackId?: number\n sessionId?: string // Session ID for routing\n}\n\n/**\n * WiFi setup request from App\n */\nexport interface RequestWifiSetup extends BaseMessage {\n type: AppToCloudMessageType.REQUEST_WIFI_SETUP\n packageName: string\n sessionId: string\n reason?: string\n}\n\n/**\n * Union type for all messages from Apps to cloud\n */\nexport type AppToCloudMessage =\n | AppConnectionInit\n | AppSubscriptionUpdate\n | AppLocationPollRequest\n | DisplayRequest\n | PhotoRequest\n | RgbLedControlRequest\n | AudioPlayRequest\n | AudioStopRequest\n | RtmpStreamRequest\n | RtmpStreamStopRequest\n | ManagedStreamRequest\n | ManagedStreamStopRequest\n | StreamStatusCheckRequest\n | DashboardContentUpdate\n | DashboardModeChange\n | DashboardSystemUpdate\n | RequestWifiSetup\n // New App-to-App communication messages\n | AppBroadcastMessage\n | AppDirectMessage\n | AppUserDiscovery\n | AppRoomJoin\n | AppRoomLeave\n\n/**\n * Type guard to check if a message is a App connection init\n */\nexport function isAppConnectionInit(message: AppToCloudMessage): message is AppConnectionInit {\n return message.type === AppToCloudMessageType.CONNECTION_INIT\n}\n\n/**\n * Type guard to check if a message is a App subscription update\n */\nexport function isAppSubscriptionUpdate(message: AppToCloudMessage): message is AppSubscriptionUpdate {\n return message.type === AppToCloudMessageType.SUBSCRIPTION_UPDATE\n}\n\n/**\n * Type guard to check if a message is a App display request\n */\nexport function isDisplayRequest(message: AppToCloudMessage): message is DisplayRequest {\n return message.type === AppToCloudMessageType.DISPLAY_REQUEST\n}\n\n/**\n * Type guard to check if a message is a App photo request\n */\nexport function isPhotoRequest(message: AppToCloudMessage): message is PhotoRequest {\n return message.type === AppToCloudMessageType.PHOTO_REQUEST\n}\n\n/**\n * Type guard to check if a message is a RGB LED control request\n */\nexport function isRgbLedControlRequest(message: AppToCloudMessage): message is RgbLedControlRequest {\n return message.type === AppToCloudMessageType.RGB_LED_CONTROL\n}\n\n/**\n * Type guard to check if a message is a App audio play request\n */\nexport function isAudioPlayRequest(message: AppToCloudMessage): message is AudioPlayRequest {\n return message.type === AppToCloudMessageType.AUDIO_PLAY_REQUEST\n}\n\n/**\n * Type guard to check if a message is a App audio stop request\n */\nexport function isAudioStopRequest(message: AppToCloudMessage): message is AudioStopRequest {\n return message.type === AppToCloudMessageType.AUDIO_STOP_REQUEST\n}\n\n/**\n * Type guard to check if a message is a dashboard content update\n */\nexport function isDashboardContentUpdate(message: AppToCloudMessage): message is DashboardContentUpdate {\n return message.type === AppToCloudMessageType.DASHBOARD_CONTENT_UPDATE\n}\n\n/**\n * Type guard to check if a message is a dashboard mode change\n */\nexport function isDashboardModeChange(message: AppToCloudMessage): message is DashboardModeChange {\n return message.type === AppToCloudMessageType.DASHBOARD_MODE_CHANGE\n}\n\n/**\n * Type guard to check if a message is a dashboard system update\n */\nexport function isDashboardSystemUpdate(message: AppToCloudMessage): message is DashboardSystemUpdate {\n return message.type === AppToCloudMessageType.DASHBOARD_SYSTEM_UPDATE\n}\n\n/**\n * Type guard to check if a message is a managed stream request\n */\nexport function isManagedStreamRequest(message: AppToCloudMessage): message is ManagedStreamRequest {\n return message.type === AppToCloudMessageType.MANAGED_STREAM_REQUEST\n}\n\n/**\n * Type guard to check if a message is a managed stream stop request\n */\nexport function isManagedStreamStopRequest(message: AppToCloudMessage): message is ManagedStreamStopRequest {\n return message.type === AppToCloudMessageType.MANAGED_STREAM_STOP\n}\n\n//===========================================================\n// App-to-App Communication Messages\n//===========================================================\n\n/**\n * Broadcast message to all users with the same App active\n */\nexport interface AppBroadcastMessage extends BaseMessage {\n type: AppToCloudMessageType.APP_BROADCAST_MESSAGE\n packageName: string\n sessionId: string\n payload: any\n messageId: string\n senderUserId: string\n}\n\n/**\n * Direct message to a specific user with the same App active\n */\nexport interface AppDirectMessage extends BaseMessage {\n type: AppToCloudMessageType.APP_DIRECT_MESSAGE\n packageName: string\n sessionId: string\n targetUserId: string\n payload: any\n messageId: string\n senderUserId: string\n}\n\n/**\n * Request to discover other users with the same App active\n */\nexport interface AppUserDiscovery extends BaseMessage {\n type: AppToCloudMessageType.APP_USER_DISCOVERY\n packageName: string\n sessionId: string\n includeUserProfiles?: boolean\n}\n\n/**\n * Join a communication room for group messaging\n */\nexport interface AppRoomJoin extends BaseMessage {\n type: AppToCloudMessageType.APP_ROOM_JOIN\n packageName: string\n sessionId: string\n roomId: string\n roomConfig?: {\n maxUsers?: number\n isPrivate?: boolean\n metadata?: any\n }\n}\n\n/**\n * Leave a communication room\n */\nexport interface AppRoomLeave extends BaseMessage {\n type: AppToCloudMessageType.APP_ROOM_LEAVE\n packageName: string\n sessionId: string\n roomId: string\n}\n\n/**\n * Type guard to check if a message is an RTMP stream request\n */\nexport function isRtmpStreamRequest(message: AppToCloudMessage): message is RtmpStreamRequest {\n return message.type === AppToCloudMessageType.RTMP_STREAM_REQUEST\n}\n\n/**\n * Type guard to check if a message is an RTMP stream stop request\n */\nexport function isRtmpStreamStopRequest(message: AppToCloudMessage): message is RtmpStreamStopRequest {\n return message.type === AppToCloudMessageType.RTMP_STREAM_STOP\n}\n",
|
|
14
|
+
"/**\n * 🎨 Bitmap Utilities Module\n *\n * Provides helper functions for working with bitmap images in MentraOS applications.\n * Includes file loading, data validation, and format conversion utilities.\n *\n * @example\n * ```typescript\n * import { BitmapUtils } from '@mentra/sdk';\n *\n * // Load a single BMP file\n * const bmpHex = await BitmapUtils.loadBmpAsHex('./my-image.bmp');\n * session.layouts.showBitmapView(bmpHex);\n *\n * // Load multiple animation frames\n * const frames = await BitmapUtils.loadBmpFrames('./animations', 10);\n * session.layouts.showBitmapAnimation(frames, 1500, true);\n * ```\n */\n\nimport * as fs from \"fs/promises\";\nimport * as path from \"path\";\nimport { Jimp } from \"jimp\";\n\n/**\n * Validation result for bitmap data\n */\nexport interface BitmapValidation {\n /** Whether the bitmap data is valid */\n isValid: boolean;\n /** Total byte count of the bitmap */\n byteCount: number;\n /** Number of black (non-FF) pixels found */\n blackPixels: number;\n /** Array of validation error messages */\n errors: string[];\n /** Additional metadata about the bitmap */\n metadata?: {\n /** Expected bitmap dimensions (if detectable) */\n dimensions?: { width: number; height: number };\n /** Bitmap file format info */\n format?: string;\n };\n}\n\n/**\n * Options for loading bitmap frames\n */\nexport interface LoadFramesOptions {\n /** File name pattern (default: 'animation_10_frame_{i}.bmp') */\n filePattern?: string;\n /** Starting frame number (default: 1) */\n startFrame?: number;\n /** Validate each frame during loading (default: true) */\n validateFrames?: boolean;\n /** Continue loading if a frame is missing (default: false) */\n skipMissingFrames?: boolean;\n}\n\n/**\n * Utility class for working with bitmap images in MentraOS applications\n */\nexport class BitmapUtils {\n static async convert24BitTo1BitBMP(input24BitBmp: Buffer): Promise<Buffer> {\n // Read header information from 24-bit BMP\n const width = input24BitBmp.readUInt32LE(18);\n const height = Math.abs(input24BitBmp.readInt32LE(22)); // Height can be negative (top-down BMP)\n const isTopDown = input24BitBmp.readInt32LE(22) < 0;\n const bitsPerPixel = input24BitBmp.readUInt16LE(28);\n\n if (bitsPerPixel !== 24) {\n throw new Error(\"Input must be a 24-bit BMP\");\n }\n\n // Calculate row sizes (both must be 4-byte aligned)\n const rowSize24 = Math.ceil((width * 3) / 4) * 4;\n const rowSize1 = Math.ceil(width / 32) * 4; // 32 pixels per 4 bytes\n\n // Calculate sizes for 1-bit BMP\n const colorTableSize = 8; // 2 colors * 4 bytes each\n const headerSize = 54 + colorTableSize;\n const pixelDataSize = rowSize1 * height;\n const fileSize = headerSize + pixelDataSize;\n\n // Create new buffer for 1-bit BMP\n const output1BitBmp = Buffer.alloc(fileSize);\n let offset = 0;\n\n // Write BMP file header (14 bytes)\n output1BitBmp.write(\"BM\", offset);\n offset += 2; // Signature\n output1BitBmp.writeUInt32LE(fileSize, offset);\n offset += 4; // File size\n output1BitBmp.writeUInt16LE(0, offset);\n offset += 2; // Reserved 1\n output1BitBmp.writeUInt16LE(0, offset);\n offset += 2; // Reserved 2\n output1BitBmp.writeUInt32LE(headerSize, offset);\n offset += 4; // Pixel data offset\n\n // Write DIB header (40 bytes)\n output1BitBmp.writeUInt32LE(40, offset);\n offset += 4; // DIB header size\n output1BitBmp.writeInt32LE(width, offset);\n offset += 4; // Width\n output1BitBmp.writeInt32LE(height, offset);\n offset += 4; // Height (positive for bottom-up)\n output1BitBmp.writeUInt16LE(1, offset);\n offset += 2; // Planes\n output1BitBmp.writeUInt16LE(1, offset);\n offset += 2; // Bits per pixel (1-bit)\n output1BitBmp.writeUInt32LE(0, offset);\n offset += 4; // Compression (none)\n output1BitBmp.writeUInt32LE(pixelDataSize, offset);\n offset += 4; // Image size\n output1BitBmp.writeInt32LE(2835, offset);\n offset += 4; // X pixels per meter (72 DPI)\n output1BitBmp.writeInt32LE(2835, offset);\n offset += 4; // Y pixels per meter (72 DPI)\n output1BitBmp.writeUInt32LE(2, offset);\n offset += 4; // Colors used\n output1BitBmp.writeUInt32LE(2, offset);\n offset += 4; // Important colors\n\n // Write color table (8 bytes)\n // Black (index 0): B=0, G=0, R=0, Reserved=0\n output1BitBmp.writeUInt32LE(0x00000000, offset);\n offset += 4;\n // White (index 1): B=255, G=255, R=255, Reserved=0\n output1BitBmp.writeUInt8(255, offset++); // Blue\n output1BitBmp.writeUInt8(255, offset++); // Green\n output1BitBmp.writeUInt8(255, offset++); // Red\n output1BitBmp.writeUInt8(0, offset++); // Reserved\n\n // Convert pixel data from 24-bit to 1-bit\n const pixelDataStart24 = 54; // 24-bit BMP has no color table\n\n for (let y = 0; y < height; y++) {\n // BMP files are usually stored bottom-up\n const sourceY = isTopDown ? y : height - 1 - y;\n const destY = height - 1 - y; // Always write bottom-up for compatibility\n\n // Initialize the row with zeros\n const rowData = Buffer.alloc(rowSize1);\n\n for (let x = 0; x < width; x++) {\n // Get pixel from 24-bit BMP\n const offset24 = pixelDataStart24 + sourceY * rowSize24 + x * 3;\n const blue = input24BitBmp[offset24];\n const green = input24BitBmp[offset24 + 1];\n const red = input24BitBmp[offset24 + 2];\n\n // Determine if pixel is white (assuming pure black or white)\n // White = 1, Black = 0\n const isWhite = red > 128 || green > 128 || blue > 128 ? 1 : 0;\n\n // Calculate bit position\n const byteIndex = Math.floor(x / 8);\n const bitPosition = 7 - (x % 8); // MSB first\n\n // Set bit if white\n if (isWhite) {\n rowData[byteIndex] |= 1 << bitPosition;\n }\n }\n\n // Write row to output buffer\n const destOffset = offset + destY * rowSize1;\n rowData.copy(output1BitBmp, destOffset);\n }\n\n return output1BitBmp;\n }\n\n /**\n * Load a BMP file as hex string from filesystem\n *\n * @param filePath - Path to the BMP file\n * @returns Promise resolving to hex-encoded bitmap data\n * @throws Error if file cannot be read or is not a valid BMP\n *\n * @example\n * ```typescript\n * const bmpBase64 = await BitmapUtils.loadBmpFromFileAsBase64('./assets/icon.bmp');\n * session.layouts.showBitmapView(bmpBase64);\n * ```\n */\n static async fileToBase64(filePath: string): Promise<string> {\n try {\n const bmpData = await fs.readFile(filePath);\n\n return this.bufferToBase64(bmpData);\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(\n `Failed to load BMP file ${filePath}: ${error.message}`,\n );\n }\n throw new Error(`Failed to load BMP file ${filePath}: Unknown error`);\n }\n }\n\n static async bufferToBase64(bmpData: Buffer): Promise<string> {\n return bmpData.toString(\"base64\");\n }\n\n static async padBase64Bitmap(\n bmpBase64: string,\n padding?: { left: number; top: number },\n ): Promise<string> {\n const buffer = Buffer.from(bmpBase64, \"base64\");\n const paddedBuffer = await this.padBmpForGlasses(\n buffer,\n padding?.left,\n padding?.top,\n );\n return paddedBuffer.toString(\"base64\");\n }\n\n static async padBmpForGlasses(\n bmpData: Buffer,\n leftPadding: number = 50,\n topPadding: number = 35,\n ): Promise<Buffer> {\n try {\n // Basic BMP validation - check for BMP signature\n if (bmpData.length < 14 || bmpData[0] !== 0x42 || bmpData[1] !== 0x4d) {\n throw new Error(\n `Bmp data is not a valid BMP file (missing BM signature)`,\n );\n }\n\n let finalBmpData = bmpData;\n\n // Load the image with Jimp\n const image = await Jimp.read(bmpData);\n\n // Check if we need to add padding\n if (image.width !== 576 || image.height !== 135) {\n console.log(\n `Adding padding to BMP since it isn't 576x135 (assuming it's 526x100!)(current: ${image.width}x${image.height})`,\n );\n\n // Create a new 576x135 white canvas\n const paddedImage = new Jimp({\n width: 576,\n height: 135,\n color: 0x00000000,\n });\n\n // // Calculate position to place the original image (with padding)\n const leftPadding = 50; // 45px padding on left\n const topPadding = 35; // 35px padding on top\n\n // Composite the original image onto the white canvas\n // paddedImage.composite(image, leftPadding, topPadding);\n paddedImage.composite(image, leftPadding, topPadding);\n\n finalBmpData = await this.convert24BitTo1BitBMP(\n await paddedImage.getBuffer(\"image/bmp\"),\n );\n }\n // No padding needed, just return as hex\n console.log(`finalBmpData: ${finalBmpData.length} bytes`);\n return finalBmpData;\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`Failed to load BMP data: ${error.message}`);\n }\n throw new Error(`Failed to load BMP data: Unknown error`);\n }\n }\n\n /**\n * Load multiple BMP frames as hex array for animations\n *\n * @param basePath - Directory containing the frame files\n * @param frameCount - Number of frames to load\n * @param options - Loading options and configuration\n * @returns Promise resolving to array of hex-encoded bitmap data\n *\n * @example\n * ```typescript\n * // Load 10 frames with default pattern\n * const frames = await BitmapUtils.loadBmpFrames('./animations', 10);\n *\n * // Load with custom pattern\n * const customFrames = await BitmapUtils.loadBmpFrames('./sprites', 8, {\n * filePattern: 'sprite_{i}.bmp',\n * startFrame: 0\n * });\n * ```\n */\n static async loadBmpFrames(\n basePath: string,\n frameCount: number,\n options: LoadFramesOptions = {},\n ): Promise<string[]> {\n const {\n filePattern = \"animation_10_frame_{i}.bmp\",\n startFrame = 1,\n validateFrames = true,\n skipMissingFrames = false,\n } = options;\n\n const frames: string[] = [];\n const errors: string[] = [];\n\n for (let i = 0; i < frameCount; i++) {\n const frameNumber = startFrame + i;\n const fileName = filePattern.replace(\"{i}\", frameNumber.toString());\n const filePath = path.join(basePath, fileName);\n\n try {\n const frameBase64 = await this.fileToBase64(filePath);\n\n if (validateFrames) {\n const validation = this.validateBase64Bitmap(frameBase64);\n if (!validation.isValid) {\n const errorMsg = `Frame ${frameNumber} validation failed: ${validation.errors.join(\n \", \",\n )}`;\n if (skipMissingFrames) {\n console.warn(`⚠️ ${errorMsg} - skipping`);\n continue;\n } else {\n throw new Error(errorMsg);\n }\n }\n console.log(\n `✅ Frame ${frameNumber} validated (${validation.blackPixels} black pixels)`,\n );\n }\n\n frames.push(frameBase64);\n } catch (error) {\n const errorMsg = `Failed to load frame ${frameNumber} (${fileName}): ${\n error instanceof Error ? error.message : \"Unknown error\"\n }`;\n\n if (skipMissingFrames) {\n console.warn(`⚠️ ${errorMsg} - skipping`);\n continue;\n } else {\n errors.push(errorMsg);\n }\n }\n }\n\n if (errors.length > 0) {\n throw new Error(`Failed to load frames:\\n${errors.join(\"\\n\")}`);\n }\n\n if (frames.length === 0) {\n throw new Error(`No valid frames loaded from ${basePath}`);\n }\n\n console.log(`📚 Loaded ${frames.length} animation frames from ${basePath}`);\n return frames;\n }\n\n /**\n * Validate BMP hex data integrity and extract metadata\n *\n * @param hexString - Hex-encoded bitmap data\n * @returns Validation result with detailed information\n *\n * @example\n * ```typescript\n * const validation = BitmapUtils.validateBmpHex(bmpHex);\n * if (!validation.isValid) {\n * console.error('Invalid bitmap:', validation.errors);\n * } else {\n * console.log(`Valid bitmap: ${validation.blackPixels} black pixels`);\n * }\n * ```\n */\n static validateBase64Bitmap(bmpFrame: string): BitmapValidation {\n const errors: string[] = [];\n let byteCount = 0;\n let blackPixels = 0;\n const metadata: BitmapValidation[\"metadata\"] = {};\n\n try {\n const hexString = Buffer.from(bmpFrame, \"base64\").toString(\"hex\");\n // Basic hex validation\n if (typeof hexString !== \"string\" || hexString.length === 0) {\n errors.push(\"Hex string is empty or invalid\");\n return { isValid: false, byteCount: 0, blackPixels: 0, errors };\n }\n\n if (hexString.length % 2 !== 0) {\n errors.push(\"Hex string length must be even\");\n return { isValid: false, byteCount: 0, blackPixels: 0, errors };\n }\n\n // Convert to buffer\n const buffer = Buffer.from(hexString, \"hex\");\n byteCount = buffer.length;\n\n // BMP signature validation\n if (buffer.length < 14) {\n errors.push(\n \"File too small to be a valid BMP (minimum 14 bytes for header)\",\n );\n } else {\n if (buffer[0] !== 0x42 || buffer[1] !== 0x4d) {\n errors.push('Invalid BMP signature (should start with \"BM\")');\n }\n }\n\n // Size validation for MentraOS (576x135 = ~9782 bytes expected)\n const expectedSize = 9782;\n if (buffer.length < expectedSize - 100) {\n // Allow some tolerance\n errors.push(\n `BMP too small (${buffer.length} bytes, expected ~${expectedSize})`,\n );\n } else if (buffer.length > expectedSize + 1000) {\n // Allow some tolerance\n errors.push(\n `BMP too large (${buffer.length} bytes, expected ~${expectedSize})`,\n );\n }\n\n // Extract BMP metadata if header is valid\n if (buffer.length >= 54) {\n try {\n // BMP width and height are at offsets 18 and 22 (little-endian)\n const width = buffer.readUInt32LE(18);\n const height = buffer.readUInt32LE(22);\n metadata.dimensions = { width, height };\n metadata.format = \"BMP\";\n\n // Validate dimensions for MentraOS glasses\n if (width !== 576 || height !== 135) {\n errors.push(\n `Invalid dimensions (${width}x${height}, expected 576x135 for MentraOS)`,\n );\n }\n } catch (e) {\n errors.push(\"Failed to parse BMP header metadata\");\n }\n }\n\n // Pixel data validation (assumes 54-byte header + pixel data)\n if (buffer.length > 62) {\n const pixelData = buffer.slice(62); // Skip BMP header\n blackPixels = Array.from(pixelData).filter((b) => b !== 0xff).length;\n\n if (blackPixels === 0) {\n errors.push(\"No black pixels found (image appears to be all white)\");\n }\n } else {\n errors.push(\"File too small to contain pixel data\");\n }\n } catch (error) {\n errors.push(\n `Failed to parse hex data: ${\n error instanceof Error ? error.message : \"Unknown error\"\n }`,\n );\n }\n\n return {\n isValid: errors.length === 0,\n byteCount,\n blackPixels,\n errors,\n metadata: Object.keys(metadata).length > 0 ? metadata : undefined,\n };\n }\n\n /**\n * Convert bitmap data between different formats\n *\n * @param data - Input bitmap data\n * @param fromFormat - Source format ('hex' | 'base64' | 'buffer')\n * @param toFormat - Target format ('hex' | 'base64' | 'buffer')\n * @returns Converted bitmap data\n *\n * @example\n * ```typescript\n * const base64Data = BitmapUtils.convertFormat(hexData, 'hex', 'base64');\n * const bufferData = BitmapUtils.convertFormat(base64Data, 'base64', 'buffer');\n * ```\n */\n static convertFormat(\n data: string | Buffer,\n fromFormat: \"hex\" | \"base64\" | \"buffer\",\n toFormat: \"hex\" | \"base64\" | \"buffer\",\n ): string | Buffer {\n let buffer: Buffer;\n\n // Convert input to buffer\n switch (fromFormat) {\n case \"hex\":\n buffer = Buffer.from(data as string, \"hex\");\n break;\n case \"base64\":\n buffer = Buffer.from(data as string, \"base64\");\n break;\n case \"buffer\":\n buffer = data as Buffer;\n break;\n default:\n throw new Error(`Unsupported source format: ${fromFormat}`);\n }\n\n // Convert buffer to target format\n switch (toFormat) {\n case \"hex\":\n return buffer.toString(\"hex\");\n case \"base64\":\n return buffer.toString(\"base64\");\n case \"buffer\":\n return buffer;\n default:\n throw new Error(`Unsupported target format: ${toFormat}`);\n }\n }\n\n /**\n * Get bitmap information without full validation\n *\n * @param hexString - Hex-encoded bitmap data\n * @returns Basic bitmap information\n *\n * @example\n * ```typescript\n * const info = BitmapUtils.getBitmapInfo(bmpHex);\n * console.log(`Bitmap: ${info.width}x${info.height}, ${info.blackPixels} black pixels`);\n * ```\n */\n static getBitmapInfo(hexString: string): {\n byteCount: number;\n blackPixels: number;\n width?: number;\n height?: number;\n isValidBmp: boolean;\n } {\n try {\n const buffer = Buffer.from(hexString, \"hex\");\n const isValidBmp =\n buffer.length >= 14 && buffer[0] === 0x42 && buffer[1] === 0x4d;\n\n let width: number | undefined;\n let height: number | undefined;\n\n if (isValidBmp && buffer.length >= 54) {\n try {\n width = buffer.readUInt32LE(18);\n height = buffer.readUInt32LE(22);\n } catch (e) {\n // Ignore metadata parsing errors\n }\n }\n\n const pixelData = buffer.slice(62);\n const blackPixels = Array.from(pixelData).filter(\n (b) => b !== 0xff,\n ).length;\n\n return {\n byteCount: buffer.length,\n blackPixels,\n width,\n height,\n isValidBmp,\n };\n } catch (error) {\n return {\n byteCount: 0,\n blackPixels: 0,\n isValidBmp: false,\n };\n }\n }\n}\n",
|
|
15
|
+
"/**\n * 🎬 Animation Utilities Module\n *\n * Provides helper functions for creating and managing bitmap animations in MentraOS applications.\n * Includes timing utilities, animation factories, and performance optimization helpers.\n *\n * @example\n * ```typescript\n * import { AnimationUtils } from '@mentra/sdk';\n *\n * // Create animation from files\n * const animation = await AnimationUtils.createBitmapAnimation(\n * session, './frames', 10, 1750, true\n * );\n *\n * // Simple delay utility\n * await AnimationUtils.delay(2000);\n *\n * // Stop animation\n * animation.stop();\n * ```\n */\n\nimport { AppSession } from \"../app/session\";\nimport { BitmapUtils, LoadFramesOptions } from \"./bitmap-utils\";\n\n/**\n * Configuration options for bitmap animations\n */\nexport interface AnimationConfig {\n /** Time between frames in milliseconds (default: 1750ms - optimized for MentraOS) */\n intervalMs?: number;\n /** Whether to loop the animation continuously (default: false) */\n repeat?: boolean;\n /** Validate frames before starting animation (default: true) */\n validateFrames?: boolean;\n /** Options for loading frames from files */\n loadOptions?: LoadFramesOptions;\n /** Callback fired when animation starts */\n onStart?: () => void;\n /** Callback fired when animation stops/completes */\n onStop?: () => void;\n /** Callback fired on each frame display */\n onFrame?: (frameIndex: number, totalFrames: number) => void;\n /** Callback fired if animation encounters an error */\n onError?: (error: string) => void;\n}\n\n/**\n * Animation controller interface\n */\nexport interface AnimationController {\n /** Stop the animation */\n stop: () => void;\n /** Check if animation is currently running */\n isRunning: () => boolean;\n /** Get current frame index */\n getCurrentFrame: () => number;\n /** Get total frame count */\n getTotalFrames: () => number;\n}\n\n/**\n * Performance timing information\n */\nexport interface TimingInfo {\n /** Target interval between frames */\n targetInterval: number;\n /** Actual measured interval between frames */\n actualInterval: number;\n /** Timing drift (difference between target and actual) */\n drift: number;\n /** Frame rate (frames per second) */\n fps: number;\n}\n\n/**\n * Utility class for creating and managing animations in MentraOS applications\n */\nexport class AnimationUtils {\n /**\n * Simple async delay helper\n *\n * @param ms - Milliseconds to delay\n * @returns Promise that resolves after the specified delay\n *\n * @example\n * ```typescript\n * console.log('Starting...');\n * await AnimationUtils.delay(2000);\n * console.log('2 seconds later!');\n * ```\n */\n static delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n\n /**\n * Create bitmap animation from files with advanced configuration\n *\n * @param session - MentraOS app session\n * @param basePath - Directory containing animation frames\n * @param frameCount - Number of frames to load\n * @param config - Animation configuration options\n * @returns Promise resolving to animation controller\n *\n * @example\n * ```typescript\n * // Simple animation\n * const animation = await AnimationUtils.createBitmapAnimation(\n * session, './animations', 10\n * );\n *\n * // Advanced configuration\n * const advancedAnimation = await AnimationUtils.createBitmapAnimation(\n * session, './sprites', 8, {\n * intervalMs: 1000,\n * repeat: true,\n * loadOptions: { filePattern: 'sprite_{i}.bmp', startFrame: 0 },\n * onFrame: (frame, total) => console.log(`Frame ${frame}/${total}`),\n * onError: (error) => console.error('Animation error:', error)\n * }\n * );\n * ```\n */\n static async createBitmapAnimation(\n session: AppSession,\n basePath: string,\n frameCount: number,\n config: AnimationConfig = {},\n ): Promise<AnimationController> {\n const {\n intervalMs = 1750, // Optimized for MentraOS hardware\n repeat = false,\n validateFrames = true,\n loadOptions = {},\n onStart,\n onStop,\n onFrame,\n onError,\n } = config;\n\n try {\n console.log(\n `🎬 Loading ${frameCount} animation frames from ${basePath}...`,\n );\n\n // Load frames with validation\n const frames = await BitmapUtils.loadBmpFrames(basePath, frameCount, {\n validateFrames,\n ...loadOptions,\n });\n\n if (frames.length === 0) {\n throw new Error(\"No frames loaded for animation\");\n }\n\n console.log(\n `📚 Animation ready: ${frames.length} frames at ${intervalMs}ms intervals`,\n );\n\n // Create enhanced animation with the loaded frames\n return this.createBitmapAnimationFromFrames(session, frames, {\n intervalMs,\n repeat,\n onStart,\n onStop,\n onFrame,\n onError,\n });\n } catch (error) {\n const errorMsg = `Failed to create animation: ${error instanceof Error ? error.message : \"Unknown error\"}`;\n console.error(`❌ ${errorMsg}`);\n if (onError) {\n onError(errorMsg);\n }\n throw new Error(errorMsg);\n }\n }\n\n /**\n * Create bitmap animation from pre-loaded frame data\n *\n * @param session - MentraOS app session\n * @param frames - Array of hex-encoded bitmap data\n * @param config - Animation configuration options\n * @returns Animation controller\n *\n * @example\n * ```typescript\n * const frames = ['424d461a...', '424d461b...', '424d461c...'];\n * const animation = AnimationUtils.createBitmapAnimationFromFrames(\n * session, frames, { intervalMs: 1500, repeat: true }\n * );\n * ```\n */\n static createBitmapAnimationFromFrames(\n session: AppSession,\n frames: string[],\n config: Omit<AnimationConfig, \"loadOptions\" | \"validateFrames\"> = {},\n ): AnimationController {\n const {\n intervalMs = 1750,\n repeat = false,\n onStart,\n onStop,\n onFrame,\n onError,\n } = config;\n\n let isRunning = false;\n const currentFrame = 0;\n let animationController: { stop: () => void } | null = null;\n\n const controller: AnimationController = {\n stop: () => {\n if (animationController) {\n animationController.stop();\n animationController = null;\n }\n isRunning = false;\n if (onStop) {\n onStop();\n }\n console.log(\"🛑 Animation stopped\");\n },\n\n isRunning: () => isRunning,\n\n getCurrentFrame: () => currentFrame,\n\n getTotalFrames: () => frames.length,\n };\n\n try {\n // Start the animation using the session's built-in method\n animationController = session.layouts.showBitmapAnimation(\n frames,\n intervalMs,\n repeat,\n );\n isRunning = true;\n\n if (onStart) {\n onStart();\n }\n\n console.log(\n `🎬 Animation started: ${frames.length} frames at ${intervalMs}ms${repeat ? \" (repeating)\" : \"\"}`,\n );\n\n // If we have frame callbacks, we need to track timing manually\n // This is a limitation of the current SDK - we can't hook into individual frame displays\n if (onFrame) {\n let frameTracker = 0;\n\n // Call onFrame for the first frame immediately\n onFrame(frameTracker, frames.length);\n\n const frameInterval = setInterval(() => {\n if (!isRunning) {\n clearInterval(frameInterval);\n return;\n }\n\n frameTracker = (frameTracker + 1) % frames.length;\n onFrame(frameTracker, frames.length);\n\n // If not repeating and we've shown all frames, stop tracking\n if (!repeat && frameTracker === frames.length - 1) {\n clearInterval(frameInterval);\n }\n }, intervalMs);\n\n // Override stop to also clear frame tracking\n const originalStop = controller.stop;\n controller.stop = () => {\n clearInterval(frameInterval);\n originalStop();\n };\n }\n } catch (error) {\n const errorMsg = `Failed to start animation: ${error instanceof Error ? error.message : \"Unknown error\"}`;\n console.error(`❌ ${errorMsg}`);\n if (onError) {\n onError(errorMsg);\n }\n throw new Error(errorMsg);\n }\n\n return controller;\n }\n\n /**\n * Create a sequence of bitmap displays with custom timing\n *\n * @param session - MentraOS app session\n * @param sequence - Array of frame data with individual timing\n * @returns Promise that resolves when sequence completes\n *\n * @example\n * ```typescript\n * await AnimationUtils.createBitmapSequence(session, [\n * { frame: frame1Hex, duration: 1000 },\n * { frame: frame2Hex, duration: 500 },\n * { frame: frame3Hex, duration: 2000 }\n * ]);\n * ```\n */\n static async createBitmapSequence(\n session: AppSession,\n sequence: Array<{ frame: string; duration: number }>,\n ): Promise<void> {\n console.log(\n `🎭 Starting bitmap sequence: ${sequence.length} frames with custom timing`,\n );\n\n for (let i = 0; i < sequence.length; i++) {\n const { frame, duration } = sequence[i];\n\n try {\n console.log(\n `📽️ Sequence frame ${i + 1}/${sequence.length} (${duration}ms)`,\n );\n session.layouts.showBitmapView(frame);\n\n if (i < sequence.length - 1) {\n // Don't delay after the last frame\n await this.delay(duration);\n }\n } catch (error) {\n console.error(`❌ Error in sequence frame ${i + 1}:`, error);\n throw error;\n }\n }\n\n console.log(\"✅ Bitmap sequence completed\");\n }\n\n /**\n * Measure animation timing performance\n *\n * @param targetInterval - Expected interval between frames in ms\n * @param measureDuration - How long to measure in ms (default: 10 seconds)\n * @returns Promise resolving to timing performance data\n *\n * @example\n * ```typescript\n * const timing = await AnimationUtils.measureTiming(1750, 10000);\n * console.log(`Target: ${timing.targetInterval}ms, Actual: ${timing.actualInterval}ms`);\n * console.log(`Drift: ${timing.drift}ms, FPS: ${timing.fps.toFixed(1)}`);\n * ```\n */\n static async measureTiming(\n targetInterval: number,\n measureDuration: number = 10000,\n ): Promise<TimingInfo> {\n return new Promise((resolve) => {\n const timestamps: number[] = [];\n const startTime = Date.now();\n\n const measureInterval = setInterval(() => {\n timestamps.push(Date.now());\n }, targetInterval);\n\n setTimeout(() => {\n clearInterval(measureInterval);\n\n if (timestamps.length < 2) {\n resolve({\n targetInterval,\n actualInterval: targetInterval,\n drift: 0,\n fps: 1000 / targetInterval,\n });\n return;\n }\n\n // Calculate actual interval\n const intervals = [];\n for (let i = 1; i < timestamps.length; i++) {\n intervals.push(timestamps[i] - timestamps[i - 1]);\n }\n\n const actualInterval =\n intervals.reduce((a, b) => a + b, 0) / intervals.length;\n const drift = actualInterval - targetInterval;\n const fps = 1000 / actualInterval;\n\n resolve({\n targetInterval,\n actualInterval,\n drift,\n fps,\n });\n }, measureDuration);\n });\n }\n\n /**\n * Create optimized animation settings for different hardware\n *\n * @param deviceType - Target device type\n * @returns Recommended animation configuration\n *\n * @example\n * ```typescript\n * const config = AnimationUtils.getOptimizedConfig('even-realities-g1');\n * const animation = await AnimationUtils.createBitmapAnimation(\n * session, './frames', 10, config\n * );\n * ```\n */\n static getOptimizedConfig(\n deviceType: \"even-realities-g1\" | \"generic\",\n ): AnimationConfig {\n switch (deviceType) {\n case \"even-realities-g1\":\n return {\n intervalMs: 1650, // Tested optimal timing for Even Realities G1\n repeat: false,\n validateFrames: true,\n loadOptions: {\n validateFrames: true,\n skipMissingFrames: false,\n },\n };\n\n case \"generic\":\n default:\n return {\n intervalMs: 1000,\n repeat: false,\n validateFrames: true,\n loadOptions: {\n validateFrames: true,\n skipMissingFrames: false,\n },\n };\n }\n }\n\n /**\n * Preload and cache animation frames for better performance\n *\n * @param basePath - Directory containing frames\n * @param frameCount - Number of frames to preload\n * @param options - Loading options\n * @returns Promise resolving to cached frame data\n *\n * @example\n * ```typescript\n * // Preload frames\n * const cachedFrames = await AnimationUtils.preloadFrames('./animations', 10);\n *\n * // Use cached frames multiple times\n * const animation1 = AnimationUtils.createBitmapAnimationFromFrames(session, cachedFrames);\n * const animation2 = AnimationUtils.createBitmapAnimationFromFrames(session, cachedFrames);\n * ```\n */\n static async preloadFrames(\n basePath: string,\n frameCount: number,\n options: LoadFramesOptions = {},\n ): Promise<string[]> {\n console.log(`📦 Preloading ${frameCount} frames from ${basePath}...`);\n\n const frames = await BitmapUtils.loadBmpFrames(basePath, frameCount, {\n validateFrames: true,\n ...options,\n });\n\n console.log(\n `✅ Preloaded ${frames.length} frames (${frames.reduce((total: number, frame: string) => total + frame.length, 0)} total characters)`,\n );\n\n return frames;\n }\n}\n",
|
|
16
|
+
"// src/messages/cloud-to-app.ts\n\nimport {BaseMessage} from \"./base\"\nimport {CloudToAppMessageType, GlassesToCloudMessageType} from \"../message-types\"\nimport {ExtendedStreamType, StreamType} from \"../streams\"\nimport type {AppSettings, AppConfig} from \"../models\"\nimport type {DashboardMode} from \"../dashboard\"\nimport type {Capabilities} from \"../capabilities\"\nimport type {\n LocationUpdate,\n CalendarEvent,\n RtmpStreamStatus,\n PhotoResponse,\n RgbLedControlResponse,\n} from \"./glasses-to-cloud\"\nimport type {AppSession} from \"../../app/session\"\n\n//===========================================================\n// Responses\n//===========================================================\n\n/**\n * Connection acknowledgment to App\n */\nexport interface AppConnectionAck extends BaseMessage {\n type: CloudToAppMessageType.CONNECTION_ACK\n settings?: AppSettings\n mentraosSettings?: Record<string, any> // MentraOS system settings\n config?: AppConfig // App config sent from cloud\n capabilities?: Capabilities // Device capability profile\n}\n\n/**\n * Connection error to App\n */\nexport interface AppConnectionError extends BaseMessage {\n type: CloudToAppMessageType.CONNECTION_ERROR\n message: string\n code?: string\n}\n\n//===========================================================\n// Permission messages\n//===========================================================\n\n/**\n * Permission error detail for a specific stream\n */\nexport interface PermissionErrorDetail {\n /** The stream type that was rejected */\n stream: string\n /** The permission required for this stream */\n requiredPermission: string\n /** Detailed message explaining the rejection */\n message: string\n}\n\n/**\n * Permission error notification to App\n * Sent when subscriptions are rejected due to missing permissions\n */\nexport interface PermissionError extends BaseMessage {\n type: CloudToAppMessageType.PERMISSION_ERROR\n /** General error message */\n message: string\n /** Array of details for each rejected stream */\n details: PermissionErrorDetail[]\n}\n\n//===========================================================\n// Updates\n//===========================================================\n\n/**\n * App stopped notification to App\n */\nexport interface AppStopped extends BaseMessage {\n type: CloudToAppMessageType.APP_STOPPED\n reason: \"user_disabled\" | \"system_stop\" | \"error\"\n message?: string\n}\n\n/**\n * Settings update to App\n */\nexport interface SettingsUpdate extends BaseMessage {\n type: CloudToAppMessageType.SETTINGS_UPDATE\n packageName: string\n settings: AppSettings\n}\n\n/**\n * Device capabilities update to App\n * Sent when the connected glasses model changes or capabilities are updated\n */\nexport interface CapabilitiesUpdate extends BaseMessage {\n type: CloudToAppMessageType.CAPABILITIES_UPDATE\n capabilities: Capabilities | null\n modelName: string | null\n}\n\n/**\n * MentraOS settings update to App\n */\nexport interface MentraosSettingsUpdate extends BaseMessage {\n type: \"augmentos_settings_update\"\n sessionId: string\n settings: Record<string, any>\n timestamp: Date\n}\n\n//===========================================================\n// Audio-related data types\n//===========================================================\n/**\n * Transcription data\n */\nexport interface TranscriptionData extends BaseMessage {\n type: StreamType.TRANSCRIPTION\n text: string // The transcribed text\n isFinal: boolean // Whether this is a final transcription\n utteranceId?: string // Unique ID for this speech segment - interim and final for same utterance share the same ID\n transcribeLanguage?: string // Subscription language code (used for routing, e.g., \"en-US\")\n detectedLanguage?: string // Actual detected language from speech recognition (e.g., \"ja\", \"en\")\n startTime: number // Start time in milliseconds\n endTime: number // End time in milliseconds\n speakerId?: string // ID of the speaker if available (from diarization)\n duration?: number // Audio duration in milliseconds\n provider?: string // The transcription provider (e.g., \"azure\", \"soniox\")\n confidence?: number // Confidence score (0-1)\n metadata?: TranscriptionMetadata // Token-level metadata (always included)\n}\n\n/**\n * Metadata for transcription containing token-level details\n */\nexport interface TranscriptionMetadata {\n provider: \"soniox\" | \"azure\" | string\n soniox?: {\n tokens: SonioxToken[]\n }\n azure?: {\n // Azure-specific metadata can be added later\n tokens?: any[]\n }\n alibaba?: {\n // Alibaba-specific metadata can be added later\n tokens?: any[]\n }\n}\n\n/**\n * Soniox token with word-level details\n */\nexport interface SonioxToken {\n text: string\n startMs?: number\n endMs?: number\n confidence: number\n isFinal: boolean\n speaker?: string\n}\n\n/**\n * Translation data\n */\nexport interface TranslationData extends BaseMessage {\n type: StreamType.TRANSLATION\n text: string // The transcribed text\n originalText?: string // The original transcribed text before translation\n isFinal: boolean // Whether this is a final transcription\n startTime: number // Start time in milliseconds\n endTime: number // End time in milliseconds\n speakerId?: string // ID of the speaker if available\n duration?: number // Audio duration in milliseconds\n transcribeLanguage?: string // The language code of the transcribed text\n translateLanguage?: string // The language code of the translated text\n didTranslate?: boolean // Whether the text was translated\n provider?: string // The translation provider (e.g., \"azure\", \"google\")\n confidence?: number // Confidence score (0-1)\n}\n\n/**\n * Audio chunk data\n */\nexport interface AudioChunk extends BaseMessage {\n type: StreamType.AUDIO_CHUNK\n arrayBuffer: ArrayBufferLike // The audio data\n sampleRate?: number // Audio sample rate (e.g., 16000 Hz)\n}\n\n/**\n * Tool call from cloud to App\n * Represents a tool invocation with filled parameters\n */\nexport interface ToolCall {\n toolId: string // The ID of the tool that was called\n toolParameters: Record<string, string | number | boolean> // The parameters of the tool that was called\n timestamp: Date // Timestamp when the tool was called\n userId: string // ID of the user who triggered the tool call\n activeSession: AppSession | null\n}\n\n//===========================================================\n// Stream data\n//===========================================================\n\n/**\n * Stream data to App\n */\nexport interface DataStream extends BaseMessage {\n type: CloudToAppMessageType.DATA_STREAM\n streamType: ExtendedStreamType\n data: unknown // Type depends on the streamType\n}\n\n//===========================================================\n// Dashboard messages\n//===========================================================\n\n/**\n * Dashboard mode changed notification\n */\nexport interface DashboardModeChanged extends BaseMessage {\n type: CloudToAppMessageType.DASHBOARD_MODE_CHANGED\n mode: DashboardMode\n}\n\n/**\n * Dashboard always-on state changed notification\n */\nexport interface DashboardAlwaysOnChanged extends BaseMessage {\n type: CloudToAppMessageType.DASHBOARD_ALWAYS_ON_CHANGED\n enabled: boolean\n}\n\n/**\n * Standard connection error (for server compatibility)\n */\nexport interface StandardConnectionError extends BaseMessage {\n type: \"connection_error\"\n message: string\n}\n\n/**\n * Custom message for general-purpose communication (cloud to App)\n */\nexport interface CustomMessage extends BaseMessage {\n type: CloudToAppMessageType.CUSTOM_MESSAGE\n action: string // Identifies the specific action/message type\n payload: any // Custom data payload\n}\n\n/**\n * Output status for a re-stream destination\n */\nexport interface OutputStatus {\n /** The destination URL */\n url: string\n /** Friendly name if provided */\n name?: string\n /** Status of this output */\n status: \"active\" | \"error\" | \"stopped\"\n /** Error message if status is error */\n error?: string\n}\n\n/**\n * Managed RTMP stream status update\n * Sent when managed stream status changes or URLs are ready\n */\nexport interface ManagedStreamStatus extends BaseMessage {\n type: CloudToAppMessageType.MANAGED_STREAM_STATUS\n status: \"initializing\" | \"preparing\" | \"active\" | \"stopping\" | \"stopped\" | \"error\"\n hlsUrl?: string\n dashUrl?: string\n webrtcUrl?: string\n /** Cloudflare Stream player/preview URL for embedding */\n previewUrl?: string\n /** Thumbnail image URL */\n thumbnailUrl?: string\n message?: string\n streamId?: string\n /** Status of re-stream outputs if configured */\n outputs?: OutputStatus[]\n}\n\n/**\n * Stream status check response\n * Returns information about any existing streams for the user\n */\nexport interface StreamStatusCheckResponse extends BaseMessage {\n type: CloudToAppMessageType.STREAM_STATUS_CHECK_RESPONSE\n hasActiveStream: boolean\n streamInfo?: {\n type: \"managed\" | \"unmanaged\"\n streamId: string\n status: string\n createdAt: Date\n // For managed streams\n hlsUrl?: string\n dashUrl?: string\n webrtcUrl?: string\n previewUrl?: string\n thumbnailUrl?: string\n activeViewers?: number\n // For unmanaged streams\n rtmpUrl?: string\n requestingAppId?: string\n }\n}\n\n/**\n * Audio play response to App\n */\nexport interface AudioPlayResponse extends BaseMessage {\n type: CloudToAppMessageType.AUDIO_PLAY_RESPONSE\n requestId: string\n success: boolean\n error?: string // Error message (if failed)\n duration?: number // Duration of audio in milliseconds (if successful)\n}\n\n/**\n * Union type for all messages from cloud to Apps\n */\nexport type CloudToAppMessage =\n | AppConnectionAck\n | AppConnectionError\n | StandardConnectionError\n | DataStream\n | AppStopped\n | SettingsUpdate\n | CapabilitiesUpdate\n | TranscriptionData\n | TranslationData\n | AudioChunk\n | LocationUpdate\n | CalendarEvent\n | PhotoResponse\n | DashboardModeChanged\n | DashboardAlwaysOnChanged\n | CustomMessage\n | ManagedStreamStatus\n | StreamStatusCheckResponse\n | MentraosSettingsUpdate\n // New App-to-App communication response messages\n | AppMessageReceived\n | AppUserJoined\n | AppUserLeft\n | AppRoomUpdated\n | AppDirectMessageResponse\n | RtmpStreamStatus\n | PhotoResponse\n | RgbLedControlResponse\n | PermissionError\n | AudioPlayResponse\n\n//===========================================================\n// Type guards\n//===========================================================\n\nexport function isAppConnectionAck(message: CloudToAppMessage): message is AppConnectionAck {\n return message.type === CloudToAppMessageType.CONNECTION_ACK\n}\n\nexport function isAppConnectionError(message: CloudToAppMessage): message is AppConnectionError {\n return message.type === CloudToAppMessageType.CONNECTION_ERROR || (message as any).type === \"connection_error\"\n}\n\nexport function isAppStopped(message: CloudToAppMessage): message is AppStopped {\n return message.type === CloudToAppMessageType.APP_STOPPED\n}\n\nexport function isSettingsUpdate(message: CloudToAppMessage): message is SettingsUpdate {\n return message.type === CloudToAppMessageType.SETTINGS_UPDATE\n}\n\nexport function isCapabilitiesUpdate(message: CloudToAppMessage): message is CapabilitiesUpdate {\n return message.type === CloudToAppMessageType.CAPABILITIES_UPDATE\n}\n\nexport function isDataStream(message: CloudToAppMessage): message is DataStream {\n return message.type === CloudToAppMessageType.DATA_STREAM\n}\n\nexport function isAudioChunk(message: CloudToAppMessage): message is AudioChunk {\n return message.type === StreamType.AUDIO_CHUNK\n}\n\nexport function isDashboardModeChanged(message: CloudToAppMessage): message is DashboardModeChanged {\n return message.type === CloudToAppMessageType.DASHBOARD_MODE_CHANGED\n}\n\nexport function isDashboardAlwaysOnChanged(message: CloudToAppMessage): message is DashboardAlwaysOnChanged {\n return message.type === CloudToAppMessageType.DASHBOARD_ALWAYS_ON_CHANGED\n}\n\nexport function isManagedStreamStatus(message: CloudToAppMessage): message is ManagedStreamStatus {\n return message.type === CloudToAppMessageType.MANAGED_STREAM_STATUS\n}\n\nexport function isRtmpStreamStatus(message: CloudToAppMessage): message is RtmpStreamStatus {\n return message.type === GlassesToCloudMessageType.RTMP_STREAM_STATUS\n}\n\nexport function isPhotoResponse(message: CloudToAppMessage): message is PhotoResponse {\n return message.type === GlassesToCloudMessageType.PHOTO_RESPONSE\n}\n\nexport function isRgbLedControlResponse(message: CloudToAppMessage): message is RgbLedControlResponse {\n return message.type === GlassesToCloudMessageType.RGB_LED_CONTROL_RESPONSE\n}\n\nexport function isStreamStatusCheckResponse(message: CloudToAppMessage): message is StreamStatusCheckResponse {\n return message.type === CloudToAppMessageType.STREAM_STATUS_CHECK_RESPONSE\n}\n\nexport function isAudioPlayResponse(message: CloudToAppMessage): message is AudioPlayResponse {\n return message.type === CloudToAppMessageType.AUDIO_PLAY_RESPONSE\n}\n\n// New type guards for App-to-App communication\nexport function isAppMessageReceived(message: CloudToAppMessage): message is AppMessageReceived {\n return message.type === CloudToAppMessageType.APP_MESSAGE_RECEIVED\n}\n\nexport function isAppUserJoined(message: CloudToAppMessage): message is AppUserJoined {\n return message.type === CloudToAppMessageType.APP_USER_JOINED\n}\n\nexport function isAppUserLeft(message: CloudToAppMessage): message is AppUserLeft {\n return message.type === CloudToAppMessageType.APP_USER_LEFT\n}\n\n//===========================================================\n// App-to-App Communication Response Messages\n//===========================================================\n\n/**\n * Message received from another App user\n */\nexport interface AppMessageReceived extends BaseMessage {\n type: CloudToAppMessageType.APP_MESSAGE_RECEIVED\n payload: any\n messageId: string\n senderUserId: string\n senderSessionId: string\n roomId?: string\n}\n\n/**\n * Notification that a user joined the App\n */\nexport interface AppUserJoined extends BaseMessage {\n type: CloudToAppMessageType.APP_USER_JOINED\n userId: string\n sessionId: string\n joinedAt: Date\n userProfile?: any\n roomId?: string\n}\n\n/**\n * Notification that a user left the App\n */\nexport interface AppUserLeft extends BaseMessage {\n type: CloudToAppMessageType.APP_USER_LEFT\n userId: string\n sessionId: string\n leftAt: Date\n roomId?: string\n}\n\n/**\n * Room status update (members, config changes, etc.)\n */\nexport interface AppRoomUpdated extends BaseMessage {\n type: CloudToAppMessageType.APP_ROOM_UPDATED\n roomId: string\n updateType: \"user_joined\" | \"user_left\" | \"config_changed\" | \"room_closed\"\n roomData: {\n memberCount: number\n maxUsers?: number\n isPrivate?: boolean\n metadata?: any\n }\n}\n\n/**\n * Response to a direct message attempt\n */\nexport interface AppDirectMessageResponse extends BaseMessage {\n type: CloudToAppMessageType.APP_DIRECT_MESSAGE_RESPONSE\n messageId: string\n success: boolean\n error?: string\n targetUserId: string\n}\n\n//===========================================================\n// Cloud-to-Sdk Communication Response Messages\n//===========================================================\n\n/**\n * Permission data structures for permission fetch responses\n */\nexport interface Permission {\n type: string // or a union/enum if you want stricter typing\n description: string\n _id: string\n}\n\n/**\n * Package permissions response structure\n */\nexport interface PackagePermissions {\n packageName: string\n permissions: Permission[]\n}\n",
|
|
17
|
+
"// src/enums.ts\n\n/**\n * Types of Third-Party Applications (Apps)\n */\nexport enum AppType {\n SYSTEM_DASHBOARD = \"system_dashboard\", // Special UI placement, system functionality\n BACKGROUND = \"background\", // Can temporarily take control of display\n STANDARD = \"standard\", // Regular App (default) only one standard app can run at a time. starting a standard App will close any other standard App that is running.\n}\n\n/**\n * Types of layouts for displaying content\n */\nexport enum LayoutType {\n TEXT_WALL = \"text_wall\",\n DOUBLE_TEXT_WALL = \"double_text_wall\",\n DASHBOARD_CARD = \"dashboard_card\",\n REFERENCE_CARD = \"reference_card\",\n BITMAP_VIEW = \"bitmap_view\",\n BITMAP_ANIMATION = \"bitmap_animation\",\n CLEAR_VIEW = \"clear_view\",\n}\n\n/**\n * Types of views for displaying content\n */\nexport enum ViewType {\n DASHBOARD = \"dashboard\", // Regular dashboard (main/expanded)\n // ALWAYS_ON = \"always_on\", // Persistent overlay dashboard\n MAIN = \"main\", // Regular app content\n}\n\n// Types for AppSettings\nexport enum AppSettingType {\n TOGGLE = \"toggle\",\n TEXT = \"text\",\n SELECT = \"select\",\n SLIDER = \"slider\",\n GROUP = \"group\",\n TEXT_NO_SAVE_BUTTON = \"text_no_save_button\",\n SELECT_WITH_SEARCH = \"select_with_search\",\n MULTISELECT = \"multiselect\",\n TITLE_VALUE = \"titleValue\",\n NUMERIC_INPUT = \"numeric_input\",\n TIME_PICKER = \"time_picker\",\n}\n// | { type: \"toggle\"; key: string; label: string; defaultValue: boolean }\n// | { type: \"text\"; key: string; label: string; defaultValue?: string }\n// | { type: \"select\"; key: string; label: string; options: { label: string; value: string }[]; defaultValue?: string };\n\n/**\n * Types of hardware components that apps can require\n */\nexport enum HardwareType {\n CAMERA = \"CAMERA\",\n DISPLAY = \"DISPLAY\",\n MICROPHONE = \"MICROPHONE\",\n SPEAKER = \"SPEAKER\",\n IMU = \"IMU\",\n BUTTON = \"BUTTON\",\n LIGHT = \"LIGHT\",\n WIFI = \"WIFI\",\n}\n\n/**\n * Levels of hardware requirements\n */\nexport enum HardwareRequirementLevel {\n REQUIRED = \"REQUIRED\", // App cannot function without this hardware\n OPTIONAL = \"OPTIONAL\", // App has enhanced features with this hardware\n}\n",
|
|
18
|
+
"// @mentra/sdk\n// packages/sdk/types/src/models.ts - Core models\n\nimport {\n AppSettingType,\n AppType,\n HardwareType,\n HardwareRequirementLevel,\n} from \"./enums\";\n\n// Tool parameter type definition\nexport interface ToolParameterSchema {\n type: \"string\" | \"number\" | \"boolean\";\n description: string;\n enum?: string[];\n required?: boolean;\n}\n\n// Tool schema definition for Apps\nexport interface ToolSchema {\n id: string;\n description: string;\n activationPhrases?: string[];\n parameters?: Record<string, ToolParameterSchema>;\n}\n\n/**\n * Developer profile information\n */\nexport interface DeveloperProfile {\n company?: string;\n website?: string;\n contactEmail?: string;\n description?: string;\n logo?: string;\n}\n\n// Define PermissionType enum with legacy support\nexport enum PermissionType {\n MICROPHONE = \"MICROPHONE\",\n LOCATION = \"LOCATION\",\n BACKGROUND_LOCATION = \"BACKGROUND_LOCATION\",\n CALENDAR = \"CALENDAR\",\n CAMERA = \"CAMERA\",\n\n // Legacy notification permission (backward compatibility)\n NOTIFICATIONS = \"NOTIFICATIONS\",\n\n // New granular notification permissions\n READ_NOTIFICATIONS = \"READ_NOTIFICATIONS\",\n POST_NOTIFICATIONS = \"POST_NOTIFICATIONS\",\n\n ALL = \"ALL\",\n}\n\n// Legacy permission mapping for backward compatibility\nexport const LEGACY_PERMISSION_MAP = new Map<PermissionType, PermissionType[]>([\n [PermissionType.NOTIFICATIONS, [PermissionType.READ_NOTIFICATIONS]],\n]);\n\n// Permission interface\nexport interface Permission {\n type: PermissionType;\n description?: string;\n}\n\n/**\n * Hardware requirement for an app\n */\nexport interface HardwareRequirement {\n type: HardwareType;\n level: HardwareRequirementLevel;\n description?: string; // Why this hardware is needed\n}\n\n/**\n * Base interface for applications\n */\nexport interface AppI {\n packageName: string;\n name: string;\n publicUrl: string; // Base URL of the app server\n isSystemApp?: boolean; // Is this a system app?\n uninstallable?: boolean; // Can the app be uninstalled?\n\n webviewURL?: string; // URL for phone UI\n logoURL: string;\n appType: AppType; // Type of app\n appStoreId?: string; // Which app store registered this app\n\n /**\n * @deprecated Use organizationId instead. Will be removed after migration.\n */\n developerId?: string; // ID of the developer who created the app\n organizationId?: any; // ID of the organization that owns this app\n\n // Auth\n hashedEndpointSecret?: string;\n hashedApiKey?: string;\n\n // App details\n permissions?: Permission[];\n description?: string;\n version?: string;\n settings?: AppSettings;\n tools?: ToolSchema[];\n\n /**\n * Hardware requirements for the app\n * If not specified, app is assumed to work with any hardware\n */\n hardwareRequirements?: HardwareRequirement[];\n\n isPublic?: boolean;\n appStoreStatus?: \"DEVELOPMENT\" | \"SUBMITTED\" | \"REJECTED\" | \"PUBLISHED\";\n}\n\n/**\n * Base interface for all app settings\n */\nexport interface BaseAppSetting {\n key: string;\n label: string;\n value?: any; // User's selected value\n defaultValue?: any; // System default\n}\n\n/**\n * Setting types for applications\n */\nexport type AppSetting =\n | (BaseAppSetting & {\n type: AppSettingType.TOGGLE;\n defaultValue: boolean;\n value?: boolean;\n })\n | (BaseAppSetting & {\n type: AppSettingType.TEXT;\n defaultValue?: string;\n value?: string;\n })\n | (BaseAppSetting & {\n type: AppSettingType.TEXT_NO_SAVE_BUTTON;\n defaultValue?: string;\n value?: string;\n maxLines?: number;\n })\n | (BaseAppSetting & {\n type: AppSettingType.SELECT;\n options: { label: string; value: any }[];\n defaultValue?: any;\n value?: any;\n })\n | (BaseAppSetting & {\n type: AppSettingType.SELECT_WITH_SEARCH;\n options: { label: string; value: any }[];\n defaultValue?: any;\n value?: any;\n })\n | (BaseAppSetting & {\n type: AppSettingType.MULTISELECT;\n options: { label: string; value: any }[];\n defaultValue?: any[];\n value?: any[];\n })\n | (BaseAppSetting & {\n type: AppSettingType.SLIDER;\n min: number;\n max: number;\n defaultValue: number;\n value?: number;\n })\n | (BaseAppSetting & {\n type: AppSettingType.NUMERIC_INPUT;\n min?: number;\n max?: number;\n step?: number;\n placeholder?: string;\n defaultValue?: number;\n value?: number;\n })\n | (BaseAppSetting & {\n type: AppSettingType.TIME_PICKER;\n showSeconds?: boolean;\n defaultValue?: number; // Total seconds\n value?: number; // Total seconds\n })\n | (BaseAppSetting & {\n type: AppSettingType.GROUP;\n title: string;\n })\n | (BaseAppSetting & {\n type: AppSettingType.TITLE_VALUE;\n label: string;\n value: any;\n key?: never; // TITLE_VALUE settings don't need keys since they're display-only\n });\n\nexport type AppSettings = AppSetting[];\n\n/**\n * App configuration file structure\n * Represents the schema in app_config.json\n */\nexport interface AppConfig {\n name: string;\n description: string;\n version: string;\n settings: AppSetting[];\n tools: ToolSchema[];\n}\n\n/**\n * Validate a App configuration object\n * @param config Object to validate\n * @returns True if the config is valid\n */\nexport function validateAppConfig(config: any): config is AppConfig {\n if (!config || typeof config !== \"object\") return false;\n\n // Check required string properties\n if (\n typeof config.name !== \"string\" ||\n typeof config.description !== \"string\" ||\n typeof config.version !== \"string\"\n ) {\n return false;\n }\n\n // Check settings array\n if (!Array.isArray(config.settings)) return false;\n\n // Validate each setting\n return config.settings.every((setting: any) => {\n // Group settings just need a title\n if (setting.type === \"group\") {\n return typeof setting.title === \"string\";\n }\n\n // TITLE_VALUE settings just need label and value\n if (setting.type === \"titleValue\") {\n return typeof setting.label === \"string\" && \"value\" in setting;\n }\n\n // Regular settings need key and label\n if (typeof setting.key !== \"string\" || typeof setting.label !== \"string\") {\n return false;\n }\n\n // Type-specific validation\n switch (setting.type) {\n case AppSettingType.TOGGLE:\n return typeof setting.defaultValue === \"boolean\";\n\n case AppSettingType.TEXT:\n case AppSettingType.TEXT_NO_SAVE_BUTTON:\n return (\n setting.defaultValue === undefined ||\n typeof setting.defaultValue === \"string\"\n );\n\n case AppSettingType.SELECT:\n case AppSettingType.SELECT_WITH_SEARCH:\n return (\n Array.isArray(setting.options) &&\n setting.options.every(\n (opt: any) => typeof opt.label === \"string\" && \"value\" in opt,\n )\n );\n\n case AppSettingType.MULTISELECT:\n return (\n Array.isArray(setting.options) &&\n setting.options.every(\n (opt: any) => typeof opt.label === \"string\" && \"value\" in opt,\n ) &&\n (setting.defaultValue === undefined ||\n Array.isArray(setting.defaultValue))\n );\n\n case AppSettingType.SLIDER:\n return (\n typeof setting.defaultValue === \"number\" &&\n typeof setting.min === \"number\" &&\n typeof setting.max === \"number\" &&\n setting.min <= setting.max\n );\n\n case AppSettingType.NUMERIC_INPUT:\n return (\n (setting.defaultValue === undefined ||\n typeof setting.defaultValue === \"number\") &&\n (setting.min === undefined || typeof setting.min === \"number\") &&\n (setting.max === undefined || typeof setting.max === \"number\") &&\n (setting.step === undefined || typeof setting.step === \"number\") &&\n (setting.placeholder === undefined ||\n typeof setting.placeholder === \"string\")\n );\n\n case AppSettingType.TIME_PICKER:\n return (\n (setting.defaultValue === undefined ||\n typeof setting.defaultValue === \"number\") &&\n (setting.showSeconds === undefined ||\n typeof setting.showSeconds === \"boolean\")\n );\n\n case AppSettingType.GROUP:\n return typeof setting.title === \"string\";\n\n case AppSettingType.TITLE_VALUE:\n return typeof setting.label === \"string\" && \"value\" in setting;\n\n default:\n return false;\n }\n });\n}\n\n/**\n * Transcript segment for speech processing\n */\nexport interface TranscriptSegment {\n speakerId?: string;\n resultId: string;\n text: string;\n timestamp: Date;\n isFinal: boolean;\n}\n\n/**\n * Complete transcript\n */\nexport interface TranscriptI {\n segments: TranscriptSegment[];\n languageSegments?: Map<string, TranscriptSegment[]>; // Language-indexed map for multi-language support\n}\n",
|
|
19
|
+
"// src/webhook.ts\n\n/**\n * Types of webhook requests that can be sent to Apps\n */\nexport enum WebhookRequestType {\n /** Request to start a App session */\n SESSION_REQUEST = \"session_request\",\n\n /** Request to stop a App session */\n STOP_REQUEST = \"stop_request\",\n}\n\n/**\n * Base interface for all webhook requests\n */\nexport interface BaseWebhookRequest {\n /** Type of webhook request */\n type: WebhookRequestType;\n\n /** Session ID for the request */\n sessionId: string;\n\n /** User ID associated with the session */\n userId: string;\n\n /** Timestamp of the request */\n timestamp: string;\n}\n\n/**\n * Session request webhook\n *\n * Sent to a App when a user starts the App\n */\nexport interface SessionWebhookRequest extends BaseWebhookRequest {\n type: WebhookRequestType.SESSION_REQUEST;\n mentraOSWebsocketUrl?: string;\n augmentOSWebsocketUrl?: string;\n}\n\n/**\n * Stop request webhook\n *\n * Sent to a App when a user or the system stops the App\n */\nexport interface StopWebhookRequest extends BaseWebhookRequest {\n type: WebhookRequestType.STOP_REQUEST;\n reason: \"user_disabled\" | \"system_stop\" | \"error\";\n}\n\n/**\n * Union type for all webhook requests\n */\nexport type WebhookRequest = SessionWebhookRequest | StopWebhookRequest;\n\n/**\n * Response to a webhook request\n */\nexport interface WebhookResponse {\n status: \"success\" | \"error\";\n message?: string;\n}\n\n/**\n * Type guard to check if a webhook request is a session request\n */\nexport function isSessionWebhookRequest(\n request: WebhookRequest,\n): request is SessionWebhookRequest {\n return request.type === WebhookRequestType.SESSION_REQUEST;\n}\n\n/**\n * Type guard to check if a webhook request is a stop request\n */\nexport function isStopWebhookRequest(\n request: WebhookRequest,\n): request is StopWebhookRequest {\n return request.type === WebhookRequestType.STOP_REQUEST;\n}\n",
|
|
20
|
+
"/**\n * 🚀 App Server Module\n *\n * Creates and manages a server for Apps in the MentraOS ecosystem.\n * Handles webhook endpoints, session management, and cleanup.\n */\nimport express, { type Express } from \"express\";\nimport path from \"path\";\nimport fs from \"fs\";\nimport { AppSession } from \"../session/index\";\nimport { createAuthMiddleware } from \"../webview\";\nimport { newSDKUpdate } from \"../../constants/log-messages/updates\";\n\nimport {\n WebhookRequest,\n WebhookResponse,\n SessionWebhookRequest,\n StopWebhookRequest,\n ToolCall,\n WebhookRequestType,\n} from \"../../types\";\n\nimport { Logger } from \"pino\";\nimport { logger as rootLogger } from \"../../logging/logger\";\nimport axios from \"axios\";\n\nexport const GIVE_APP_CONTROL_OF_TOOL_RESPONSE: string =\n \"GIVE_APP_CONTROL_OF_TOOL_RESPONSE\";\n\n/**\n * 🔧 Configuration options for App Server\n *\n * @example\n * ```typescript\n * const config: AppServerConfig = {\n * packageName: 'org.example.myapp',\n * apiKey: 'your_api_key',\n * port: 7010,\n * publicDir: './public'\n * };\n * ```\n */\nexport interface AppServerConfig {\n /** 📦 Unique identifier for your App (e.g., 'org.company.appname') must match what you specified at https://console.mentra.glass */\n packageName: string;\n /** 🔑 API key for authentication with MentraOS Cloud */\n apiKey: string;\n /** 🌐 Port number for the server (default: 7010) */\n port?: number;\n\n /** Cloud API URL (default: 'api.mentra.glass') */\n cloudApiUrl?: string;\n\n /** 🛣️ [DEPRECATED] do not set: The SDK will automatically expose an endpoint at '/webhook' */\n webhookPath?: string;\n /**\n * 📂 Directory for serving static files (e.g., images, logos)\n * Set to false to disable static file serving\n */\n publicDir?: string | false;\n\n /** ❤️ Enable health check endpoint at /health (default: true) */\n healthCheck?: boolean;\n /**\n * 🔐 Secret key used to sign session cookies\n * This must be a strong, unique secret\n */\n cookieSecret?: string;\n /** App instructions string shown to the user */\n appInstructions?: string;\n}\n\n/**\n * 🎯 App Server Implementation\n *\n * Base class for creating App servers. Handles:\n * - 🔄 Session lifecycle management\n * - 📡 Webhook endpoints for MentraOS Cloud\n * - 📂 Static file serving\n * - ❤️ Health checks\n * - 🧹 Cleanup on shutdown\n *\n * @example\n * ```typescript\n * class MyAppServer extends AppServer {\n * protected async onSession(session: AppSession, sessionId: string, userId: string) {\n * // Handle new user sessions here\n * session.events.onTranscription((data) => {\n * session.layouts.showTextWall(data.text);\n * });\n * }\n * }\n *\n * const server = new MyAppServer({\n * packageName: 'org.example.myapp',\n * apiKey: 'your_api_key',\n * publicDir: \"/public\",\n * });\n *\n * await server.start();\n * ```\n */\nexport class AppServer {\n /** Express app instance */\n private app: Express;\n /** Map of active user sessions by sessionId */\n private activeSessions = new Map<string, AppSession>();\n /** Map of active user sessions by userId */\n private activeSessionsByUserId = new Map<string, AppSession>();\n /** Array of cleanup handlers to run on shutdown */\n private cleanupHandlers: Array<() => void> = [];\n /** App instructions string shown to the user */\n private appInstructions: string | null = null;\n\n public readonly logger: Logger;\n\n constructor(private config: AppServerConfig) {\n // Set defaults and merge with provided config\n this.config = {\n port: 7010,\n webhookPath: \"/webhook\",\n publicDir: false,\n healthCheck: true,\n ...config,\n };\n\n this.logger = rootLogger.child({\n app: this.config.packageName,\n packageName: this.config.packageName,\n service: \"app-server\",\n });\n\n // Initialize Express app\n this.app = express();\n this.app.use(express.json());\n\n const cookieParser = require(\"cookie-parser\");\n this.app.use(\n cookieParser(\n this.config.cookieSecret ||\n `AOS_${this.config.packageName}_${this.config.apiKey.substring(\n 0,\n 8,\n )}`,\n ),\n );\n\n // Apply authentication middleware\n this.app.use(\n createAuthMiddleware({\n apiKey: this.config.apiKey,\n packageName: this.config.packageName,\n getAppSessionForUser: (userId: string) => {\n return this.activeSessionsByUserId.get(userId) || null;\n },\n cookieSecret:\n this.config.cookieSecret ||\n `AOS_${this.config.packageName}_${this.config.apiKey.substring(\n 0,\n 8,\n )}`,\n }) as any,\n );\n\n this.appInstructions = (config as any).appInstructions || null;\n\n // Setup server features\n this.setupWebhook();\n this.setupSettingsEndpoint();\n this.setupHealthCheck();\n this.setupToolCallEndpoint();\n this.setupPhotoUploadEndpoint();\n this.setupMentraAuthRedirect();\n this.setupPublicDir();\n this.setupShutdown();\n }\n\n // Expose Express app for custom routes.\n // This is useful for adding custom API routes or middleware.\n public getExpressApp(): Express {\n return this.app;\n }\n\n /**\n * 👥 Session Handler\n * Override this method to handle new App sessions.\n * This is where you implement your app's core functionality.\n *\n * @param session - App session instance for the user\n * @param sessionId - Unique identifier for this session\n * @param userId - User's identifier\n */\n protected async onSession(\n session: AppSession,\n sessionId: string,\n userId: string,\n ): Promise<void> {\n this.logger.info(\n `🚀 Starting new session handling for session ${sessionId} and user ${userId}`,\n );\n // Core session handling logic (onboarding removed)\n this.logger.info(\n `✅ Session handling completed for session ${sessionId} and user ${userId}`,\n );\n }\n\n /**\n * 👥 Stop Handler\n * Override this method to handle stop requests.\n * This is where you can clean up resources when a session is stopped.\n *\n * @param sessionId - Unique identifier for this session\n * @param userId - User's identifier\n * @param reason - Reason for stopping\n */\n protected async onStop(\n sessionId: string,\n userId: string,\n reason: string,\n ): Promise<void> {\n this.logger.debug(\n `Session ${sessionId} stopped for user ${userId}. Reason: ${reason}`,\n );\n\n // Default implementation: close the session if it exists\n const session = this.activeSessions.get(sessionId);\n if (session) {\n session.disconnect();\n this.activeSessions.delete(sessionId);\n this.activeSessionsByUserId.delete(userId);\n }\n }\n\n /**\n * 🛠️ Tool Call Handler\n * Override this method to handle tool calls from MentraOS Cloud.\n * This is where you implement your app's tool functionality.\n *\n * @param toolCall - The tool call request containing tool details and parameters\n * @returns Optional string response that will be sent back to MentraOS Cloud\n */\n protected async onToolCall(toolCall: ToolCall): Promise<string | undefined> {\n this.logger.debug(`Tool call received: ${toolCall.toolId}`);\n this.logger.debug(`Parameters: ${JSON.stringify(toolCall.toolParameters)}`);\n return undefined;\n }\n\n /**\n * 🚀 Start the Server\n * Starts listening for incoming connections and webhook calls.\n *\n * @returns Promise that resolves when server is ready\n */\n public start(): Promise<void> {\n return new Promise((resolve) => {\n this.app.listen(this.config.port, async () => {\n this.logger.info(\n `🎯 App server running at http://localhost:${this.config.port}`,\n );\n if (this.config.publicDir) {\n this.logger.info(\n `📂 Serving static files from ${this.config.publicDir}`,\n );\n }\n\n // 🔑 Grab SDK version\n try {\n // Look for the actual installed @mentra/sdk package.json in node_modules\n const sdkPkgPath = path.resolve(\n process.cwd(),\n \"node_modules/@mentra/sdk/package.json\",\n );\n\n let currentVersion = \"unknown\";\n\n if (fs.existsSync(sdkPkgPath)) {\n const sdkPkg = JSON.parse(fs.readFileSync(sdkPkgPath, \"utf-8\"));\n\n // Get the actual installed version\n currentVersion = sdkPkg.version || \"not-found\"; // located in the node module\n } else {\n this.logger.debug(\n { sdkPkgPath },\n \"No @mentra/sdk package.json found at path\",\n );\n }\n\n // this.logger.debug(`Developer is using SDK version: ${currentVersion}`);\n\n // Fetch latest SDK version from the API endpoint\n let latest: string | null = null;\n try {\n const cloudHost = \"api.mentra.glass\";\n const response = await axios.get(\n `https://${cloudHost}/api/sdk/version`,\n { timeout: 3000 }, // 3 second timeout\n );\n if (response.data && response.data.success && response.data.data) {\n latest = response.data.data.latest;\n }\n } catch {\n this.logger.debug(\n \"Failed to fetch latest SDK version - skipping version check (offline or API unavailable)\",\n );\n }\n\n if (currentVersion === \"not-found\") {\n this.logger.warn(\n `⚠️ @mentra/sdk not found in your project dependencies. Please install it with: npm install @mentra/sdk`,\n );\n } else if (latest && latest !== currentVersion) {\n this.logger.warn(newSDKUpdate(latest));\n }\n } catch (err) {\n this.logger.error(err, \"Version check failed\");\n }\n\n resolve();\n });\n });\n }\n\n /**\n * 🛑 Stop the Server\n * Gracefully shuts down the server and cleans up all sessions.\n */\n public stop(): void {\n this.logger.info(\"\\n🛑 Shutting down...\");\n this.cleanup();\n process.exit(0);\n }\n\n /**\n * 🔐 Generate a App token for a user\n * This should be called when handling a session webhook request.\n *\n * @param userId - User identifier\n * @param sessionId - Session identifier\n * @param secretKey - Secret key for signing the token\n * @returns JWT token string\n */\n protected generateToken(\n userId: string,\n sessionId: string,\n secretKey: string,\n ): string {\n const { createToken } = require(\"../token/utils\");\n return createToken(\n {\n userId,\n packageName: this.config.packageName,\n sessionId,\n },\n { secretKey },\n );\n }\n\n /**\n * 🧹 Add Cleanup Handler\n * Register a function to be called during server shutdown.\n *\n * @param handler - Function to call during cleanup\n */\n protected addCleanupHandler(handler: () => void): void {\n this.cleanupHandlers.push(handler);\n }\n\n /**\n * 🎯 Setup Webhook Endpoint\n * Creates the webhook endpoint that MentraOS Cloud calls to start new sessions.\n */\n private setupWebhook(): void {\n if (!this.config.webhookPath) {\n this.logger.error(\"❌ Webhook path not set\");\n throw new Error(\"Webhook path not set\");\n }\n\n this.app.post(this.config.webhookPath, async (req, res) => {\n try {\n const webhookRequest = req.body as WebhookRequest;\n\n // Handle session request\n if (webhookRequest.type === WebhookRequestType.SESSION_REQUEST) {\n await this.handleSessionRequest(webhookRequest, res);\n }\n // Handle stop request\n else if (webhookRequest.type === WebhookRequestType.STOP_REQUEST) {\n await this.handleStopRequest(webhookRequest, res);\n }\n // Unknown webhook type\n else {\n this.logger.error(\"❌ Unknown webhook request type\");\n res.status(400).json({\n status: \"error\",\n message: \"Unknown webhook request type\",\n } as WebhookResponse);\n }\n } catch (error) {\n this.logger.error(\n error,\n \"❌ Error handling webhook: \" + (error as Error).message,\n );\n res.status(500).json({\n status: \"error\",\n message: \"Error handling webhook: \" + (error as Error).message,\n } as WebhookResponse);\n }\n });\n }\n\n /**\n * 🛠️ Setup Tool Call Endpoint\n * Creates a /tool endpoint for handling tool calls from MentraOS Cloud.\n */\n private setupToolCallEndpoint(): void {\n this.app.post(\"/tool\", async (req, res) => {\n try {\n const toolCall = req.body as ToolCall;\n if (this.activeSessionsByUserId.has(toolCall.userId)) {\n toolCall.activeSession =\n this.activeSessionsByUserId.get(toolCall.userId) || null;\n } else {\n toolCall.activeSession = null;\n }\n this.logger.info(\n { body: req.body },\n `🔧 Received tool call: ${toolCall.toolId}`,\n );\n // Call the onToolCall handler and get the response\n const response = await this.onToolCall(toolCall);\n\n // Send back the response if one was provided\n if (response !== undefined) {\n res.json({ status: \"success\", reply: response });\n } else {\n res.json({ status: \"success\", reply: null });\n }\n } catch (error) {\n this.logger.error(error, \"❌ Error handling tool call:\");\n res.status(500).json({\n status: \"error\",\n message:\n error instanceof Error\n ? error.message\n : \"Unknown error occurred calling tool\",\n });\n }\n });\n this.app.get(\"/tool\", async (req, res) => {\n res.json({ status: \"success\", reply: \"Hello, world!\" });\n });\n }\n\n /**\n * Handle a session request webhook\n */\n private async handleSessionRequest(\n request: SessionWebhookRequest,\n res: express.Response,\n ): Promise<void> {\n const { sessionId, userId, mentraOSWebsocketUrl, augmentOSWebsocketUrl } =\n request;\n this.logger.info(\n { userId },\n `🗣️ Received session request for user ${userId}, session ${sessionId}\\n\\n`,\n );\n\n // Create new App session\n const session = new AppSession({\n packageName: this.config.packageName,\n apiKey: this.config.apiKey,\n mentraOSWebsocketUrl: mentraOSWebsocketUrl || augmentOSWebsocketUrl, // The websocket URL for the specific MentraOS server that this userSession is connecting to.\n appServer: this,\n userId,\n });\n\n // Setup session event handlers\n const cleanupDisconnect = session.events.onDisconnected((info) => {\n // Handle different disconnect info formats (string or object)\n if (typeof info === \"string\") {\n this.logger.info(`👋 Session ${sessionId} disconnected: ${info}`);\n } else {\n // It's an object with detailed disconnect information\n this.logger.info(\n `👋 Session ${sessionId} disconnected: ${info.message} (code: ${info.code}, reason: ${info.reason})`,\n );\n\n // Check if this is a user session end event\n // This happens when the UserSession is disposed after 1 minute grace period\n if (info.sessionEnded === true) {\n this.logger.info(\n `🛑 User session ended for session ${sessionId}, calling onStop`,\n );\n\n // Call onStop with session end reason\n // This allows apps to clean up resources when the user's session ends\n this.onStop(sessionId, userId, \"User session ended\").catch(\n (error) => {\n this.logger.error(\n error,\n `❌ Error in onStop handler for session end:`,\n );\n },\n );\n }\n // Check if this is a permanent disconnection after exhausted reconnection attempts\n else if (info.permanent === true) {\n this.logger.info(\n `🛑 Permanent disconnection detected for session ${sessionId}, calling onStop`,\n );\n\n // Keep track of the original session before removal\n // const session = this.activeSessions.get(sessionId);\n const _session = this.activeSessions.get(sessionId);\n\n // Call onStop with a reconnection failure reason\n this.onStop(\n sessionId,\n userId,\n `Connection permanently lost: ${info.reason}`,\n ).catch((error) => {\n this.logger.error(\n error,\n `❌ Error in onStop handler for permanent disconnection:`,\n );\n });\n }\n }\n\n // Remove the session from active sessions in all cases\n this.activeSessions.delete(sessionId);\n this.activeSessionsByUserId.delete(userId);\n });\n\n const cleanupError = session.events.onError((error) => {\n this.logger.error(error, `❌ [Session ${sessionId}] Error:`);\n });\n\n // Start the session\n try {\n await session.connect(sessionId);\n this.activeSessions.set(sessionId, session);\n this.activeSessionsByUserId.set(userId, session);\n await this.onSession(session, sessionId, userId);\n res.status(200).json({ status: \"success\" } as WebhookResponse);\n } catch (error) {\n this.logger.error(error, \"❌ Failed to connect:\");\n cleanupDisconnect();\n cleanupError();\n res.status(500).json({\n status: \"error\",\n message: \"Failed to connect\",\n } as WebhookResponse);\n }\n }\n\n /**\n * Handle a stop request webhook\n */\n private async handleStopRequest(\n request: StopWebhookRequest,\n res: express.Response,\n ): Promise<void> {\n const { sessionId, userId, reason } = request;\n this.logger.info(\n `\\n\\n🛑 Received stop request for user ${userId}, session ${sessionId}, reason: ${reason}\\n\\n`,\n );\n\n try {\n await this.onStop(sessionId, userId, reason);\n res.status(200).json({ status: \"success\" } as WebhookResponse);\n } catch (error) {\n this.logger.error(error, \"❌ Error handling stop request:\");\n res.status(500).json({\n status: \"error\",\n message: \"Failed to process stop request\",\n } as WebhookResponse);\n }\n }\n\n /**\n * ❤️ Setup Health Check Endpoint\n * Creates a /health endpoint for monitoring server status.\n */\n private setupHealthCheck(): void {\n if (this.config.healthCheck) {\n this.app.get(\"/health\", (req, res) => {\n res.json({\n status: \"healthy\",\n app: this.config.packageName,\n activeSessions: this.activeSessions.size,\n });\n });\n }\n }\n\n /**\n * ⚙️ Setup Settings Endpoint\n * Creates a /settings endpoint that the MentraOS Cloud can use to update settings.\n */\n private setupSettingsEndpoint(): void {\n this.app.post(\"/settings\", async (req, res) => {\n try {\n const { userIdForSettings, settings } = req.body;\n\n if (!userIdForSettings || !Array.isArray(settings)) {\n return res.status(400).json({\n status: \"error\",\n message: \"Missing userId or settings array in request body\",\n });\n }\n\n this.logger.info(\n `⚙️ Received settings update for user ${userIdForSettings}`,\n );\n\n // Find all active sessions for this user\n const userSessions: AppSession[] = [];\n\n // Look through all active sessions\n this.activeSessions.forEach((session, _sessionId) => {\n // Check if the session has this userId (not directly accessible)\n // We're relying on the webhook handler to have already verified this\n if (session.userId === userIdForSettings) {\n userSessions.push(session);\n }\n });\n\n if (userSessions.length === 0) {\n this.logger.warn(\n `⚠️ No active sessions found for user ${userIdForSettings}`,\n );\n } else {\n this.logger.info(\n `🔄 Updating settings for ${userSessions.length} active sessions`,\n );\n }\n\n // Update settings for all of the user's sessions\n for (const session of userSessions) {\n session.updateSettingsForTesting(settings);\n }\n\n // Allow subclasses to handle settings updates if they implement the method\n if (typeof (this as any).onSettingsUpdate === \"function\") {\n await (this as any).onSettingsUpdate(userIdForSettings, settings);\n }\n\n res.json({\n status: \"success\",\n message: \"Settings updated successfully\",\n sessionsUpdated: userSessions.length,\n });\n } catch (error) {\n this.logger.error(error, \"❌ Error handling settings update:\");\n res.status(500).json({\n status: \"error\",\n message: \"Internal server error processing settings update\",\n });\n }\n });\n }\n\n /**\n * 📂 Setup Static File Serving\n * Configures Express to serve static files from the specified directory.\n */\n private setupPublicDir(): void {\n if (this.config.publicDir) {\n const publicPath = path.resolve(this.config.publicDir);\n this.app.use(express.static(publicPath));\n this.logger.info(`📂 Serving static files from ${publicPath}`);\n }\n }\n\n /**\n * 🛑 Setup Shutdown Handlers\n * Registers process signal handlers for graceful shutdown.\n */\n private setupShutdown(): void {\n process.on(\"SIGTERM\", () => this.stop());\n process.on(\"SIGINT\", () => this.stop());\n }\n\n /**\n * 🧹 Cleanup\n * Closes all active sessions and runs cleanup handlers.\n */\n private cleanup(): void {\n // Close all active sessions\n for (const [sessionId, session] of this.activeSessions) {\n this.logger.info(`👋 Closing session ${sessionId}`);\n session.disconnect();\n }\n this.activeSessions.clear();\n this.activeSessionsByUserId.clear();\n\n // Run cleanup handlers\n this.cleanupHandlers.forEach((handler) => handler());\n }\n\n /**\n * 🎯 Setup Photo Upload Endpoint\n * Creates a /photo-upload endpoint for receiving photos directly from ASG glasses\n */\n private setupPhotoUploadEndpoint(): void {\n const multer = require(\"multer\");\n\n // Configure multer for handling multipart form data\n const upload = multer({\n storage: multer.memoryStorage(),\n limits: {\n fileSize: 10 * 1024 * 1024, // 10MB limit\n },\n fileFilter: (req: any, file: any, cb: any) => {\n // Accept image files only\n if (file.mimetype && file.mimetype.startsWith(\"image/\")) {\n cb(null, true);\n } else {\n cb(new Error(\"Only image files are allowed\"), false);\n }\n },\n });\n\n this.app.post(\n \"/photo-upload\",\n upload.single(\"photo\"),\n async (req: any, res: any) => {\n try {\n const { requestId, type, success, errorCode, errorMessage } =\n req.body;\n const photoFile = req.file;\n\n console.log(\"Received photo response: \", req.body);\n\n this.logger.info(\n { requestId, type, success, errorCode },\n `📸 Received photo response: ${requestId} (type: ${type})`,\n );\n\n if (!requestId) {\n this.logger.error(\"No requestId in photo response\");\n return res.status(400).json({\n success: false,\n error: \"No requestId provided\",\n });\n }\n\n // Find the corresponding session that made this photo request\n const session = this.findSessionByPhotoRequestId(requestId);\n if (!session) {\n this.logger.warn(\n { requestId },\n \"No active session found for photo request\",\n );\n return res.status(404).json({\n success: false,\n error: \"No active session found for this photo request\",\n });\n }\n\n // Handle error response (no photo file, but has error info)\n if (type === \"photo_error\" || success === false) {\n // Create error response object\n const errorResponse = {\n requestId,\n success: false as const,\n error: {\n code: errorCode || \"UNKNOWN_ERROR\",\n message: errorMessage || \"Unknown error occurred\",\n },\n };\n\n // Deliver error to the session (logging happens in camera module)\n session.camera.handlePhotoError(errorResponse);\n\n // Respond to ASG client\n return res.json({\n success: true,\n requestId,\n message: \"Photo error received successfully\",\n });\n }\n\n // Handle successful photo upload\n if (!photoFile) {\n this.logger.error(\n { requestId },\n \"No photo file in successful upload\",\n );\n return res.status(400).json({\n success: false,\n error: \"No photo file provided for successful upload\",\n });\n }\n\n // Create photo data object\n const photoData = {\n buffer: photoFile.buffer,\n mimeType: photoFile.mimetype,\n filename: photoFile.originalname || \"photo.jpg\",\n requestId,\n size: photoFile.size,\n timestamp: new Date(),\n };\n\n // Deliver photo to the session\n session.camera.handlePhotoReceived(photoData);\n\n // Respond to ASG client\n res.json({\n success: true,\n requestId,\n message: \"Photo received successfully\",\n });\n } catch (error) {\n this.logger.error(error, \"❌ Error handling photo response\");\n res.status(500).json({\n success: false,\n error: \"Internal server error processing photo response\",\n });\n }\n },\n );\n }\n\n /**\n * 🔐 Setup Mentra Auth Redirect Endpoint\n * Creates a /mentra-auth endpoint that redirects to the MentraOS OAuth flow.\n */\n private setupMentraAuthRedirect(): void {\n this.app.get(\"/mentra-auth\", (req, res) => {\n // Redirect to the account.mentra.glass OAuth flow with the app's package name\n const authUrl = `https://account.mentra.glass/auth?packagename=${encodeURIComponent(\n this.config.packageName,\n )}`;\n\n this.logger.info(`🔐 Redirecting to MentraOS OAuth flow: ${authUrl}`);\n\n res.redirect(302, authUrl);\n });\n }\n\n /**\n * Find session that has a pending photo request for the given requestId\n */\n private findSessionByPhotoRequestId(\n requestId: string,\n ): AppSession | undefined {\n for (const [_sessionId, session] of this.activeSessions) {\n if (session.camera.hasPhotoPendingRequest(requestId)) {\n return session;\n }\n }\n return undefined;\n }\n}\n\n/**\n * @deprecated Use `AppServerConfig` instead. `TpaServerConfig` is deprecated and will be removed in a future version.\n * This is an alias for backward compatibility only.\n *\n * @example\n * ```typescript\n * // ❌ Deprecated - Don't use this\n * const config: TpaServerConfig = { ... };\n *\n * // ✅ Use this instead\n * const config: AppServerConfig = { ... };\n * ```\n */\nexport type TpaServerConfig = AppServerConfig;\n\n/**\n * @deprecated Use `AppServer` instead. `TpaServer` is deprecated and will be removed in a future version.\n * This is an alias for backward compatibility only.\n *\n * @example\n * ```typescript\n * // ❌ Deprecated - Don't use this\n * class MyServer extends TpaServer { ... }\n *\n * // ✅ Use this instead\n * class MyServer extends AppServer { ... }\n * ```\n */\nexport class TpaServer extends AppServer {\n constructor(config: TpaServerConfig) {\n super(config);\n // Emit a deprecation warning to help developers migrate\n console.warn(\n \"⚠️ DEPRECATION WARNING: TpaServer is deprecated and will be removed in a future version. \" +\n \"Please use AppServer instead. \" +\n 'Simply replace \"TpaServer\" with \"AppServer\" in your code.',\n );\n }\n}\n",
|
|
21
|
+
"/* eslint-disable import/order */\n/* eslint-disable no-restricted-imports */\n/**\n * 🎯 App Session Module\n *\n * Manages an active Third Party App session with MentraOS Cloud.\n * Handles real-time communication, event subscriptions, and display management.\n */\nimport {WebSocket} from \"ws\"\nimport {EventManager, EventData} from \"./events\"\nimport {LayoutManager} from \"./layouts\"\nimport {SettingsManager} from \"./settings\"\nimport {LocationManager} from \"./modules/location\"\nimport {CameraModule} from \"./modules/camera\"\nimport {LedModule} from \"./modules/led\"\nimport {AudioManager} from \"./modules/audio\"\nimport {ResourceTracker} from \"../../utils/resource-tracker\"\nimport {\n // Message types\n AppToCloudMessage,\n CloudToAppMessage,\n AppConnectionInit,\n AppSubscriptionUpdate,\n AudioPlayResponse,\n RequestWifiSetup,\n AppToCloudMessageType,\n CloudToAppMessageType,\n\n // Event data types\n StreamType,\n ExtendedStreamType,\n ButtonPress,\n HeadPosition,\n TouchEvent,\n PhoneNotification,\n PhoneNotificationDismissed,\n TranscriptionData,\n TranslationData,\n createTouchEventStream,\n\n // Type guards\n isAppConnectionAck,\n isAppConnectionError,\n isDataStream,\n isAppStopped,\n isSettingsUpdate,\n isDashboardModeChanged,\n isDashboardAlwaysOnChanged,\n isAudioPlayResponse,\n isCapabilitiesUpdate,\n\n // Other types\n AppSettings,\n AppSetting,\n AppConfig,\n validateAppConfig,\n AudioChunk,\n VpsCoordinates,\n PhotoTaken,\n SubscriptionRequest,\n Capabilities,\n CapabilitiesUpdate,\n} from \"../../types\"\nimport {DashboardAPI} from \"../../types/dashboard\"\nimport {MentraosSettingsUpdate} from \"../../types/messages/cloud-to-app\"\nimport {Logger} from \"pino\"\nimport {AppServer} from \"../server\"\nimport axios from \"axios\"\nimport EventEmitter from \"events\"\n\n// Import the cloud-to-app specific type guards\nimport {\n isPhotoResponse,\n isRgbLedControlResponse,\n isRtmpStreamStatus,\n isManagedStreamStatus,\n isStreamStatusCheckResponse,\n} from \"../../types/messages/cloud-to-app\"\nimport {SimpleStorage} from \"./modules/simple-storage\"\nimport {readNotificationWarnLog} from \"../../utils/permissions-utils\"\n\n/**\n * ⚙️ Configuration options for App Session\n *\n * @example\n * ```typescript\n * const config: AppSessionConfig = {\n * packageName: 'org.example.myapp',\n * apiKey: 'your_api_key',\n * // Auto-reconnection is enabled by default\n * // autoReconnect: true\n * };\n * ```\n */\nexport interface AppSessionConfig {\n /** 📦 Unique identifier for your App (e.g., 'org.company.appname') */\n packageName: string\n /** 🔑 API key for authentication with MentraOS Cloud */\n apiKey: string\n /** 🔌 WebSocket server URL (default: 'ws://localhost:7002/app-ws') */\n mentraOSWebsocketUrl?: string\n /** 🔄 Automatically attempt to reconnect on disconnect (default: true) */\n autoReconnect?: boolean\n /** 🔁 Maximum number of reconnection attempts (default: 3) */\n maxReconnectAttempts?: number\n /** ⏱️ Base delay between reconnection attempts in ms (default: 1000) */\n reconnectDelay?: number\n\n userId: string // user ID for tracking sessions (email of the user).\n appServer: AppServer // Optional App server instance for advanced features\n}\n\n// List of event types that should never be subscribed to as streams\nconst APP_TO_APP_EVENT_TYPES = [\n \"app_message_received\",\n \"app_user_joined\",\n \"app_user_left\",\n \"app_room_updated\",\n \"app_direct_message_response\",\n]\n\n/**\n * 🚀 App Session Implementation\n *\n * Manages a live connection between your App and MentraOS Cloud.\n * Provides interfaces for:\n * - 🎮 Event handling (transcription, head position, etc.)\n * - 📱 Display management in AR view\n * - 🔌 Connection lifecycle\n * - 🔄 Automatic reconnection\n *\n * @example\n * ```typescript\n * const session = new AppSession({\n * packageName: 'org.example.myapp',\n * apiKey: 'your_api_key'\n * });\n *\n * // Handle events\n * session.onTranscription((data) => {\n * session.layouts.showTextWall(data.text);\n * });\n *\n * // Connect to cloud\n * await session.connect('session_123');\n * ```\n */\nexport class AppSession {\n /** WebSocket connection to MentraOS Cloud */\n private ws: WebSocket | null = null\n /** Current session identifier */\n private sessionId: string | null = null\n /** Number of reconnection attempts made */\n private reconnectAttempts = 0\n /** Active event subscriptions */\n private subscriptions = new Set<ExtendedStreamType>()\n /** Map to store rate options for streams */\n private streamRates = new Map<ExtendedStreamType, string>()\n /** Resource tracker for automatic cleanup */\n private resources = new ResourceTracker()\n /** Internal settings storage - use public settings API instead */\n private settingsData: AppSettings = []\n /** App configuration loaded from app_config.json */\n private appConfig: AppConfig | null = null\n /** Whether to update subscriptions when settings change */\n private shouldUpdateSubscriptionsOnSettingsChange = false\n /** Custom subscription handler for settings-based subscriptions */\n private subscriptionSettingsHandler?: (settings: AppSettings) => ExtendedStreamType[]\n /** Settings that should trigger subscription updates when changed */\n private subscriptionUpdateTriggers: string[] = []\n /** Pending user discovery requests waiting for responses */\n private pendingUserDiscoveryRequests = new Map<\n string,\n {\n resolve: (userList: any) => void\n reject: (reason: any) => void\n }\n >()\n /** Pending direct message requests waiting for responses */\n private pendingDirectMessages = new Map<\n string,\n {\n resolve: (success: boolean) => void\n reject: (reason: any) => void\n }\n >()\n\n /** 🎮 Event management interface */\n public readonly events: EventManager\n /** 📱 Layout management interface */\n public readonly layouts: LayoutManager\n /** ⚙️ Settings management interface */\n public readonly settings: SettingsManager\n /** 📊 Dashboard management interface */\n public readonly dashboard: DashboardAPI\n /** 📍 Location management interface */\n public readonly location: LocationManager\n /** 📷 Camera interface for photos and streaming */\n public readonly camera: CameraModule\n /** 💡 LED interface for RGB LED control */\n public readonly led: LedModule\n /** 🔊 Audio interface for audio playback */\n public readonly audio: AudioManager\n /** 🔐 Simple key-value storage interface */\n public readonly simpleStorage: SimpleStorage\n\n public readonly appServer: AppServer\n public readonly logger: Logger\n public readonly userId: string\n\n /** 🔧 Device capabilities available for this session */\n public capabilities: Capabilities | null = null\n\n /** 📡 Latest glasses connection state (includes WiFi status) */\n private glassesConnectionState: any = null // Using any for now since GlassesConnectionState is in glasses-to-cloud types\n\n /** Dedicated emitter for App-to-App events */\n private appEvents = new EventEmitter()\n\n constructor(private config: AppSessionConfig) {\n // Set defaults and merge with provided config\n this.config = {\n mentraOSWebsocketUrl: `ws://localhost:8002/app-ws`, // Use localhost as default\n autoReconnect: true, // Enable auto-reconnection by default for better reliability\n maxReconnectAttempts: 3, // Default to 3 reconnection attempts for better resilience\n reconnectDelay: 1000, // Start with 1 second delay (uses exponential backoff)\n ...config,\n }\n\n this.appServer = this.config.appServer\n this.logger = this.appServer.logger.child({\n userId: this.config.userId,\n service: \"app-session\",\n })\n this.userId = this.config.userId\n\n // Make sure the URL is correctly formatted to prevent double protocol issues\n if (this.config.mentraOSWebsocketUrl) {\n try {\n const url = new URL(this.config.mentraOSWebsocketUrl)\n if (![\"ws:\", \"wss:\"].includes(url.protocol)) {\n // Fix URLs with incorrect protocol (e.g., 'ws://http://host')\n const fixedUrl = this.config.mentraOSWebsocketUrl.replace(/^ws:\\/\\/http:\\/\\//, \"ws://\")\n this.config.mentraOSWebsocketUrl = fixedUrl\n this.logger.warn(`⚠️ [${this.config.packageName}] Fixed malformed WebSocket URL: ${fixedUrl}`)\n }\n } catch (error) {\n this.logger.error(\n error,\n `⚠️ [${this.config.packageName}] Invalid WebSocket URL format: ${this.config.mentraOSWebsocketUrl}`,\n )\n }\n }\n\n // Log initialization\n this.logger.debug(`🚀 [${this.config.packageName}] App Session initialized`)\n this.logger.debug(`🚀 [${this.config.packageName}] WebSocket URL: ${this.config.mentraOSWebsocketUrl}`)\n\n // Validate URL format - give early warning for obvious issues\n // Check URL format but handle undefined case\n if (this.config.mentraOSWebsocketUrl) {\n try {\n const url = new URL(this.config.mentraOSWebsocketUrl)\n if (![\"ws:\", \"wss:\"].includes(url.protocol)) {\n this.logger.error(\n {config: this.config},\n `⚠️ [${this.config.packageName}] Invalid WebSocket URL protocol: ${url.protocol}. Should be ws: or wss:`,\n )\n }\n } catch (error) {\n this.logger.error(\n error,\n `⚠️ [${this.config.packageName}] Invalid WebSocket URL format: ${this.config.mentraOSWebsocketUrl}`,\n )\n }\n }\n\n this.events = new EventManager(\n this.subscribe.bind(this),\n this.unsubscribe.bind(this),\n this.config.packageName,\n this.getHttpsServerUrl() || \"\",\n )\n this.layouts = new LayoutManager(config.packageName, this.send.bind(this))\n\n // Initialize settings manager with all necessary parameters, including subscribeFn for MentraOS settings\n this.settings = new SettingsManager(\n this.settingsData,\n this.config.packageName,\n this.config.mentraOSWebsocketUrl,\n this.sessionId ?? undefined,\n async (streams: string[]) => {\n this.logger.debug({streams: JSON.stringify(streams)}, `[AppSession] subscribeFn called for streams`)\n streams.forEach((stream) => {\n if (!this.subscriptions.has(stream as ExtendedStreamType)) {\n this.subscriptions.add(stream as ExtendedStreamType)\n this.logger.debug(`[AppSession] Auto-subscribed to stream '${stream}' for MentraOS setting.`)\n } else {\n this.logger.debug(`[AppSession] Already subscribed to stream '${stream}'.`)\n }\n })\n this.logger.debug(\n {subscriptions: JSON.stringify(Array.from(this.subscriptions))},\n `[AppSession] Current subscriptions after subscribeFn`,\n )\n if (this.ws?.readyState === 1) {\n this.updateSubscriptions()\n this.logger.debug(\n `[AppSession] Sent updated subscriptions to cloud after auto-subscribing to MentraOS setting.`,\n )\n } else {\n this.logger.debug(`[AppSession] WebSocket not open, will send subscriptions when connected.`)\n }\n },\n )\n\n // Initialize dashboard API with this session instance\n // Import DashboardManager dynamically to avoid circular dependency\n const {DashboardManager} = require(\"./dashboard\")\n this.dashboard = new DashboardManager(this)\n\n // Initialize camera module with session reference\n this.camera = new CameraModule(\n this,\n this.config.packageName,\n this.sessionId || \"unknown-session-id\",\n this.logger.child({module: \"camera\"}),\n )\n\n // Initialize LED control module\n this.led = new LedModule(\n this,\n this.config.packageName,\n this.sessionId || \"unknown-session-id\",\n this.logger.child({module: \"led\"}),\n )\n\n // Initialize audio module with session reference\n this.audio = new AudioManager(\n this,\n this.config.packageName,\n this.sessionId || \"unknown-session-id\",\n this.logger.child({module: \"audio\"}),\n )\n\n this.simpleStorage = new SimpleStorage(this)\n\n this.location = new LocationManager(this)\n }\n\n /**\n * Get the current session ID\n * @returns The current session ID or 'unknown-session-id' if not connected\n */\n getSessionId(): string {\n return this.sessionId || \"unknown-session-id\"\n }\n\n /**\n * Get the package name for this App\n * @returns The package name\n */\n getPackageName(): string {\n return this.config.packageName\n }\n\n // =====================================\n // 🎮 Direct Event Handling Interface\n // =====================================\n\n /**\n * @deprecated Use session.events.onTranscription() instead\n */\n onTranscription(handler: (data: TranscriptionData) => void): () => void {\n return this.events.onTranscription(handler)\n }\n\n /**\n * 🌐 Listen for speech transcription events in a specific language\n * @param language - Language code (e.g., \"en-US\")\n * @param handler - Function to handle transcription data\n * @returns Cleanup function to remove the handler\n * @throws Error if language code is invalid\n * @deprecated Use session.events.onTranscriptionForLanguage() instead\n */\n onTranscriptionForLanguage(\n language: string,\n handler: (data: TranscriptionData) => void,\n disableLanguageIdentification = false,\n ): () => void {\n return this.events.onTranscriptionForLanguage(language, handler, disableLanguageIdentification)\n }\n\n /**\n * 🌐 Listen for speech translation events for a specific language pair\n * @param sourceLanguage - Source language code (e.g., \"es-ES\")\n * @param targetLanguage - Target language code (e.g., \"en-US\")\n * @param handler - Function to handle translation data\n * @returns Cleanup function to remove the handler\n * @throws Error if language codes are invalid\n * @deprecated Use session.events.onTranslationForLanguage() instead\n */\n onTranslationForLanguage(\n sourceLanguage: string,\n targetLanguage: string,\n handler: (data: TranslationData) => void,\n ): () => void {\n return this.events.ontranslationForLanguage(sourceLanguage, targetLanguage, handler)\n }\n\n /**\n * 👤 Listen for head position changes\n * @param handler - Function to handle head position updates\n * @returns Cleanup function to remove the handler\n * @deprecated Use session.events.onHeadPosition() instead\n */\n onHeadPosition(handler: (data: HeadPosition) => void): () => void {\n return this.events.onHeadPosition(handler)\n }\n\n /**\n * 🔘 Listen for hardware button press events\n * @param handler - Function to handle button events\n * @returns Cleanup function to remove the handler\n * @deprecated Use session.events.onButtonPress() instead\n */\n onButtonPress(handler: (data: ButtonPress) => void): () => void {\n return this.events.onButtonPress(handler)\n }\n\n /**\n * 👆 Listen for touch gesture events\n * @param gestureOrHandler - Gesture name or handler function\n * @param handler - Handler function (if first param is gesture name)\n * @returns Cleanup function\n *\n * @example\n * // Subscribe to all touch events\n * session.onTouchEvent((event) => console.log(event.gesture_name));\n *\n * // Subscribe to specific gesture\n * session.onTouchEvent(\"forward_swipe\", (event) => console.log(\"Forward swipe!\"));\n */\n onTouchEvent(\n gestureOrHandler: string | ((data: TouchEvent) => void),\n handler?: (data: TouchEvent) => void,\n ): () => void {\n return this.events.onTouchEvent(gestureOrHandler as any, handler as any)\n }\n\n /**\n * 👆 Subscribe to multiple touch gestures\n * @param gestures - Array of gesture names\n * @returns Cleanup function that unsubscribes from all\n *\n * @example\n * session.subscribeToGestures([\"forward_swipe\", \"backward_swipe\"]);\n */\n subscribeToGestures(gestures: string[]): () => void {\n gestures.forEach((gesture) => {\n const stream = createTouchEventStream(gesture)\n this.subscribe(stream)\n })\n\n return () => {\n gestures.forEach((gesture) => {\n const stream = createTouchEventStream(gesture)\n this.unsubscribe(stream)\n })\n }\n }\n\n /**\n * 📱 Listen for phone notification events\n * @param handler - Function to handle notifications\n * @returns Cleanup function to remove the handler\n * @deprecated Use session.events.onPhoneNotifications() instead\n */\n onPhoneNotifications(handler: (data: PhoneNotification) => void): () => void {\n readNotificationWarnLog(this.getHttpsServerUrl() || \"\", this.getPackageName(), \"onPhoneNotifications\")\n return this.events.onPhoneNotifications(handler)\n }\n\n /**\n * 📱 Listen for phone notification dismissed events\n * @param handler - Function to handle notification dismissal data\n * @returns Cleanup function to remove the handler\n * @deprecated Use session.events.onPhoneNotificationDismissed() instead\n */\n onPhoneNotificationDismissed(handler: (data: PhoneNotificationDismissed) => void): () => void {\n return this.events.onPhoneNotificationDismissed(handler)\n }\n\n /**\n * 📡 Listen for VPS coordinates updates\n * @param handler - Function to handle VPS coordinates\n * @returns Cleanup function to remove the handler\n * @deprecated Use session.events.onVpsCoordinates() instead\n */\n onVpsCoordinates(handler: (data: VpsCoordinates) => void): () => void {\n this.subscribe(StreamType.VPS_COORDINATES)\n return this.events.onVpsCoordinates(handler)\n }\n\n /**\n * 📸 Listen for photo responses\n * @param handler - Function to handle photo response data\n * @returns Cleanup function to remove the handler\n * @deprecated Use session.events.onPhotoTaken() instead\n */\n onPhotoTaken(handler: (data: PhotoTaken) => void): () => void {\n this.subscribe(StreamType.PHOTO_TAKEN)\n return this.events.onPhotoTaken(handler)\n }\n\n // =====================================\n // 📡 Pub/Sub Interface\n // =====================================\n\n /**\n * 📬 Subscribe to a specific event stream\n * @param sub - A string or a rich subscription object\n */\n subscribe(sub: SubscriptionRequest): void {\n let type: ExtendedStreamType\n let rate: string | undefined\n\n if (typeof sub === \"string\") {\n type = sub\n } else {\n // it's a LocationStreamRequest object\n type = sub.stream\n rate = sub.rate\n }\n\n if (APP_TO_APP_EVENT_TYPES.includes(type as string)) {\n this.logger.warn(\n `[AppSession] Attempted to subscribe to App-to-App event type '${type}', which is not a valid stream. Use the event handler (e.g., onAppMessage) instead.`,\n )\n return\n }\n\n this.subscriptions.add(type)\n if (rate) {\n this.streamRates.set(type, rate)\n }\n\n if (this.ws?.readyState === 1) {\n this.updateSubscriptions()\n }\n }\n\n /**\n * 📭 Unsubscribe from a specific event stream\n * @param sub - The subscription to remove\n */\n unsubscribe(sub: SubscriptionRequest): void {\n let type: ExtendedStreamType\n if (typeof sub === \"string\") {\n type = sub\n } else {\n type = sub.stream\n }\n\n if (APP_TO_APP_EVENT_TYPES.includes(type as string)) {\n this.logger.warn(\n `[AppSession] Attempted to unsubscribe from App-to-App event type '${type}', which is not a valid stream.`,\n )\n return\n }\n this.subscriptions.delete(type)\n this.streamRates.delete(type) // also remove from our rate map\n if (this.ws?.readyState === 1) {\n this.updateSubscriptions()\n }\n }\n\n /**\n * 🎯 Generic event listener (pub/sub style)\n * @param event - Event name to listen for\n * @param handler - Event handler function\n */\n on<T extends ExtendedStreamType>(event: T, handler: (data: EventData<T>) => void): () => void {\n return this.events.on(event, handler)\n }\n\n // =====================================\n // 🔌 Connection Management\n // =====================================\n\n /**\n * 🚀 Connect to MentraOS Cloud\n * @param sessionId - Unique session identifier\n * @returns Promise that resolves when connected\n */\n async connect(sessionId: string): Promise<void> {\n this.sessionId = sessionId\n\n // Configure settings API client with the WebSocket URL and session ID\n // This allows settings to be fetched from the correct server\n this.settings.configureApiClient(this.config.packageName, this.config.mentraOSWebsocketUrl || \"\", sessionId)\n\n // Update the sessionId in the camera module\n if (this.camera) {\n this.camera.updateSessionId(sessionId)\n }\n\n // Update the sessionId in the audio module\n if (this.audio) {\n this.audio.updateSessionId(sessionId)\n }\n\n return new Promise((resolve, reject) => {\n try {\n // Clear previous resources if reconnecting\n if (this.ws) {\n // Don't call full dispose() as that would clear subscriptions\n if (this.ws.readyState !== 3) {\n // 3 = CLOSED\n this.ws.close()\n }\n this.ws = null\n }\n\n // Validate WebSocket URL before attempting connection\n if (!this.config.mentraOSWebsocketUrl) {\n this.logger.error(\"WebSocket URL is missing or undefined\")\n reject(new Error(\"WebSocket URL is required\"))\n return\n }\n\n // Add debug logging for connection attempts\n this.logger.info(\n `🔌🔌🔌 [${this.config.packageName}] Attempting to connect to: ${this.config.mentraOSWebsocketUrl} for session ${this.sessionId}`,\n )\n\n // Create connection with error handling\n this.ws = new WebSocket(this.config.mentraOSWebsocketUrl)\n\n // Track WebSocket for automatic cleanup\n this.resources.track(() => {\n if (this.ws && this.ws.readyState !== 3) {\n // 3 = CLOSED\n this.ws.close()\n }\n })\n\n this.ws.on(\"open\", () => {\n try {\n this.sendConnectionInit()\n } catch (error: unknown) {\n this.logger.error(error, \"Error during connection initialization\")\n const errorMessage = error instanceof Error ? error.message : String(error)\n this.events.emit(\"error\", new Error(`Connection initialization failed: ${errorMessage}`))\n reject(error)\n }\n })\n\n // Message handler with comprehensive error recovery\n const messageHandler = async (data: Buffer | string, isBinary: boolean) => {\n try {\n // Handle binary messages (typically audio data)\n if (isBinary && Buffer.isBuffer(data)) {\n try {\n // Validate buffer before processing\n if (data.length === 0) {\n this.events.emit(\"error\", new Error(\"Received empty binary data\"))\n return\n }\n\n // Convert Node.js Buffer to ArrayBuffer safely\n const arrayBuf: ArrayBufferLike = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength)\n\n // Create AUDIO_CHUNK event message with validation\n const audioChunk: AudioChunk = {\n type: StreamType.AUDIO_CHUNK,\n arrayBuffer: arrayBuf,\n timestamp: new Date(), // Ensure timestamp is present\n }\n\n this.handleMessage(audioChunk)\n return\n } catch (error: unknown) {\n this.logger.error(error, \"Error processing binary message:\")\n const errorMessage = error instanceof Error ? error.message : String(error)\n this.events.emit(\"error\", new Error(`Failed to process binary message: ${errorMessage}`))\n return\n }\n }\n\n // Handle ArrayBuffer data type directly\n if (data instanceof ArrayBuffer) {\n return\n }\n\n // Handle JSON messages with validation\n try {\n // Convert string data to JSON safely\n let jsonData: string\n if (typeof data === \"string\") {\n jsonData = data\n } else if (Buffer.isBuffer(data)) {\n jsonData = data.toString(\"utf8\")\n } else {\n throw new Error(\"Unknown message format\")\n }\n\n // Validate JSON before parsing\n if (!jsonData || jsonData.trim() === \"\") {\n this.events.emit(\"error\", new Error(\"Received empty JSON message\"))\n return\n }\n\n // Parse JSON with error handling\n const message = JSON.parse(jsonData) as CloudToAppMessage\n\n // Basic schema validation\n if (!message || typeof message !== \"object\" || !(\"type\" in message)) {\n this.events.emit(\"error\", new Error(\"Malformed message: missing type property\"))\n return\n }\n\n // Process the validated message\n this.handleMessage(message)\n } catch (error: unknown) {\n this.logger.error(error, \"JSON parsing error\")\n const errorMessage = error instanceof Error ? error.message : String(error)\n this.events.emit(\"error\", new Error(`Failed to parse JSON message: ${errorMessage}`))\n }\n } catch (error: unknown) {\n // Final catch - should never reach here if individual handlers work correctly\n this.logger.error({error}, \"Unhandled message processing error\")\n const errorMessage = error instanceof Error ? error.message : String(error)\n this.events.emit(\"error\", new Error(`Unhandled message error: ${errorMessage}`))\n }\n }\n\n this.ws.on(\"message\", messageHandler)\n\n // Track event handler removal for automatic cleanup\n this.resources.track(() => {\n if (this.ws) {\n this.ws.off(\"message\", messageHandler)\n }\n })\n\n // Connection closure handler\n const closeHandler = (code: number, reason: string) => {\n const reasonStr = reason ? `: ${reason}` : \"\"\n const closeInfo = `Connection closed (code: ${code})${reasonStr}`\n\n // Emit the disconnected event with structured data for better handling\n this.events.emit(\"disconnected\", {\n message: closeInfo,\n code: code,\n reason: reason || \"\",\n wasClean: code === 1000 || code === 1001,\n })\n\n // Only attempt reconnection for abnormal closures\n // https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent/code\n // 1000 (Normal Closure) and 1001 (Going Away) are normal\n // 1002-1015 are abnormal, and reason \"App stopped\" means intentional closure\n // 1008 usually when the userSession no longer exists on server. i.e user disconnected from cloud.\n const isNormalClosure = code === 1000 || code === 1001 || code === 1008\n const isManualStop = reason && reason.includes(\"App stopped\")\n const isUserSessionEnded = reason && reason.includes(\"User session ended\")\n\n // Log closure details for diagnostics\n this.logger.debug(`🔌 [${this.config.packageName}] WebSocket closed with code ${code}${reasonStr}`)\n this.logger.debug(\n `🔌 [${this.config.packageName}] isNormalClosure: ${isNormalClosure}, isManualStop: ${isManualStop}, isUserSessionEnded: ${isUserSessionEnded}`,\n )\n\n if (!isNormalClosure && !isManualStop) {\n this.logger.warn(`🔌 [${this.config.packageName}] Abnormal closure detected, attempting reconnection`)\n this.handleReconnection()\n } else {\n this.logger.debug(`🔌 [${this.config.packageName}] Normal closure detected, not attempting reconnection`)\n }\n\n // if user session ended, then trigger onStop.\n if (isUserSessionEnded) {\n this.logger.info(\n `🛑 [${this.config.packageName}] User session ended - emitting disconnected event with sessionEnded flag`,\n )\n // Emit a disconnected event with a special flag to indicate session end\n // This will be caught by AppServer which will call the onStop callback\n const disconnectInfo = {\n message: \"User session ended\",\n code: 1000, // Normal closure\n reason: \"User session ended\",\n wasClean: true,\n permanent: true, // This is permanent - no reconnection\n sessionEnded: true, // Special flag to indicate session disposal\n }\n this.events.emit(\"disconnected\", disconnectInfo)\n }\n }\n\n this.ws.on(\"close\", closeHandler)\n\n // Track event handler removal\n this.resources.track(() => {\n if (this.ws) {\n this.ws.off(\"close\", closeHandler)\n }\n })\n\n // Connection error handler\n const errorHandler = (error: Error) => {\n this.logger.error(error, \"WebSocket error\")\n this.events.emit(\"error\", error)\n }\n\n // Enhanced error handler with detailed logging\n this.ws.on(\"error\", (error: Error) => {\n this.logger.error(\n error,\n `⛔️⛔️⛔️ [${this.config.packageName}] WebSocket connection error: ${error.message}`,\n )\n\n // Try to provide more context\n const errMsg = error.message || \"\"\n if (errMsg.includes(\"ECONNREFUSED\")) {\n this.logger.error(\n `⛔️⛔️⛔️ [${this.config.packageName}] Connection refused - Check if the server is running at the specified URL`,\n )\n } else if (errMsg.includes(\"ETIMEDOUT\")) {\n this.logger.error(\n `⛔️⛔️⛔️ [${this.config.packageName}] Connection timed out - Check network connectivity and firewall rules`,\n )\n }\n\n errorHandler(error)\n })\n\n // Track event handler removal\n this.resources.track(() => {\n if (this.ws) {\n this.ws.off(\"error\", errorHandler)\n }\n })\n\n // Set up connection success handler\n const connectedCleanup = this.events.onConnected(() => resolve())\n\n // Track event handler removal\n this.resources.track(connectedCleanup)\n\n // Connection timeout with configurable duration\n const timeoutMs = 5000 // 5 seconds default\n const connectionTimeout = this.resources.setTimeout(() => {\n // Use tracked timeout that will be auto-cleared\n this.logger.error(\n {\n config: this.config,\n sessionId: this.sessionId,\n timeoutMs,\n },\n `⏱️⏱️⏱️ [${this.config.packageName}] Connection timeout after ${timeoutMs}ms`,\n )\n\n this.events.emit(\"error\", new Error(`Connection timeout after ${timeoutMs}ms`))\n reject(new Error(\"Connection timeout\"))\n }, timeoutMs)\n\n // Clear timeout on successful connection\n const timeoutCleanup = this.events.onConnected(() => {\n clearTimeout(connectionTimeout)\n resolve()\n })\n\n // Track event handler removal\n this.resources.track(timeoutCleanup)\n } catch (error: unknown) {\n this.logger.error(error, \"Connection setup error\")\n const errorMessage = error instanceof Error ? error.message : String(error)\n reject(new Error(`Failed to setup connection: ${errorMessage}`))\n }\n })\n }\n\n /**\n * 👋 Disconnect from MentraOS Cloud\n * Flushes any pending SimpleStorage writes before closing\n */\n async disconnect(): Promise<void> {\n // Flush any pending SimpleStorage writes before closing\n try {\n await this.simpleStorage.flush()\n console.log(\"SimpleStorage flushed on disconnect\")\n } catch (error) {\n console.error(\"Error flushing SimpleStorage on disconnect:\", error)\n // Continue with disconnect even if flush fails\n }\n\n // Clean up camera module first\n if (this.camera) {\n this.camera.cancelAllRequests()\n }\n\n // Clean up audio module\n if (this.audio) {\n this.audio.cancelAllRequests()\n }\n\n // Use the resource tracker to clean up everything\n this.resources.dispose()\n\n // Clean up additional resources not handled by the tracker\n this.ws = null\n this.sessionId = null\n this.subscriptions.clear()\n this.reconnectAttempts = 0\n }\n\n /**\n * 🛠️ Get all current user settings\n * @returns A copy of the current settings array\n * @deprecated Use session.settings.getAll() instead\n */\n getSettings(): AppSettings {\n return this.settings.getAll()\n }\n\n /**\n * 🔍 Get a specific setting value by key\n * @param key The setting key to look for\n * @returns The setting's value, or undefined if not found\n * @deprecated Use session.settings.get(key) instead\n */\n getSetting<T>(key: string): T | undefined {\n return this.settings.get<T>(key)\n }\n\n /**\n * ⚙️ Configure settings-based subscription updates\n * This allows Apps to automatically update their subscriptions when certain settings change\n * @param options Configuration options for settings-based subscriptions\n */\n setSubscriptionSettings(options: {\n updateOnChange: string[] // Setting keys that should trigger subscription updates\n handler: (settings: AppSettings) => ExtendedStreamType[] // Handler that returns new subscriptions\n }): void {\n this.shouldUpdateSubscriptionsOnSettingsChange = true\n this.subscriptionUpdateTriggers = options.updateOnChange\n this.subscriptionSettingsHandler = options.handler\n\n // If we already have settings, update subscriptions immediately\n if (this.settingsData.length > 0) {\n this.updateSubscriptionsFromSettings()\n }\n }\n\n /**\n * 🔄 Update subscriptions based on current settings\n * Called automatically when relevant settings change\n */\n private updateSubscriptionsFromSettings(): void {\n if (!this.subscriptionSettingsHandler) return\n\n try {\n // Get new subscriptions from handler\n const newSubscriptions = this.subscriptionSettingsHandler(this.settingsData)\n\n // Update all subscriptions at once\n this.subscriptions.clear()\n newSubscriptions.forEach((subscription) => {\n this.subscriptions.add(subscription)\n })\n\n // Send subscription update to cloud if connected\n if (this.ws && this.ws.readyState === 1) {\n this.updateSubscriptions()\n }\n } catch (error: unknown) {\n this.logger.error(error, \"Error updating subscriptions from settings\")\n const errorMessage = error instanceof Error ? error.message : String(error)\n this.events.emit(\"error\", new Error(`Failed to update subscriptions: ${errorMessage}`))\n }\n }\n\n /**\n * 🧪 For testing: Update settings locally\n * In normal operation, settings come from the cloud\n * @param newSettings The new settings to apply\n */\n updateSettingsForTesting(newSettings: AppSettings): void {\n this.settingsData = newSettings\n\n // Update the settings manager with the new settings\n this.settings.updateSettings(newSettings)\n\n // Emit update event for backwards compatibility\n this.events.emit(\"settings_update\", this.settingsData)\n\n // Check if we should update subscriptions\n if (this.shouldUpdateSubscriptionsOnSettingsChange) {\n this.updateSubscriptionsFromSettings()\n }\n }\n\n /**\n * 📝 Load configuration from a JSON file\n * @param jsonData JSON string containing App configuration\n * @returns The loaded configuration\n * @throws Error if the configuration is invalid\n */\n loadConfigFromJson(jsonData: string): AppConfig {\n try {\n const parsedConfig = JSON.parse(jsonData)\n\n if (validateAppConfig(parsedConfig)) {\n this.appConfig = parsedConfig\n return parsedConfig\n } else {\n throw new Error(\"Invalid App configuration format\")\n }\n } catch (error: unknown) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n throw new Error(`Failed to load App configuration: ${errorMessage}`)\n }\n }\n\n /**\n * 📋 Get the loaded App configuration\n * @returns The current App configuration or null if not loaded\n */\n getConfig(): AppConfig | null {\n return this.appConfig\n }\n\n /**\n * 🔌 Get the WebSocket server URL for this session\n * @returns The WebSocket server URL used by this session\n */\n getServerUrl(): string | undefined {\n return this.config.mentraOSWebsocketUrl\n }\n\n public getHttpsServerUrl(): string | undefined {\n if (!this.config.mentraOSWebsocketUrl) {\n return undefined\n }\n return AppSession.convertToHttps(this.config.mentraOSWebsocketUrl)\n }\n\n private static convertToHttps(rawUrl: string | undefined): string {\n if (!rawUrl) return \"\"\n // Remove ws:// or wss://\n let url = rawUrl.replace(/^wss?:\\/\\//, \"\")\n // Remove trailing /app-ws\n url = url.replace(/\\/app-ws$/, \"\")\n // Prepend https://\n return `https://${url}`\n }\n\n /**\n * 🔍 Get default settings from the App configuration\n * @returns Array of settings with default values\n * @throws Error if configuration is not loaded\n */\n getDefaultSettings(): AppSettings {\n if (!this.appConfig) {\n throw new Error(\"App configuration not loaded. Call loadConfigFromJson first.\")\n }\n\n return this.appConfig.settings\n .filter((s: AppSetting | {type: \"group\"; title: string}): s is AppSetting => s.type !== \"group\")\n .map((s: AppSetting) => ({\n ...s,\n value: s.defaultValue, // Set value to defaultValue\n }))\n }\n\n /**\n * 🔍 Get setting schema from configuration\n * @param key Setting key to look up\n * @returns The setting schema or undefined if not found\n */\n getSettingSchema(key: string): AppSetting | undefined {\n if (!this.appConfig) return undefined\n\n const setting = this.appConfig.settings.find(\n (s: AppSetting | {type: \"group\"; title: string}) => s.type !== \"group\" && \"key\" in s && s.key === key,\n )\n\n return setting as AppSetting | undefined\n }\n\n /**\n * 📡 Get WiFi connection status of glasses\n * @returns WiFi status object or null if glasses don't support WiFi or status not available\n */\n getWifiStatus(): {connected: boolean; ssid?: string | null} | null {\n if (!this.capabilities?.hasWifi) {\n return null\n }\n return this.glassesConnectionState?.wifi || null\n }\n\n /**\n * ✅ Check if glasses are connected to WiFi\n * @returns true if connected to WiFi, false otherwise\n */\n isWifiConnected(): boolean {\n return this.getWifiStatus()?.connected === true\n }\n\n /**\n * 🌐 Request WiFi setup from mobile app\n * Triggers a popup on the mobile app that allows user to set up WiFi on glasses\n * @param reason Optional reason message to display to the user\n */\n requestWifiSetup(reason?: string): void {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\n throw new Error(\"Not connected to MentraOS Cloud\")\n }\n\n const message: RequestWifiSetup = {\n type: AppToCloudMessageType.REQUEST_WIFI_SETUP,\n packageName: this.config.packageName,\n sessionId: this.sessionId || \"\",\n reason,\n timestamp: new Date(),\n }\n\n this.send(message)\n }\n\n /**\n * 👂 Listen for glasses connection state changes (includes WiFi status)\n * @param handler Callback function to handle connection state updates\n * @returns Cleanup function to remove the listener\n */\n onGlassesConnectionState(handler: (state: any) => void): () => void {\n return this.events.on(StreamType.GLASSES_CONNECTION_STATE, handler)\n }\n\n // =====================================\n // 🔧 Private Methods\n // =====================================\n\n /**\n * 📨 Handle incoming messages from cloud\n */\n private handleMessage(message: CloudToAppMessage): void {\n try {\n // Validate message before processing\n if (!this.validateMessage(message)) {\n this.events.emit(\"error\", new Error(\"Invalid message format received\"))\n return\n }\n\n // Handle binary data (audio or video)\n if (message instanceof ArrayBuffer) {\n this.handleBinaryMessage(message)\n return\n }\n\n // Using type guards to determine message type and safely handle each case\n try {\n if (isAppConnectionAck(message)) {\n // Get settings from connection acknowledgment\n const receivedSettings = message.settings || []\n this.settingsData = receivedSettings\n\n // Store config if provided\n if (message.config && validateAppConfig(message.config)) {\n this.appConfig = message.config\n }\n\n // Use default settings from config if no settings were provided\n if (receivedSettings.length === 0 && this.appConfig) {\n try {\n this.settingsData = this.getDefaultSettings()\n } catch (error) {\n this.logger.warn(error, \"Failed to load default settings from config:\")\n }\n }\n\n // Update the settings manager with the new settings\n this.settings.updateSettings(this.settingsData)\n\n // Handle MentraOS system settings if provided\n this.logger.debug(\n {mentraosSettings: JSON.stringify(message.mentraosSettings)},\n `[AppSession] CONNECTION_ACK mentraosSettings}`,\n )\n if (message.mentraosSettings) {\n this.logger.info(\n {mentraosSettings: JSON.stringify(message.mentraosSettings)},\n `[AppSession] Calling updatementraosSettings with`,\n )\n this.settings.updateMentraosSettings(message.mentraosSettings)\n } else {\n this.logger.warn(`[AppSession] CONNECTION_ACK message missing mentraosSettings field`)\n }\n\n // Handle device capabilities if provided\n if (message.capabilities) {\n this.capabilities = message.capabilities\n this.logger.info(`[AppSession] Device capabilities loaded for model: ${message.capabilities.modelName}`)\n } else {\n this.logger.debug(`[AppSession] No capabilities provided in CONNECTION_ACK`)\n }\n\n // Emit connected event with settings\n this.events.emit(\"connected\", this.settingsData)\n\n // Update subscriptions (normal flow)\n this.updateSubscriptions()\n\n // If settings-based subscriptions are enabled, update those too\n if (this.shouldUpdateSubscriptionsOnSettingsChange && this.settingsData.length > 0) {\n this.updateSubscriptionsFromSettings()\n }\n } else if (isAppConnectionError(message) || message.type === \"connection_error\") {\n // Handle both App-specific connection_error and standard connection_error\n const errorMessage = message.message || \"Unknown connection error\"\n this.events.emit(\"error\", new Error(errorMessage))\n } else if (message.type === StreamType.AUDIO_CHUNK) {\n if (this.subscriptions.has(StreamType.AUDIO_CHUNK)) {\n // Only process if we're subscribed to avoid unnecessary processing\n this.events.emit(StreamType.AUDIO_CHUNK, message)\n }\n } else if (isDataStream(message) && message.streamType === StreamType.GLASSES_CONNECTION_STATE) {\n // Store latest glasses connection state (includes WiFi info)\n this.glassesConnectionState = message.data\n\n // Emit to subscribed listeners\n if (this.subscriptions.has(StreamType.GLASSES_CONNECTION_STATE)) {\n const sanitizedData = this.sanitizeEventData(\n StreamType.GLASSES_CONNECTION_STATE,\n message.data,\n ) as EventData<typeof StreamType.GLASSES_CONNECTION_STATE>\n this.events.emit(StreamType.GLASSES_CONNECTION_STATE, sanitizedData)\n }\n } else if (isDataStream(message)) {\n // Ensure streamType exists before emitting the event\n const messageStreamType = message.streamType as ExtendedStreamType\n // if (message.streamType === StreamType.TRANSCRIPTION) {\n // const transcriptionData = message.data as TranscriptionData;\n // if (transcriptionData.transcribeLanguage) {\n // messageStreamType = createTranscriptionStream(transcriptionData.transcribeLanguage) as ExtendedStreamType;\n // }\n // } else if (message.streamType === StreamType.TRANSLATION) {\n // const translationData = message.data as TranslationData;\n // if (translationData.transcribeLanguage && translationData.translateLanguage) {\n // messageStreamType = createTranslationStream(translationData.transcribeLanguage, translationData.translateLanguage) as ExtendedStreamType;\n // }\n // }\n\n if (messageStreamType && this.subscriptions.has(messageStreamType)) {\n const sanitizedData = this.sanitizeEventData(messageStreamType, message.data) as EventData<\n typeof messageStreamType\n >\n this.events.emit(messageStreamType, sanitizedData)\n }\n } else if (isRtmpStreamStatus(message)) {\n // Emit as a standard stream event if subscribed\n if (this.subscriptions.has(StreamType.RTMP_STREAM_STATUS)) {\n this.events.emit(StreamType.RTMP_STREAM_STATUS, message)\n }\n\n // Update camera module's internal stream state\n this.camera.updateStreamState(message)\n } else if (isManagedStreamStatus(message)) {\n // Emit as a standard stream event if subscribed\n if (this.subscriptions.has(StreamType.MANAGED_STREAM_STATUS)) {\n this.events.emit(StreamType.MANAGED_STREAM_STATUS, message)\n }\n\n // Update camera module's managed stream state\n this.camera.handleManagedStreamStatus(message)\n } else if (isStreamStatusCheckResponse(message)) {\n // Handle stream status check response\n // This is a direct response, not a subscription-based event\n this.camera.handleStreamCheckResponse(message)\n } else if (isSettingsUpdate(message)) {\n // Store previous settings to check for changes\n const _prevSettings = [...this.settingsData]\n\n // Update internal settings storage\n this.settingsData = message.settings || []\n\n // Update the settings manager with the new settings\n const changes = this.settings.updateSettings(this.settingsData)\n\n // Emit settings update event (for backwards compatibility)\n this.events.emit(\"settings_update\", this.settingsData)\n\n // --- MentraOS settings update logic ---\n // If the message.settings looks like MentraOS settings (object with known keys), update mentraosSettings\n if (message.settings && typeof message.settings === \"object\") {\n this.settings.updateMentraosSettings(message.settings)\n }\n\n // Check if we should update subscriptions\n if (this.shouldUpdateSubscriptionsOnSettingsChange) {\n // Check if any subscription trigger settings changed\n const shouldUpdateSubs = this.subscriptionUpdateTriggers.some((key) => {\n return key in changes\n })\n\n if (shouldUpdateSubs) {\n this.updateSubscriptionsFromSettings()\n }\n }\n } else if (isCapabilitiesUpdate(message)) {\n // Update device capabilities\n const capabilitiesMessage = message as CapabilitiesUpdate\n this.capabilities = capabilitiesMessage.capabilities\n this.logger.info(\n capabilitiesMessage.capabilities,\n `[AppSession] Capabilities updated for model: ${capabilitiesMessage.modelName}`,\n )\n\n // Emit capabilities update event for applications to handle\n this.events.emit(\"capabilities_update\", {\n capabilities: capabilitiesMessage.capabilities,\n modelName: capabilitiesMessage.modelName,\n timestamp: capabilitiesMessage.timestamp,\n })\n } else if (isAppStopped(message)) {\n const reason = message.reason || \"unknown\"\n const displayReason = `App stopped: ${reason}`\n\n // Don't emit disconnected event here - let the WebSocket close handler do it\n // This prevents duplicate disconnected events when the session is disposed\n this.logger.info(`📤 [${this.config.packageName}] Received APP_STOPPED message: ${displayReason}`)\n\n // Clear reconnection state\n this.reconnectAttempts = 0\n }\n // Handle dashboard mode changes\n else if (isDashboardModeChanged(message)) {\n try {\n // Use proper type\n const mode = message.mode || \"none\"\n\n // Update dashboard state in the API\n if (this.dashboard && \"content\" in this.dashboard) {\n ;(this.dashboard.content as any).setCurrentMode(mode)\n }\n } catch (error) {\n this.logger.error(error, \"Error handling dashboard mode change\")\n }\n }\n // Handle always-on dashboard state changes\n else if (isDashboardAlwaysOnChanged(message)) {\n try {\n // Use proper type\n const enabled = !!message.enabled\n\n // Update dashboard state in the API\n if (this.dashboard && \"content\" in this.dashboard) {\n ;(this.dashboard.content as any).setAlwaysOnEnabled(enabled)\n }\n } catch (error) {\n this.logger.error(error, \"Error handling dashboard always-on change\")\n }\n }\n // Handle custom messages\n else if (message.type === CloudToAppMessageType.CUSTOM_MESSAGE) {\n this.events.emit(\"custom_message\", message)\n return\n }\n // Handle App-to-App communication messages\n else if ((message as any).type === \"app_message_received\") {\n this.appEvents.emit(\"app_message_received\", message as any)\n } else if ((message as any).type === \"app_user_joined\") {\n this.appEvents.emit(\"app_user_joined\", message as any)\n } else if ((message as any).type === \"app_user_left\") {\n this.appEvents.emit(\"app_user_left\", message as any)\n } else if ((message as any).type === \"app_room_updated\") {\n this.appEvents.emit(\"app_room_updated\", message as any)\n } else if ((message as any).type === \"app_direct_message_response\") {\n const response = message as any\n if (response.messageId && this.pendingDirectMessages.has(response.messageId)) {\n const {resolve} = this.pendingDirectMessages.get(response.messageId)!\n resolve(response.success)\n this.pendingDirectMessages.delete(response.messageId)\n }\n } else if (message.type === \"augmentos_settings_update\") {\n const mentraosMsg = message as MentraosSettingsUpdate\n if (mentraosMsg.settings && typeof mentraosMsg.settings === \"object\") {\n this.settings.updateMentraosSettings(mentraosMsg.settings)\n }\n }\n // Handle 'connection_error' as a specific case if cloud sends this string literal\n else if ((message as any).type === \"connection_error\") {\n // Treat 'connection_error' (string literal) like AppConnectionError\n // This handles cases where the cloud might send the type as a direct string\n // instead of the enum's 'tpa_connection_error' value.\n const errorMessage = (message as any).message || \"Unknown connection error (type: connection_error)\"\n this.logger.warn(\n `Received 'connection_error' type directly. Consider aligning cloud to send 'tpa_connection_error'. Message: ${errorMessage}`,\n )\n this.events.emit(\"error\", new Error(errorMessage))\n } else if (message.type === \"permission_error\") {\n // Handle permission errors from cloud\n this.logger.warn(\n {\n message: message.message,\n details: message.details,\n detailsCount: message.details?.length || 0,\n rejectedStreams: message.details?.map((d) => d.stream) || [],\n },\n \"Permission error received:\",\n )\n\n // Emit permission error event for application handling\n this.events.emit(\"permission_error\", {\n message: message.message,\n details: message.details,\n timestamp: message.timestamp,\n })\n\n // Optionally emit individual permission denied events for each stream\n message.details?.forEach((detail) => {\n this.events.emit(\"permission_denied\", {\n stream: detail.stream,\n requiredPermission: detail.requiredPermission,\n message: detail.message,\n })\n })\n } else if (isAudioPlayResponse(message)) {\n // Delegate audio play response handling to the audio module\n if (this.audio) {\n this.audio.handleAudioPlayResponse(message as AudioPlayResponse)\n }\n } else if (isPhotoResponse(message)) {\n // Legacy photo response handling - now photos come directly via webhook\n // This branch can be removed in the future as all photos now go through /photo-upload\n this.logger.warn(\n {message},\n \"Received legacy photo response - photos should now come via /photo-upload webhook\",\n )\n } else if (isRgbLedControlResponse(message)) {\n // LED control responses are no longer handled - fire-and-forget mode\n this.logger.debug({message}, \"Received LED control response (ignored - fire-and-forget mode)\")\n }\n // Handle unrecognized message types gracefully\n else {\n this.logger.warn(`Unrecognized message type: ${(message as any).type}`)\n this.events.emit(\"error\", new Error(`Unrecognized message type: ${(message as any).type}`))\n }\n } catch (processingError: unknown) {\n // Catch any errors during message processing to prevent App crashes\n this.logger.error(processingError, \"Error processing message:\")\n const errorMessage = processingError instanceof Error ? processingError.message : String(processingError)\n this.events.emit(\"error\", new Error(`Error processing message: ${errorMessage}`))\n }\n } catch (error: unknown) {\n // Final safety net to ensure the App doesn't crash on any unexpected errors\n this.logger.error(error, \"Unexpected error in message handler\")\n const errorMessage = error instanceof Error ? error.message : String(error)\n this.events.emit(\"error\", new Error(`Unexpected error in message handler: ${errorMessage}`))\n }\n }\n\n /**\n * 🧪 Validate incoming message structure\n * @param message - Message to validate\n * @returns boolean indicating if the message is valid\n */\n private validateMessage(message: CloudToAppMessage): boolean {\n // Handle ArrayBuffer case separately\n if (message instanceof ArrayBuffer) {\n return true // ArrayBuffers are always considered valid at this level\n }\n\n // Check if message is null or undefined\n if (!message) {\n return false\n }\n\n // Check if message has a type property\n if (!(\"type\" in message)) {\n return false\n }\n\n // All other message types should be objects with a type property\n return true\n }\n\n /**\n * 📦 Handle binary message data (audio or video)\n * @param buffer - Binary data as ArrayBuffer\n */\n private handleBinaryMessage(buffer: ArrayBuffer): void {\n try {\n // Safety check - only process if we're subscribed to avoid unnecessary work\n if (!this.subscriptions.has(StreamType.AUDIO_CHUNK)) {\n return\n }\n\n // Validate buffer has content before processing\n if (!buffer || buffer.byteLength === 0) {\n this.events.emit(\"error\", new Error(\"Received empty binary message\"))\n return\n }\n\n // Create a safety wrapped audio chunk with proper defaults\n const audioChunk: AudioChunk = {\n type: StreamType.AUDIO_CHUNK,\n timestamp: new Date(),\n arrayBuffer: buffer,\n sampleRate: 16000, // Default sample rate\n }\n\n // Emit to subscribers\n this.events.emit(StreamType.AUDIO_CHUNK, audioChunk)\n } catch (error: unknown) {\n this.logger.error(error, \"Error processing binary message\")\n const errorMessage = error instanceof Error ? error.message : String(error)\n this.events.emit(\"error\", new Error(`Error processing binary message: ${errorMessage}`))\n }\n }\n\n /**\n * 🧹 Sanitize event data to prevent crashes from malformed data\n * @param streamType - The type of stream data\n * @param data - The potentially unsafe data to sanitize\n * @returns Sanitized data safe for processing\n */\n private sanitizeEventData(streamType: ExtendedStreamType, data: unknown): any {\n try {\n // If data is null or undefined, return an empty object to prevent crashes\n if (data === null || data === undefined) {\n return {}\n }\n\n // For specific stream types, perform targeted sanitization\n switch (streamType) {\n case StreamType.TRANSCRIPTION: {\n // Ensure text field exists and is a string\n if (typeof (data as TranscriptionData).text !== \"string\") {\n return {\n text: \"\",\n isFinal: true,\n startTime: Date.now(),\n endTime: Date.now(),\n }\n }\n break\n }\n\n case StreamType.HEAD_POSITION: {\n // Ensure position data has required numeric fields\n // Handle HeadPosition - Note the property position instead of x,y,z\n const pos = data as any\n if (typeof pos?.position !== \"string\") {\n return {position: \"up\", timestamp: new Date()}\n }\n break\n }\n\n case StreamType.BUTTON_PRESS: {\n // Ensure button type is valid\n const btn = data as any\n if (!btn.buttonId || !btn.pressType) {\n return {\n buttonId: \"unknown\",\n pressType: \"short\",\n timestamp: new Date(),\n }\n }\n break\n }\n }\n\n return data\n } catch (error: unknown) {\n this.logger.error(error, `Error sanitizing ${streamType} data`)\n // Return a safe empty object if something goes wrong\n return {}\n }\n }\n\n /**\n * 🔐 Send connection initialization message\n */\n private sendConnectionInit(): void {\n const message: AppConnectionInit = {\n type: AppToCloudMessageType.CONNECTION_INIT,\n sessionId: this.sessionId!,\n packageName: this.config.packageName,\n apiKey: this.config.apiKey,\n timestamp: new Date(),\n }\n this.send(message)\n }\n\n /**\n * 📝 Update subscription list with cloud\n */\n private updateSubscriptions(): void {\n this.logger.info(\n {subscriptions: JSON.stringify(Array.from(this.subscriptions))},\n `[AppSession] updateSubscriptions: sending subscriptions to cloud`,\n )\n\n // [MODIFIED] builds the array of SubscriptionRequest objects to send to the cloud\n const subscriptionPayload: SubscriptionRequest[] = Array.from(this.subscriptions).map((stream) => {\n const rate = this.streamRates.get(stream)\n if (rate && stream === StreamType.LOCATION_STREAM) {\n return {stream: \"location_stream\", rate: rate as any}\n }\n return stream\n })\n\n const message: AppSubscriptionUpdate = {\n type: AppToCloudMessageType.SUBSCRIPTION_UPDATE,\n packageName: this.config.packageName,\n subscriptions: subscriptionPayload, // [MODIFIED]\n sessionId: this.sessionId!,\n timestamp: new Date(),\n }\n this.send(message)\n }\n\n /**\n * 🔄 Handle reconnection with exponential backoff\n */\n private async handleReconnection(): Promise<void> {\n // Check if reconnection is allowed\n if (!this.config.autoReconnect || !this.sessionId) {\n this.logger.debug(\n `🔄 Reconnection skipped: autoReconnect=${this.config.autoReconnect}, sessionId=${\n this.sessionId ? \"valid\" : \"invalid\"\n }`,\n )\n return\n }\n\n // Check if we've exceeded the maximum attempts\n const maxAttempts = this.config.maxReconnectAttempts || 3\n if (this.reconnectAttempts >= maxAttempts) {\n this.logger.info(`🔄 Maximum reconnection attempts (${maxAttempts}) reached, giving up`)\n\n // Emit a permanent disconnection event to trigger onStop in the App server\n this.events.emit(\"disconnected\", {\n message: `Connection permanently lost after ${maxAttempts} failed reconnection attempts`,\n code: 4000, // Custom code for max reconnection attempts exhausted\n reason: \"Maximum reconnection attempts exceeded\",\n wasClean: false,\n permanent: true, // Flag this as a permanent disconnection\n })\n\n return\n }\n\n // Calculate delay with exponential backoff\n const baseDelay = this.config.reconnectDelay || 1000\n const delay = baseDelay * Math.pow(2, this.reconnectAttempts)\n this.reconnectAttempts++\n\n this.logger.debug(\n `🔄 [${this.config.packageName}] Reconnection attempt ${this.reconnectAttempts}/${maxAttempts} in ${delay}ms`,\n )\n\n // Use the resource tracker for the timeout\n await new Promise<void>((resolve) => {\n this.resources.setTimeout(() => resolve(), delay)\n })\n\n try {\n this.logger.debug(`🔄 [${this.config.packageName}] Attempting to reconnect...`)\n await this.connect(this.sessionId)\n this.logger.debug(`✅ [${this.config.packageName}] Reconnection successful!`)\n this.reconnectAttempts = 0\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n this.logger.error(error, `❌ [${this.config.packageName}] Reconnection failed for user ${this.userId}`)\n this.events.emit(\"error\", new Error(`Reconnection failed: ${errorMessage}`))\n\n // Check if this was the last attempt\n if (this.reconnectAttempts >= maxAttempts) {\n this.logger.debug(\n `🔄 [${this.config.packageName}] Final reconnection attempt failed, emitting permanent disconnection`,\n )\n\n // Emit permanent disconnection event after the last failed attempt\n this.events.emit(\"disconnected\", {\n message: `Connection permanently lost after ${maxAttempts} failed reconnection attempts`,\n code: 4000, // Custom code for max reconnection attempts exhausted\n reason: \"Maximum reconnection attempts exceeded\",\n wasClean: false,\n permanent: true, // Flag this as a permanent disconnection\n })\n }\n }\n }\n\n /**\n * 📤 Public API for modules to send messages\n * Always uses current WebSocket connection\n */\n public sendMessage(message: AppToCloudMessage): void {\n return this.send(message)\n }\n\n /**\n * 📤 Send message to cloud with validation and error handling\n * @throws {Error} If WebSocket is not connected\n */\n private send(message: AppToCloudMessage): void {\n try {\n // Verify WebSocket connection is valid\n if (!this.ws) {\n throw new Error(\"WebSocket connection not established\")\n }\n\n if (this.ws.readyState !== 1) {\n const stateMap: Record<number, string> = {\n 0: \"CONNECTING\",\n 1: \"OPEN\",\n 2: \"CLOSING\",\n 3: \"CLOSED\",\n }\n const stateName = stateMap[this.ws.readyState] || \"UNKNOWN\"\n throw new Error(`WebSocket not connected (current state: ${stateName})`)\n }\n\n // Validate message before sending\n if (!message || typeof message !== \"object\") {\n throw new Error(\"Invalid message: must be an object\")\n }\n\n if (!(\"type\" in message)) {\n throw new Error('Invalid message: missing \"type\" property')\n }\n\n // Ensure message format is consistent\n if (!(\"timestamp\" in message) || !(message.timestamp instanceof Date)) {\n message.timestamp = new Date()\n }\n\n // Try to send with error handling\n try {\n const serializedMessage = JSON.stringify(message)\n this.ws.send(serializedMessage)\n } catch (sendError: unknown) {\n const errorMessage = sendError instanceof Error ? sendError.message : String(sendError)\n throw new Error(`Failed to send message: ${errorMessage}`)\n }\n } catch (error: unknown) {\n // Log the error and emit an event so App developers are aware\n this.logger.error(error, \"Message send error\")\n\n // Ensure we always emit an Error object\n if (error instanceof Error) {\n this.events.emit(\"error\", error)\n } else {\n this.events.emit(\"error\", new Error(String(error)))\n }\n\n // Re-throw to maintain the original function behavior\n throw error\n }\n }\n\n /**\n * Fetch the onboarding instructions for this session from the backend.\n * @returns Promise resolving to the instructions string or null\n */\n public async getInstructions(): Promise<string | null> {\n try {\n const baseUrl = this.getServerUrl()\n const response = await axios.get(`${baseUrl}/api/instructions`, {\n params: {userId: this.userId},\n })\n return response.data.instructions || null\n } catch (err) {\n this.logger.error(err, `Error fetching instructions from backend`)\n return null\n }\n }\n // =====================================\n // 👥 App-to-App Communication Interface\n // =====================================\n\n /**\n * 👥 Discover other users currently using the same App\n * @param includeProfiles - Whether to include user profile information\n * @returns Promise that resolves with list of active users\n */\n async discoverAppUsers(domain: string, includeProfiles = false): Promise<any> {\n // Use the domain argument as the base URL if provided\n if (!domain) {\n throw new Error(\"Domain (API base URL) is required for user discovery\")\n }\n const url = `${domain}/api/app-communication/discover-users`\n // Use the user's core token for authentication\n const appApiKey = this.config.apiKey // This may need to be updated if you store the core token elsewhere\n\n if (!appApiKey) {\n throw new Error(\"Core token (apiKey) is required for user discovery\")\n }\n const body = {\n packageName: this.config.packageName,\n userId: this.userId,\n includeUserProfiles: includeProfiles,\n }\n const response = await fetch(url, {\n method: \"POST\",\n headers: {\n \"Authorization\": `Bearer ${appApiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(body),\n })\n if (!response.ok) {\n const errorText = await response.text()\n throw new Error(`Failed to discover users: ${response.status} ${response.statusText} - ${errorText}`)\n }\n return await response.json()\n }\n\n /**\n * 🔍 Check if a specific user is currently active\n * @param userId - User ID to check for\n * @returns Promise that resolves with boolean indicating if user is active\n */\n async isUserActive(userId: string): Promise<boolean> {\n try {\n const userList = await this.discoverAppUsers(\"\", false)\n return userList.users.some((user: any) => user.userId === userId)\n } catch (error) {\n this.logger.error({error, userId}, \"Error checking if user is active\")\n return false\n }\n }\n\n /**\n * 📊 Get user count for this App\n * @returns Promise that resolves with number of active users\n */\n async getUserCount(domain: string): Promise<number> {\n try {\n const userList = await this.discoverAppUsers(domain, false)\n return userList.totalUsers\n } catch (error) {\n this.logger.error(error, \"Error getting user count\")\n return 0\n }\n }\n\n /**\n * 📢 Send broadcast message to all users with same App active\n * @param payload - Message payload to send\n * @param roomId - Optional room ID for room-based messaging\n * @returns Promise that resolves when message is sent\n */\n async broadcastToAppUsers(payload: any, _roomId?: string): Promise<void> {\n try {\n const messageId = this.generateMessageId()\n\n const message = {\n type: \"app_broadcast_message\",\n packageName: this.config.packageName,\n sessionId: this.sessionId!,\n payload,\n messageId,\n senderUserId: this.userId,\n timestamp: new Date(),\n }\n\n this.send(message as any)\n } catch (error: unknown) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n throw new Error(`Failed to broadcast message: ${errorMessage}`)\n }\n }\n\n /**\n * 📤 Send direct message to specific user\n * @param targetUserId - User ID to send message to\n * @param payload - Message payload to send\n * @returns Promise that resolves with success status\n */\n async sendDirectMessage(targetUserId: string, payload: any): Promise<boolean> {\n return new Promise((resolve, reject) => {\n try {\n const messageId = this.generateMessageId()\n\n // Store promise resolver\n this.pendingDirectMessages.set(messageId, {resolve, reject})\n\n const message = {\n type: \"app_direct_message\",\n packageName: this.config.packageName,\n sessionId: this.sessionId!,\n targetUserId,\n payload,\n messageId,\n senderUserId: this.userId,\n timestamp: new Date(),\n }\n\n this.send(message as any)\n\n // Set timeout to avoid hanging promises\n const timeoutMs = 15000 // 15 seconds\n this.resources.setTimeout(() => {\n if (this.pendingDirectMessages.has(messageId)) {\n this.pendingDirectMessages.get(messageId)!.reject(new Error(\"Direct message timed out\"))\n this.pendingDirectMessages.delete(messageId)\n }\n }, timeoutMs)\n } catch (error: unknown) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n reject(new Error(`Failed to send direct message: ${errorMessage}`))\n }\n })\n }\n\n /**\n * 🏠 Join a communication room for group messaging\n * @param roomId - Room ID to join\n * @param roomConfig - Optional room configuration\n * @returns Promise that resolves when room is joined\n */\n async joinAppRoom(\n roomId: string,\n roomConfig?: {\n maxUsers?: number\n isPrivate?: boolean\n metadata?: any\n },\n ): Promise<void> {\n try {\n const message = {\n type: \"app_room_join\",\n packageName: this.config.packageName,\n sessionId: this.sessionId!,\n roomId,\n roomConfig,\n timestamp: new Date(),\n }\n\n this.send(message as any)\n } catch (error: unknown) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n throw new Error(`Failed to join room: ${errorMessage}`)\n }\n }\n\n /**\n * 🚪 Leave a communication room\n * @param roomId - Room ID to leave\n * @returns Promise that resolves when room is left\n */\n async leaveAppRoom(roomId: string): Promise<void> {\n try {\n const message = {\n type: \"app_room_leave\",\n packageName: this.config.packageName,\n sessionId: this.sessionId!,\n roomId,\n timestamp: new Date(),\n }\n\n this.send(message as any)\n } catch (error: unknown) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n throw new Error(`Failed to leave room: ${errorMessage}`)\n }\n }\n\n /**\n * 📨 Listen for messages from other App users\n * @param handler - Function to handle incoming messages\n * @returns Cleanup function to remove the handler\n */\n onAppMessage(handler: (message: any) => void): () => void {\n this.appEvents.on(\"app_message_received\", handler)\n return () => this.appEvents.off(\"app_message_received\", handler)\n }\n\n /**\n * 👋 Listen for user join events\n * @param handler - Function to handle user join events\n * @returns Cleanup function to remove the handler\n */\n onAppUserJoined(handler: (data: any) => void): () => void {\n this.appEvents.on(\"app_user_joined\", handler)\n return () => this.appEvents.off(\"app_user_joined\", handler)\n }\n\n /**\n * 🚪 Listen for user leave events\n * @param handler - Function to handle user leave events\n * @returns Cleanup function to remove the handler\n */\n onAppUserLeft(handler: (data: any) => void): () => void {\n this.appEvents.on(\"app_user_left\", handler)\n return () => this.appEvents.off(\"app_user_left\", handler)\n }\n\n /**\n * 🏠 Listen for room update events\n * @param handler - Function to handle room updates\n * @returns Cleanup function to remove the handler\n */\n onAppRoomUpdated(handler: (data: any) => void): () => void {\n this.appEvents.on(\"app_room_updated\", handler)\n return () => this.appEvents.off(\"app_room_updated\", handler)\n }\n\n /**\n * 🔧 Generate unique message ID\n * @returns Unique message identifier\n */\n private generateMessageId(): string {\n return `msg_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`\n }\n}\n\n/**\n * @deprecated Use `AppSessionConfig` instead. `TpaSessionConfig` is deprecated and will be removed in a future version.\n * This is an alias for backward compatibility only.\n *\n * @example\n * ```typescript\n * // ❌ Deprecated - Don't use this\n * const config: TpaSessionConfig = { ... };\n *\n * // ✅ Use this instead\n * const config: AppSessionConfig = { ... };\n * ```\n */\nexport type TpaSessionConfig = AppSessionConfig\n\n/**\n * @deprecated Use `AppSession` instead. `TpaSession` is deprecated and will be removed in a future version.\n * This is an alias for backward compatibility only.\n *\n * @example\n * ```typescript\n * // ❌ Deprecated - Don't use this\n * const session = new TpaSession(config);\n *\n * // ✅ Use this instead\n * const session = new AppSession(config);\n * ```\n */\nexport class TpaSession extends AppSession {\n constructor(config: TpaSessionConfig) {\n super(config)\n // Emit a deprecation warning to help developers migrate\n console.warn(\n \"⚠️ DEPRECATION WARNING: TpaSession is deprecated and will be removed in a future version. \" +\n \"Please use AppSession instead. \" +\n 'Simply replace \"TpaSession\" with \"AppSession\" in your code.',\n )\n }\n}\n\n// Export module types for developers\nexport {CameraModule, PhotoRequestOptions, RtmpStreamOptions} from \"./modules/camera\"\nexport {LedModule, LedControlOptions} from \"./modules/led\"\nexport {AudioManager, AudioPlayOptions, AudioPlayResult, SpeakOptions} from \"./modules/audio\"\nexport {SimpleStorage} from \"./modules/simple-storage\"\n",
|
|
22
|
+
"/**\n * 🎮 Event Manager Module\n */\nimport EventEmitter from \"events\"\nimport {\n StreamType,\n ExtendedStreamType,\n AppSettings,\n WebSocketError,\n // Event data types\n ButtonPress,\n HeadPosition,\n PhoneNotification,\n TranscriptionData,\n TranslationData,\n GlassesBatteryUpdate,\n PhoneBatteryUpdate,\n GlassesConnectionState,\n LocationUpdate,\n Vad,\n AudioChunk,\n CalendarEvent,\n VpsCoordinates,\n // Language stream helpers\n createTranscriptionStream,\n isValidLanguageCode,\n createTranslationStream,\n CustomMessage,\n RtmpStreamStatus,\n PhotoTaken,\n ManagedStreamStatus,\n PhoneNotificationDismissed,\n Capabilities,\n TouchEvent,\n createTouchEventStream,\n} from \"../../types\"\nimport {DashboardMode} from \"../../types/dashboard\"\nimport {PermissionErrorDetail} from \"../../types/messages/cloud-to-app\"\nimport {calendarWarnLog, microPhoneWarnLog} from \"../../utils/permissions-utils\"\n\n/** 🎯 Type-safe event handler function */\ntype Handler<T> = (data: T) => void\n\n/** 🔄 System events not tied to streams */\ninterface SystemEvents {\n connected: AppSettings | undefined\n disconnected:\n | string\n | {\n message: string // Human-readable close message\n code: number // WebSocket close code (1000 = normal)\n reason: string // Reason provided by server\n wasClean: boolean // Whether this was a clean closure\n permanent?: boolean // Whether this is a permanent disconnection (no more reconnection attempts)\n sessionEnded?: boolean // Whether this disconnection is due to user session ending\n }\n error: WebSocketError | Error\n settings_update: AppSettings\n capabilities_update: {\n capabilities: Capabilities | null\n modelName: string | null\n timestamp?: Date\n }\n dashboard_mode_change: {mode: DashboardMode | \"none\"}\n dashboard_always_on_change: {enabled: boolean}\n custom_message: CustomMessage\n permission_error: {\n message: string\n details: PermissionErrorDetail[]\n timestamp?: Date\n }\n permission_denied: {\n stream: string\n requiredPermission: string\n message: string\n }\n}\n\n/** 📡 All possible event types */\ntype EventType = ExtendedStreamType | keyof SystemEvents\n\n/** 📦 Map of stream types to their data types */\nexport interface StreamDataTypes {\n [StreamType.BUTTON_PRESS]: ButtonPress\n [StreamType.HEAD_POSITION]: HeadPosition\n [StreamType.PHONE_NOTIFICATION]: PhoneNotification\n [StreamType.TRANSCRIPTION]: TranscriptionData\n [StreamType.TRANSLATION]: TranslationData\n [StreamType.GLASSES_BATTERY_UPDATE]: GlassesBatteryUpdate\n [StreamType.PHONE_BATTERY_UPDATE]: PhoneBatteryUpdate\n [StreamType.GLASSES_CONNECTION_STATE]: GlassesConnectionState\n [StreamType.LOCATION_UPDATE]: LocationUpdate\n [StreamType.CALENDAR_EVENT]: CalendarEvent\n [StreamType.VAD]: Vad\n [StreamType.PHONE_NOTIFICATION_DISMISSED]: PhoneNotificationDismissed\n [StreamType.AUDIO_CHUNK]: AudioChunk\n [StreamType.VIDEO]: ArrayBuffer\n [StreamType.RTMP_STREAM_STATUS]: RtmpStreamStatus\n [StreamType.MANAGED_STREAM_STATUS]: ManagedStreamStatus\n [StreamType.VPS_COORDINATES]: VpsCoordinates\n [StreamType.PHOTO_TAKEN]: PhotoTaken\n [StreamType.OPEN_DASHBOARD]: never\n [StreamType.START_APP]: never\n [StreamType.STOP_APP]: never\n [StreamType.ALL]: never\n [StreamType.WILDCARD]: never\n}\n\n/** 📦 Data type for an event */\nexport type EventData<T extends EventType> = T extends keyof StreamDataTypes\n ? StreamDataTypes[T]\n : T extends keyof SystemEvents\n ? SystemEvents[T]\n : T extends string\n ? T extends `${StreamType.TRANSCRIPTION}:${string}`\n ? TranscriptionData\n : T extends `${StreamType.TRANSLATION}:${string}`\n ? TranslationData\n : never\n : never\n\nexport class EventManager {\n private emitter: EventEmitter\n private handlers: Map<EventType, Set<Handler<unknown>>>\n private lastLanguageTranscriptioCleanupHandler: () => void\n private lastLanguageTranslationCleanupHandler: () => void\n\n constructor(\n private subscribe: (type: ExtendedStreamType) => void,\n private unsubscribe: (type: ExtendedStreamType) => void,\n private packageName: string,\n private baseUrl: string,\n ) {\n this.emitter = new EventEmitter()\n this.handlers = new Map()\n this.lastLanguageTranscriptioCleanupHandler = () => {}\n this.lastLanguageTranslationCleanupHandler = () => {}\n }\n\n // Convenience handlers for common event types\n\n onTranscription(handler: Handler<TranscriptionData>) {\n // Only make the API call if we have a base URL (server-side environment)\n microPhoneWarnLog(this.baseUrl, this.packageName, this.onTranscription.name)\n\n return this.addHandler(createTranscriptionStream(\"en-US\"), handler)\n }\n\n /**\n * 🎤 Listen for transcription events in a specific language\n * @param language - Language code (e.g., \"en-US\") or \"auto\" for automatic detection\n * @param handler - Function to handle transcription data\n * @param optionsOrBoolean - Optional configuration object or boolean (backward compatible)\n * @param optionsOrBoolean.disableLanguageIdentification - Disable language identification (defaults to false/enabled)\n * @param optionsOrBoolean.hints - Array of language code hints to improve detection (e.g., [\"es\", \"fr\"])\n * @returns Cleanup function to remove the handler\n * @throws Error if language code is invalid\n */\n onTranscriptionForLanguage(\n language: string,\n handler: Handler<TranscriptionData>,\n optionsOrBoolean?:\n | boolean\n | {\n disableLanguageIdentification?: boolean\n hints?: string[]\n },\n ): () => void {\n if (language !== \"auto\" && !isValidLanguageCode(language)) {\n throw new Error(`Invalid language code: ${language}`)\n }\n this.lastLanguageTranscriptioCleanupHandler()\n\n // Handle backward compatibility: boolean or options object\n const options =\n typeof optionsOrBoolean === \"boolean\" ? {disableLanguageIdentification: optionsOrBoolean} : optionsOrBoolean\n\n const streamType = createTranscriptionStream(language, options)\n this.lastLanguageTranscriptioCleanupHandler = this.addHandler(streamType, handler)\n return this.lastLanguageTranscriptioCleanupHandler\n }\n\n /**\n * 🌐 Listen for translation events for a specific language pair\n * @param sourceLanguage - Source language code (e.g., \"es-ES\")\n * @param targetLanguage - Target language code (e.g., \"en-US\")\n * @param handler - Function to handle translation data\n * @returns Cleanup function to remove the handler\n * @throws Error if language codes are invalid\n */\n ontranslationForLanguage(\n sourceLanguage: string,\n targetLanguage: string,\n handler: Handler<TranslationData>,\n ): () => void {\n microPhoneWarnLog(this.baseUrl || \"\", this.packageName, this.ontranslationForLanguage.name)\n if (!isValidLanguageCode(sourceLanguage)) {\n throw new Error(`Invalid source language code: ${sourceLanguage}`)\n }\n if (!isValidLanguageCode(targetLanguage)) {\n throw new Error(`Invalid target language code: ${targetLanguage}`)\n }\n\n this.lastLanguageTranslationCleanupHandler()\n const streamType = createTranslationStream(sourceLanguage, targetLanguage)\n this.lastLanguageTranslationCleanupHandler = this.addHandler(streamType, handler)\n\n return this.lastLanguageTranslationCleanupHandler\n }\n\n onHeadPosition(handler: Handler<HeadPosition>) {\n return this.addHandler(StreamType.HEAD_POSITION, handler)\n }\n\n onButtonPress(handler: Handler<ButtonPress>) {\n return this.addHandler(StreamType.BUTTON_PRESS, handler)\n }\n\n onTouchEvent(gestureOrHandler: string | Handler<TouchEvent>, handler?: Handler<TouchEvent>): () => void {\n // Handle both: onTouchEvent(handler) and onTouchEvent(\"forward_swipe\", handler)\n if (typeof gestureOrHandler === \"function\") {\n // Subscribe to all touch events\n return this.addHandler(StreamType.TOUCH_EVENT, gestureOrHandler)\n } else {\n // Subscribe to specific gesture\n const gestureStream = createTouchEventStream(gestureOrHandler)\n return this.addHandler(gestureStream, handler!)\n }\n }\n\n onPhoneNotifications(handler: Handler<PhoneNotification>) {\n return this.addHandler(StreamType.PHONE_NOTIFICATION, handler)\n }\n\n onPhoneNotificationDismissed(handler: Handler<PhoneNotificationDismissed>) {\n return this.addHandler(StreamType.PHONE_NOTIFICATION_DISMISSED, handler)\n }\n\n onGlassesBattery(handler: Handler<GlassesBatteryUpdate>) {\n return this.addHandler(StreamType.GLASSES_BATTERY_UPDATE, handler)\n }\n\n onPhoneBattery(handler: Handler<PhoneBatteryUpdate>) {\n return this.addHandler(StreamType.PHONE_BATTERY_UPDATE, handler)\n }\n\n onVoiceActivity(handler: Handler<Vad>) {\n microPhoneWarnLog(this.baseUrl || \"\", this.packageName, this.onVoiceActivity.name)\n return this.addHandler(StreamType.VAD, handler)\n }\n\n onLocation(handler: Handler<LocationUpdate>) {\n return this.addHandler(StreamType.LOCATION_UPDATE, handler)\n }\n\n onCalendarEvent(handler: Handler<CalendarEvent>) {\n return this.addHandler(StreamType.CALENDAR_EVENT, handler)\n }\n\n /**\n * 🎤 Listen for audio chunk data\n * @param handler - Function to handle audio chunks\n * @returns Cleanup function to remove the handler\n */\n onAudioChunk(handler: Handler<AudioChunk>) {\n return this.addHandler(StreamType.AUDIO_CHUNK, handler)\n }\n\n // System event handlers\n\n onConnected(handler: Handler<SystemEvents[\"connected\"]>) {\n this.emitter.on(\"connected\", handler)\n return () => this.emitter.off(\"connected\", handler)\n }\n\n onDisconnected(handler: Handler<SystemEvents[\"disconnected\"]>) {\n this.emitter.on(\"disconnected\", handler)\n return () => this.emitter.off(\"disconnected\", handler)\n }\n\n onError(handler: Handler<SystemEvents[\"error\"]>) {\n this.emitter.on(\"error\", handler)\n return () => this.emitter.off(\"error\", handler)\n }\n\n onSettingsUpdate(handler: Handler<SystemEvents[\"settings_update\"]>) {\n this.emitter.on(\"settings_update\", handler)\n return () => this.emitter.off(\"settings_update\", handler)\n }\n\n /**\n * 🔧 Listen for device capabilities updates\n * @param handler - Function to handle capabilities updates\n * @returns Cleanup function to remove the handler\n */\n onCapabilitiesUpdate(handler: Handler<SystemEvents[\"capabilities_update\"]>) {\n this.emitter.on(\"capabilities_update\", handler)\n return () => this.emitter.off(\"capabilities_update\", handler)\n }\n\n /**\n * 🌐 Listen for dashboard mode changes\n * @param handler - Function to handle dashboard mode changes\n * @returns Cleanup function to remove the handler\n */\n onDashboardModeChange(handler: Handler<SystemEvents[\"dashboard_mode_change\"]>) {\n this.emitter.on(\"dashboard_mode_change\", handler)\n return () => this.emitter.off(\"dashboard_mode_change\", handler)\n }\n\n /**\n * 🌐 Listen for dashboard always-on mode changes\n * @param handler - Function to handle dashboard always-on mode changes\n * @returns Cleanup function to remove the handler\n */\n onDashboardAlwaysOnChange(handler: Handler<SystemEvents[\"dashboard_always_on_change\"]>) {\n this.emitter.on(\"dashboard_always_on_change\", handler)\n return () => this.emitter.off(\"dashboard_always_on_change\", handler)\n }\n\n /**\n * 🚫 Listen for permission errors when subscriptions are rejected\n * @param handler - Function to handle permission errors\n * @returns Cleanup function to remove the handler\n */\n onPermissionError(handler: Handler<SystemEvents[\"permission_error\"]>) {\n this.emitter.on(\"permission_error\", handler)\n return () => this.emitter.off(\"permission_error\", handler)\n }\n\n /**\n * 🚫 Listen for individual permission denied events for specific streams\n * @param handler - Function to handle permission denied events\n * @returns Cleanup function to remove the handler\n */\n onPermissionDenied(handler: Handler<SystemEvents[\"permission_denied\"]>) {\n this.emitter.on(\"permission_denied\", handler)\n return () => this.emitter.off(\"permission_denied\", handler)\n }\n\n /**\n * 🔄 Listen for changes to a specific setting\n * @param key - Setting key to monitor\n * @param handler - Function to handle setting value changes\n * @returns Cleanup function to remove the handler\n */\n onSettingChange<T>(key: string, handler: (value: T, previousValue: T | undefined) => void): () => void {\n let previousValue: T | undefined = undefined\n\n const settingsHandler = (settings: AppSettings) => {\n try {\n const setting = settings.find((s) => s.key === key)\n if (setting) {\n // Only call handler if value has changed\n if (setting.value !== previousValue) {\n const newValue = setting.value as T\n handler(newValue, previousValue)\n previousValue = newValue\n }\n }\n } catch (error: unknown) {\n console.error(`Error in onSettingChange handler for key \"${key}\":`, error)\n }\n }\n\n this.emitter.on(\"settings_update\", settingsHandler)\n this.emitter.on(\"connected\", settingsHandler) // Also check when first connected\n\n return () => {\n this.emitter.off(\"settings_update\", settingsHandler)\n this.emitter.off(\"connected\", settingsHandler)\n }\n }\n\n /**\n * 🔄 Generic event handler\n *\n * Use this for stream types without specific handler methods\n */\n on<T extends ExtendedStreamType>(type: T, handler: Handler<EventData<T>>): () => void {\n // Check permissions for specific stream types\n if (type === StreamType.CALENDAR_EVENT) {\n calendarWarnLog(this.baseUrl, this.packageName, \"on\")\n }\n return this.addHandler(type, handler)\n }\n\n /**\n * ➕ Add an event handler and subscribe if needed\n */\n private addHandler<T extends ExtendedStreamType>(type: T, handler: Handler<EventData<T>>): () => void {\n const handlers = this.handlers.get(type) ?? new Set()\n\n if (handlers.size === 0) {\n this.handlers.set(type, handlers)\n this.subscribe(type)\n }\n handlers.add(handler as Handler<unknown>)\n return () => this.removeHandler(type, handler)\n }\n\n /**\n * ➖ Remove an event handler\n */\n private removeHandler<T extends ExtendedStreamType>(type: T, handler: Handler<EventData<T>>): void {\n const handlers = this.handlers.get(type)\n if (!handlers) return\n\n handlers.delete(handler as Handler<unknown>)\n if (handlers.size === 0) {\n this.handlers.delete(type)\n this.unsubscribe(type)\n }\n }\n\n /**\n * 📡 Emit an event to all registered handlers with error isolation\n */\n emit<T extends EventType>(event: T, data: EventData<T>): void {\n try {\n // Emit to EventEmitter handlers (system events)\n // console.log(`#### Emitting to ${event}`);\n this.emitter.emit(event, data)\n\n // Emit to stream handlers if applicable\n const handlers = this.handlers.get(event)\n // console.log(`#### Handlers: ${JSON.stringify(handlers)}`);\n\n if (handlers) {\n // Create array of handlers to prevent modification during iteration\n const handlersArray = Array.from(handlers)\n // console.log(`((())) HandlersArray: ${JSON.stringify(handlersArray)}`);\n\n // Execute each handler in isolated try/catch to prevent one handler\n // from crashing the entire App\n handlersArray.forEach((handler) => {\n try {\n ;(handler as Handler<EventData<T>>)(data)\n } catch (handlerError: unknown) {\n // Log the error but don't let it propagate\n console.error(`Error in handler for event '${String(event)}':`, handlerError)\n\n // Emit an error event for tracking purposes\n if (event !== \"error\") {\n // Prevent infinite recursion\n const errorMessage = handlerError instanceof Error ? handlerError.message : String(handlerError)\n\n this.emitter.emit(\"error\", new Error(`Handler error for event '${String(event)}': ${errorMessage}`))\n }\n }\n })\n }\n } catch (emitError: unknown) {\n // Catch any errors in the emission process itself\n console.error(`Fatal error emitting event '${String(event)}':`, emitError)\n\n // Try to emit an error event if we're not already handling an error\n if (event !== \"error\") {\n try {\n const errorMessage = emitError instanceof Error ? emitError.message : String(emitError)\n\n this.emitter.emit(\"error\", new Error(`Event emission error for '${String(event)}': ${errorMessage}`))\n } catch (nestedError) {\n // If even this fails, just log it - nothing more we can do\n console.error(\"Failed to emit error event:\", nestedError)\n }\n }\n }\n }\n\n /**\n * 📨 Listen for custom messages with a specific action\n * @param action - The action identifier to filter by\n * @param handler - Function to handle the message\n * @returns Cleanup function to remove the handler\n */\n onCustomMessage(action: string, handler: (payload: any) => void): () => void {\n const messageHandler = (message: CustomMessage) => {\n if (message.action === action) {\n handler(message.payload)\n }\n }\n\n this.emitter.on(\"custom_message\", messageHandler)\n return () => this.emitter.off(\"custom_message\", messageHandler)\n }\n\n onVpsCoordinates(handler: Handler<VpsCoordinates>) {\n return this.addHandler(StreamType.VPS_COORDINATES, handler)\n }\n\n /**\n * 📸 Listen for photo responses\n * @param handler - Function to handle photo response data\n * @returns Cleanup function to remove the handler\n */\n onPhotoTaken(handler: Handler<PhotoTaken>) {\n return this.addHandler(StreamType.PHOTO_TAKEN, handler)\n }\n}\n",
|
|
23
|
+
"// src/index.ts\n\nexport * from \"./token\"\n\n// Message type enums\nexport * from \"./message-types\"\n\n// Base message type\nexport * from \"./messages/base\"\n\n// Messages by direction - export everything except the conflicting type guards\nexport * from \"./messages/glasses-to-cloud\"\nexport * from \"./messages/cloud-to-glasses\"\n\n// Export from app-to-cloud excluding isPhotoRequest which conflicts with cloud-to-glasses\nexport {\n // Types\n SubscriptionRequest,\n AppConnectionInit,\n AppSubscriptionUpdate,\n PhotoRequest,\n RgbLedControlRequest,\n RtmpStreamRequest,\n RtmpStreamStopRequest,\n AppLocationPollRequest,\n RestreamDestination,\n ManagedStreamRequest,\n ManagedStreamStopRequest,\n StreamStatusCheckRequest,\n AudioPlayRequest,\n AudioStopRequest,\n AppToCloudMessage,\n AppBroadcastMessage,\n AppDirectMessage,\n AppUserDiscovery,\n AppRoomJoin,\n AppRoomLeave,\n RequestWifiSetup,\n // Type guards - all except isPhotoRequest\n isAppConnectionInit,\n isAppSubscriptionUpdate,\n isDisplayRequest,\n isRgbLedControlRequest,\n isAudioPlayRequest,\n isAudioStopRequest,\n isDashboardContentUpdate,\n isDashboardModeChange,\n isDashboardSystemUpdate,\n isManagedStreamRequest,\n isManagedStreamStopRequest,\n isRtmpStreamRequest,\n isRtmpStreamStopRequest,\n // Export with alias to avoid conflict\n isPhotoRequest as isPhotoRequestFromApp,\n} from \"./messages/app-to-cloud\"\n\n// Export cloud-to-app but exclude the conflicting type guards\nexport {\n // Types\n AppConnectionAck,\n AppConnectionError,\n AppStopped,\n SettingsUpdate as AppSettingsUpdate, // Alias to avoid conflict with cloud-to-glasses SettingsUpdate\n CapabilitiesUpdate,\n DataStream,\n CloudToAppMessage,\n AudioPlayResponse,\n TranslationData,\n ToolCall,\n StandardConnectionError,\n CustomMessage,\n ManagedStreamStatus,\n StreamStatusCheckResponse,\n OutputStatus,\n MentraosSettingsUpdate,\n TranscriptionData,\n TranscriptionMetadata,\n SonioxToken,\n AudioChunk,\n PermissionError,\n PermissionErrorDetail,\n // Type guards (excluding isPhotoResponse and isRtmpStreamStatus which conflict)\n isAppConnectionAck,\n isAppConnectionError,\n isAppStopped,\n isSettingsUpdate,\n isCapabilitiesUpdate,\n isDataStream,\n isAudioChunk,\n isAudioPlayResponse,\n isDashboardModeChanged,\n isDashboardAlwaysOnChanged,\n isManagedStreamStatus,\n isStreamStatusCheckResponse,\n // Re-export the cloud-to-app versions of these type guards since they're the ones\n // that should be used when dealing with CloudToAppMessage types\n isPhotoResponse as isPhotoResponseFromCloud,\n isRtmpStreamStatus as isRtmpStreamStatusFromCloud,\n isRgbLedControlResponse as isRgbLedControlResponseFromCloud,\n} from \"./messages/cloud-to-app\"\n\n// Stream types\nexport * from \"./streams\"\n\n// Layout types\nexport * from \"./layouts\"\n\n// Dashboard types\nexport * from \"./dashboard\"\n\n// RTMP streaming types\nexport * from \"./rtmp-stream\"\n\n// Other system enums\nexport * from \"./enums\"\n\n// Core model interfaces\nexport * from \"./models\"\n\n// Webhook interfaces\nexport * from \"./webhooks\"\n\n// Capability Discovery types\nexport * from \"./capabilities\"\n\n// Photo data types\nexport * from \"./photo-data\"\n\n/**\n * WebSocket error information\n */\nexport interface WebSocketError {\n code: string\n message: string\n details?: unknown\n}\n\nimport type {Request} from \"express\"\nimport type {AppSession} from \"../app/session\"\n\nexport interface AuthenticatedRequest extends Request {\n authUserId?: string\n activeSession: AppSession | null\n}\n",
|
|
24
|
+
"/**\n * warning.ts\n *\n * This file defines styled warning messages that are displayed when an app\n * attempts to use functionality requiring permissions it hasn't declared.\n *\n * Each function generates a bordered terminal warning box with:\n * - ASCII art logo (left side)\n * - Permission requirement details (right side)\n * - Link to developer portal for adding permissions\n *\n * The warnings use chalk for terminal colors and boxen for bordered output,\n * creating a professional side-by-side layout that alerts developers to\n * missing permissions in their app configuration.\n *\n * These are shown during SDK runtime when permission checks fail, helping\n * developers identify and fix permission issues quickly.\n */\nimport chalk from \"chalk\";\nimport boxen from \"boxen\";\nimport { warnLog } from \"./logos\";\n\nconst createPermissionWarning = (\n permissionName: string,\n funcName?: string,\n packageName?: string,\n): string => {\n // Strip ANSI codes for width calculation\n // eslint-disable-next-line no-control-regex\n const stripAnsi = (str: string) => str.replace(/\\u001b\\[\\d+m/g, \"\");\n\n const title = chalk.bold.yellow(\"⚠️ Permission Required\");\n const message = `${chalk.yellow(funcName || \"This function\")} requires ${chalk.bold(permissionName)} permission.`;\n const instructions = chalk.dim(\n \"Please enable this permission in the developer portal at:\",\n );\n const url = chalk.cyan.underline(\n `https://console.mentra.glass/apps/${packageName}/edit`,\n );\n const hint = chalk.dim(\"under *Required Permissions*.\");\n\n // Split logo into lines (without color for width calculation)\n const logoLines = warnLog.split(\"\\n\");\n const coloredLogoLines = logoLines.map((line) => chalk.yellow(line));\n\n const textContent = [title, \"\", message, \"\", instructions, url, \"\", hint];\n\n // Find actual logo width (max line length without ANSI codes)\n const logoWidth = Math.max(\n ...logoLines.map((line) => stripAnsi(line).length),\n );\n\n // Create side-by-side layout\n const maxLines = Math.max(coloredLogoLines.length, textContent.length);\n const combinedLines: string[] = [];\n\n for (let i = 0; i < maxLines; i++) {\n const rawLogoLine = logoLines[i] || \"\";\n const coloredLogoLine = coloredLogoLines[i] || \"\";\n const actualLogoLength = stripAnsi(rawLogoLine).length;\n const padding = \" \".repeat(Math.max(0, logoWidth - actualLogoLength));\n const textLine = textContent[i] || \"\";\n combinedLines.push(`${coloredLogoLine}${padding} ${textLine}`);\n }\n\n return boxen(combinedLines.join(\"\\n\"), {\n padding: 1,\n margin: 1,\n borderStyle: \"round\",\n borderColor: \"yellow\",\n float: \"left\",\n });\n};\n\nexport const noMicrophoneWarn = (\n funcName?: string,\n packageName?: string,\n): string => {\n return createPermissionWarning(\"microphone\", funcName, packageName);\n};\n\nexport const locationWarn = (\n funcName?: string,\n packageName?: string,\n): string => {\n return createPermissionWarning(\"location\", funcName, packageName);\n};\n\nexport const baackgroundLocationWarn = (\n funcName?: string,\n packageName?: string,\n): string => {\n return createPermissionWarning(\"background location\", funcName, packageName);\n};\n\nexport const calendarWarn = (\n funcName?: string,\n packageName?: string,\n): string => {\n return createPermissionWarning(\"calendar\", funcName, packageName);\n};\n\nexport const readNotficationWarn = (\n funcName?: string,\n packageName?: string,\n): string => {\n return createPermissionWarning(\"read notification\", funcName, packageName);\n};\n\nexport const postNotficationWarn = (\n funcName?: string,\n packageName?: string,\n): string => {\n return createPermissionWarning(\"post notification\", funcName, packageName);\n};\n\nexport const cameraWarn = (funcName?: string, packageName?: string): string => {\n return createPermissionWarning(\"camera\", funcName, packageName);\n};\n",
|
|
25
|
+
"export const warnLog = String.raw`\n__/\\\\\\\\____________/\\\\\\\\_ \n _\\/\\\\\\\\\\\\________/\\\\\\\\\\\\_ \n _\\/\\\\\\//\\\\\\____/\\\\\\//\\\\\\_ \n _\\/\\\\\\\\///\\\\\\/\\\\\\/_\\/\\\\\\_ \n _\\/\\\\\\__\\///\\\\\\/___\\/\\\\\\_ \n _\\/\\\\\\____\\///_____\\/\\\\\\_ \n _\\/\\\\\\_____________\\/\\\\\\_ \n _\\/\\\\\\_____________\\/\\\\\\_ \n _\\///______________\\///__`;\n\nexport const mentraLogo_1 = String.raw`\n __ __ ________ __ __ ________ _______ ______ \n| \\ / \\| \\| \\ | \\| \\| \\ / \\ \n| $$\\ / $$| $$$$$$$$| $$\\ | $$ \\$$$$$$$$| $$$$$$$\\| $$$$$$\\\n| $$$\\ / $$$| $$__ | $$$\\| $$ | $$ | $$__| $$| $$__| $$\n| $$$$\\ $$$$| $$ \\ | $$$$\\ $$ | $$ | $$ $$| $$ $$\n| $$\\$$ $$ $$| $$$$$ | $$\\$$ $$ | $$ | $$$$$$$\\| $$$$$$$$\n| $$ \\$$$| $$| $$_____ | $$ \\$$$$ | $$ | $$ | $$| $$ | $$\n| $$ \\$ | $$| $$ \\| $$ \\$$$ | $$ | $$ | $$| $$ | $$\n \\$$ \\$$ \\$$$$$$$$ \\$$ \\$$ \\$$ \\$$ \\$$ \\$$ \\$$\n`;\n\nexport const newUpdateText = `\n┬╔╗╔╔═╗╦ ╦ ╦ ╦╔═╗╔╦╗╔═╗╔╦╗╔═╗┬\n│║║║║╣ ║║║ ║ ║╠═╝ ║║╠═╣ ║ ║╣ │\no╝╚╝╚═╝╚╩╝ ╚═╝╩ ═╩╝╩ ╩ ╩ ╚═╝o\n`;\n// export const newSDKUpdate = (versionNumb: string): string => {\n// return `\n\n// /$$ /$$ /$$$$$$$$ /$$ /$$ /$$$$$$$$ /$$$$$$$ /$$$$$$\n// | $$$ /$$$| $$_____/| $$$ | $$|__ $$__/| $$__ $$ /$$__ $$\n// | $$$$ /$$$$| $$ | $$$$| $$ | $$ | $$ \\ $$| $$ \\ $$\n// | $$ $$/$$ $$| $$$$$ | $$ $$ $$ | $$ | $$$$$$$/| $$$$$$$$\n// | $$ $$$| $$| $$__/ | $$ $$$$ | $$ | $$__ $$| $$__ $$\n// | $$\\ $ | $$| $$ | $$\\ $$$ | $$ | $$ \\ $$| $$ | $$\n// | $$ \\/ | $$| $$$$$$$$| $$ \\ $$ | $$ | $$ | $$| $$ | $$\n// |__/ |__/|________/|__/ \\__/ |__/ |__/ |__/|__/ |__/\n\n// ┬╔╗╔╔═╗╦ ╦ ╦ ╦╔═╗╔╦╗╔═╗╔╦╗╔═╗┬\n// │║║║║╣ ║║║ ║ ║╠═╝ ║║╠═╣ ║ ║╣ │\n// o╝╚╝╚═╝╚╩╝ ╚═╝╩ ═╩╝╩ ╩ ╩ ╚═╝o\n// -------------------------------\n// SDK VERSION V${versionNumb} is out!\n// -------------------------------\n// bun install @mentra/sdk@latest\n// `;\n// };\n",
|
|
26
|
+
"/**\n * permissions-utils.ts\n *\n * This file provides runtime permission validation utilities for the MentraOS SDK.\n *\n * Each function queries the public permissions API endpoint to check if an app\n * has declared the required permission for a specific feature. If the permission\n * is missing, a styled warning message is displayed in the terminal.\n *\n * Key features:\n * - Fetches app permissions from /api/public/permissions/:packageName\n * - Gracefully handles offline/unreachable endpoints (silent failure)\n * - Displays professional bordered warnings when permissions are missing\n * - Non-blocking - allows app execution to continue even if checks fail\n *\n * These functions are called automatically by SDK methods that require specific\n * permissions (e.g., microphone access, location tracking, camera, etc.) to help\n * developers identify missing permission declarations during development.\n */\nimport {\n noMicrophoneWarn,\n locationWarn,\n baackgroundLocationWarn,\n calendarWarn,\n readNotficationWarn,\n postNotficationWarn,\n cameraWarn,\n} from \"../constants/log-messages/warning\";\nimport {\n PackagePermissions,\n Permission,\n} from \"../../src/types/messages/cloud-to-app\";\n// Check if app has microphone permission, warn if missing\nexport const microPhoneWarnLog = (\n cloudServerUrl: string,\n packageName: string,\n funcName?: string,\n) => {\n if (!cloudServerUrl) return;\n\n const permissionsUrl = `${cloudServerUrl}/api/public/permissions/${encodeURIComponent(packageName)}`;\n\n // console.log(`Fetching permissions from: ${permissionsUrl}`);\n fetch(permissionsUrl)\n .then(async (res) => {\n const contentType = res.headers.get(\"content-type\");\n if (!res.ok) {\n console.warn(\n `Permission API returned ${res.status}: ${res.statusText}`,\n );\n return null;\n }\n\n if (contentType && contentType.includes(\"application/json\")) {\n return (await res.json()) as PackagePermissions;\n } else {\n const text = await res.text();\n console.warn(`Permission API returned non-JSON response: ${text}`);\n return null;\n }\n })\n .then((data: PackagePermissions | null) => {\n if (data) {\n const hasMic = data.permissions.some(\n (p: Permission) => p.type === \"MICROPHONE\",\n );\n\n if (!hasMic) {\n console.log(noMicrophoneWarn(funcName, packageName));\n }\n }\n })\n .catch((err) => {\n // Silently fail if endpoint is unreachable - don't block execution\n console.debug(\n \"Permission check skipped - endpoint unreachable:\",\n err.message,\n );\n });\n};\n\n// Check if app has location permission, warn if missing\nexport const locationWarnLog = (\n cloudServerUrl: string,\n packageName: string,\n funcName?: string,\n) => {\n if (!cloudServerUrl) return;\n\n const permissionsUrl = `${cloudServerUrl}/api/public/permissions/${encodeURIComponent(packageName)}`;\n\n fetch(permissionsUrl)\n .then(async (res) => {\n const contentType = res.headers.get(\"content-type\");\n if (!res.ok) {\n console.warn(\n `Permission API returned ${res.status}: ${res.statusText}`,\n );\n return null;\n }\n\n if (contentType && contentType.includes(\"application/json\")) {\n return (await res.json()) as PackagePermissions;\n } else {\n const text = await res.text();\n console.warn(`Permission API returned non-JSON response: ${text}`);\n return null;\n }\n })\n .then((data: PackagePermissions | null) => {\n if (data) {\n const hasLocation = data.permissions.some(\n (p: Permission) => p.type === \"LOCATION\",\n );\n\n if (!hasLocation) {\n console.log(locationWarn(funcName, packageName));\n }\n }\n })\n .catch((err) => {\n console.debug(\n \"Permission check skipped - endpoint unreachable:\",\n err.message,\n );\n });\n};\n\n// Check if app has background location permission, warn if missing\nexport const backgroundLocationWarnLog = (\n cloudServerUrl: string,\n packageName: string,\n funcName?: string,\n) => {\n if (!cloudServerUrl) return;\n\n const permissionsUrl = `${cloudServerUrl}/api/public/permissions/${encodeURIComponent(packageName)}`;\n\n fetch(permissionsUrl)\n .then(async (res) => {\n const contentType = res.headers.get(\"content-type\");\n if (!res.ok) {\n console.warn(\n `Permission API returned ${res.status}: ${res.statusText}`,\n );\n return null;\n }\n\n if (contentType && contentType.includes(\"application/json\")) {\n return (await res.json()) as PackagePermissions;\n } else {\n const text = await res.text();\n console.warn(`Permission API returned non-JSON response: ${text}`);\n return null;\n }\n })\n .then((data: PackagePermissions | null) => {\n if (data) {\n const hasBackgroundLocation = data.permissions.some(\n (p: Permission) => p.type === \"BACKGROUND_LOCATION\",\n );\n\n if (!hasBackgroundLocation) {\n console.log(baackgroundLocationWarn(funcName, packageName));\n }\n }\n })\n .catch((err) => {\n console.debug(\n \"Permission check skipped - endpoint unreachable:\",\n err.message,\n );\n });\n};\n\n// Check if app has calendar permission, warn if missing\nexport const calendarWarnLog = (\n cloudServerUrl: string,\n packageName: string,\n funcName?: string,\n) => {\n if (!cloudServerUrl) return;\n\n const permissionsUrl = `${cloudServerUrl}/api/public/permissions/${encodeURIComponent(packageName)}`;\n\n fetch(permissionsUrl)\n .then(async (res) => {\n const contentType = res.headers.get(\"content-type\");\n if (!res.ok) {\n console.warn(\n `Permission API returned ${res.status}: ${res.statusText}`,\n );\n return null;\n }\n\n if (contentType && contentType.includes(\"application/json\")) {\n return (await res.json()) as PackagePermissions;\n } else {\n const text = await res.text();\n console.warn(`Permission API returned non-JSON response: ${text}`);\n return null;\n }\n })\n .then((data: PackagePermissions | null) => {\n if (data) {\n const hasCalendar = data.permissions.some(\n (p: Permission) => p.type === \"CALENDAR\",\n );\n\n if (!hasCalendar) {\n console.log(calendarWarn(funcName, packageName));\n }\n }\n })\n .catch((err) => {\n console.debug(\n \"Permission check skipped - endpoint unreachable:\",\n err.message,\n );\n });\n};\n\n// Check if app has read notifications permission, warn if missing\nexport const readNotificationWarnLog = (\n cloudServerUrl: string,\n packageName: string,\n funcName?: string,\n) => {\n if (!cloudServerUrl) return;\n\n const permissionsUrl = `${cloudServerUrl}/api/public/permissions/${encodeURIComponent(packageName)}`;\n\n fetch(permissionsUrl)\n .then(async (res) => {\n const contentType = res.headers.get(\"content-type\");\n if (!res.ok) {\n console.warn(\n `Permission API returned ${res.status}: ${res.statusText}`,\n );\n return null;\n }\n\n if (contentType && contentType.includes(\"application/json\")) {\n return (await res.json()) as PackagePermissions;\n } else {\n const text = await res.text();\n console.warn(`Permission API returned non-JSON response: ${text}`);\n return null;\n }\n })\n .then((data: PackagePermissions | null) => {\n if (data) {\n const hasReadNotifications = data.permissions.some(\n (p: Permission) => p.type === \"READ_NOTIFICATIONS\",\n );\n\n if (!hasReadNotifications) {\n console.log(readNotficationWarn(funcName, packageName));\n }\n }\n })\n .catch((err) => {\n console.debug(\n \"Permission check skipped - endpoint unreachable:\",\n err.message,\n );\n });\n};\n\n// Check if app has post notifications permission, warn if missing\nexport const postNotificationWarnLog = (\n cloudServerUrl: string,\n packageName: string,\n funcName?: string,\n) => {\n if (!cloudServerUrl) return;\n\n const permissionsUrl = `${cloudServerUrl}/api/public/permissions/${encodeURIComponent(packageName)}`;\n\n fetch(permissionsUrl)\n .then(async (res) => {\n const contentType = res.headers.get(\"content-type\");\n if (!res.ok) {\n console.warn(\n `Permission API returned ${res.status}: ${res.statusText}`,\n );\n return null;\n }\n\n if (contentType && contentType.includes(\"application/json\")) {\n return (await res.json()) as PackagePermissions;\n } else {\n const text = await res.text();\n console.warn(`Permission API returned non-JSON response: ${text}`);\n return null;\n }\n })\n .then((data: PackagePermissions | null) => {\n if (data) {\n const hasPostNotifications = data.permissions.some(\n (p: Permission) => p.type === \"POST_NOTIFICATIONS\",\n );\n\n if (!hasPostNotifications) {\n console.log(postNotficationWarn(funcName, packageName));\n }\n }\n })\n .catch((err) => {\n console.debug(\n \"Permission check skipped - endpoint unreachable:\",\n err.message,\n );\n });\n};\n\n// Check if app has camera permission, warn if missing\nexport const cameraWarnLog = (\n cloudServerUrl: string,\n packageName: string,\n funcName?: string,\n) => {\n if (!cloudServerUrl) return;\n\n const permissionsUrl = `${cloudServerUrl}/api/public/permissions/${encodeURIComponent(packageName)}`;\n\n fetch(permissionsUrl)\n .then(async (res) => {\n const contentType = res.headers.get(\"content-type\");\n if (!res.ok) {\n console.warn(\n `Permission API returned ${res.status}: ${res.statusText}`,\n );\n return null;\n }\n\n if (contentType && contentType.includes(\"application/json\")) {\n return (await res.json()) as PackagePermissions;\n } else {\n const text = await res.text();\n console.warn(`Permission API returned non-JSON response: ${text}`);\n return null;\n }\n })\n .then((data: PackagePermissions | null) => {\n if (data) {\n const hasCamera = data.permissions.some(\n (p: Permission) => p.type === \"CAMERA\",\n );\n\n if (!hasCamera) {\n console.log(cameraWarn(funcName, packageName));\n }\n }\n })\n .catch((err) => {\n console.debug(\n \"Permission check skipped - endpoint unreachable:\",\n err.message,\n );\n });\n};\n",
|
|
27
|
+
"/**\n * 🎨 Layout Manager Module\n *\n * Manages AR display layouts for Apps. This class provides an easy-to-use interface\n * for showing different types of content in the user's AR view.\n *\n * @example\n * ```typescript\n * const layouts = new LayoutManager('org.example.myapp', sendMessage);\n *\n * // Show a simple message\n * layouts.showTextWall('Hello AR World!');\n *\n * // Show a card with title\n * layouts.showReferenceCard('Weather', 'Sunny and 75°F');\n * ```\n */\nimport { BitmapUtils } from \"../../utils/bitmap-utils\";\nimport {\n DisplayRequest,\n Layout,\n TextWall,\n DoubleTextWall,\n ReferenceCard,\n DashboardCard,\n LayoutType,\n ViewType,\n AppToCloudMessageType,\n BitmapView,\n BitmapAnimation,\n ClearView,\n} from \"../../types\";\n\nexport class LayoutManager {\n /**\n * 🎯 Creates a new LayoutManager instance\n *\n * @param packageName - App package identifier\n * @param sendMessage - Function to send display requests to MentraOS\n */\n constructor(\n private packageName: string,\n private sendMessage: (message: DisplayRequest) => void,\n ) {}\n\n /**\n * 📦 Creates a display event request with validation\n *\n * @param layout - Layout configuration to display\n * @param view - View type (main or dashboard)\n * @param durationMs - How long to show the layout (optional)\n * @returns Formatted display request\n * @throws Error if layout is invalid\n */\n private createDisplayEvent(\n layout: Layout,\n view: ViewType = ViewType.MAIN,\n durationMs?: number,\n ): DisplayRequest {\n try {\n // Validate layout data before sending\n if (!layout) {\n throw new Error(\"Layout cannot be null or undefined\");\n }\n\n if (!layout.layoutType) {\n throw new Error(\"Layout must have a layoutType property\");\n }\n\n // Layout-specific validations\n switch (layout.layoutType) {\n case LayoutType.TEXT_WALL:\n if (typeof (layout as TextWall).text !== \"string\") {\n throw new Error(\"TextWall layout must have a text property\");\n }\n // Ensure text is not too long (prevent performance issues)\n if ((layout as TextWall).text.length > 1000) {\n console.warn(\n \"TextWall text is very long, this may cause performance issues\",\n );\n }\n break;\n\n case LayoutType.DOUBLE_TEXT_WALL:\n const doubleText = layout as DoubleTextWall;\n if (typeof doubleText.topText !== \"string\") {\n throw new Error(\n \"DoubleTextWall layout must have a topText property\",\n );\n }\n if (typeof doubleText.bottomText !== \"string\") {\n throw new Error(\n \"DoubleTextWall layout must have a bottomText property\",\n );\n }\n break;\n\n case LayoutType.REFERENCE_CARD:\n const refCard = layout as ReferenceCard;\n if (typeof refCard.title !== \"string\") {\n throw new Error(\"ReferenceCard layout must have a title property\");\n }\n if (typeof refCard.text !== \"string\") {\n throw new Error(\"ReferenceCard layout must have a text property\");\n }\n break;\n\n case LayoutType.DASHBOARD_CARD:\n const dashCard = layout as DashboardCard;\n if (typeof dashCard.leftText !== \"string\") {\n throw new Error(\n \"DashboardCard layout must have a leftText property\",\n );\n }\n if (typeof dashCard.rightText !== \"string\") {\n throw new Error(\n \"DashboardCard layout must have a rightText property\",\n );\n }\n break;\n\n case LayoutType.BITMAP_VIEW:\n const bitmapView = layout as BitmapView;\n if (typeof bitmapView.data !== \"string\") {\n throw new Error(\"BitmapView layout must have a data property\");\n }\n // Check if data is too large (prevent OOM errors)\n if (bitmapView.data.length > 1000000) {\n // 1MB limit\n throw new Error(\n \"Bitmap data is too large (>1MB), please reduce size\",\n );\n }\n break;\n\n case LayoutType.CLEAR_VIEW:\n // ClearView has no additional validation needed\n break;\n }\n\n // Validate view type\n if (view !== ViewType.MAIN && view !== ViewType.DASHBOARD) {\n console.warn(`Invalid view type: ${view}, defaulting to MAIN`);\n view = ViewType.MAIN;\n }\n\n // Validate duration if provided\n if (durationMs !== undefined) {\n if (typeof durationMs !== \"number\" || durationMs < 0) {\n console.warn(`Invalid duration: ${durationMs}, ignoring`);\n durationMs = undefined;\n }\n }\n\n // Create the display request with validated data\n return {\n timestamp: new Date(),\n sessionId: \"\", // Will be filled by session\n type: AppToCloudMessageType.DISPLAY_REQUEST,\n packageName: this.packageName,\n view,\n layout,\n durationMs,\n };\n } catch (error) {\n console.error(\"Error creating display event:\", error);\n throw error; // Re-throw to notify caller\n }\n }\n\n /**\n * 📝 Shows a single block of text\n *\n * Best for:\n * - Simple messages\n * - Status updates\n * - Notifications\n *\n * @param text - Text content to display\n * @param options - Optional parameters (view, duration, priority)\n * - priority: If true, this display will not be overridden by other requests (default: false)\n *\n * @example\n * ```typescript\n * layouts.showTextWall('Connected to server');\n * layouts.showTextWall('Onboarding!', { priority: true });\n * ```\n */\n showTextWall(\n text: string,\n options?: { view?: ViewType; durationMs?: number },\n ) {\n try {\n // Validate input before processing\n if (text === undefined || text === null) {\n text = \"\"; // Default to empty string instead of crashing\n console.warn(\"showTextWall called with null/undefined text\");\n }\n\n // Ensure text is a string\n if (typeof text !== \"string\") {\n text = String(text); // Convert to string\n console.warn(\"showTextWall: Non-string input converted to string\");\n }\n\n // Create layout with validated text\n const layout: TextWall = {\n layoutType: LayoutType.TEXT_WALL,\n text,\n };\n\n // Create and send display event with error handling\n try {\n const displayEvent = this.createDisplayEvent(\n layout,\n options?.view,\n options?.durationMs,\n );\n this.sendMessage(displayEvent);\n } catch (error) {\n console.error(\"Failed to display text wall:\", error);\n // Don't re-throw - prevent app crashes\n }\n } catch (error) {\n console.error(\"Error in showTextWall:\", error);\n // Don't crash the App - fail gracefully\n }\n }\n\n /**\n * ↕️ Shows two sections of text, one above the other\n *\n * Best for:\n * - Before/After content\n * - Question/Answer displays\n * - Two-part messages\n * - Comparisons\n *\n * @param topText - Text to show in top section\n * @param bottomText - Text to show in bottom section\n * @param options - Optional parameters (view, duration)\n *\n * @example\n * ```typescript\n * layouts.showDoubleTextWall(\n * 'Original: Hello',\n * 'Translated: Bonjour'\n * );\n * ```\n */\n showDoubleTextWall(\n topText: string,\n bottomText: string,\n options?: { view?: ViewType; durationMs?: number },\n ) {\n const layout: DoubleTextWall = {\n layoutType: LayoutType.DOUBLE_TEXT_WALL,\n topText,\n bottomText,\n };\n this.sendMessage(\n this.createDisplayEvent(layout, options?.view, options?.durationMs),\n );\n }\n\n /**\n * 📇 Shows a card with a title and content\n *\n * Best for:\n * - Titled content\n * - Important information\n * - Structured data\n * - Notifications with context\n *\n * @param title - Card title\n * @param text - Main content text\n * @param options - Optional parameters (view, duration)\n *\n * @example\n * ```typescript\n * layouts.showReferenceCard(\n * 'Meeting Reminder',\n * 'Team standup in 5 minutes'\n * );\n * ```\n */\n showReferenceCard(\n title: string,\n text: string,\n options?: { view?: ViewType; durationMs?: number },\n ) {\n const layout: ReferenceCard = {\n layoutType: LayoutType.REFERENCE_CARD,\n title,\n text,\n };\n this.sendMessage(\n this.createDisplayEvent(layout, options?.view, options?.durationMs),\n );\n }\n\n /**\n * 📇 Shows a bitmap\n *\n * Uses the proven animation system internally for proper L/R eye synchronization.\n * This ensures single bitmap displays work consistently with the same\n * hardware-optimized timing as animations.\n *\n * @param data - hex or base64 encoded bitmap data\n * @param options - Optional parameters (view, duration)\n *\n * @example\n * ```typescript\n * layouts.showBitmapView(\n * yourHexOrBase64EncodedBitmapDataString\n * );\n * ```\n */\n async showBitmapView(\n base64Bitmap: string,\n options?: { view?: ViewType; padding?: { left: number; top: number } },\n ) {\n const padding = options?.padding ?? { left: 50, top: 35 };\n const bitmapFrame = await BitmapUtils.padBase64Bitmap(\n base64Bitmap,\n padding,\n );\n const validation = BitmapUtils.validateBase64Bitmap(bitmapFrame);\n if (!validation.isValid) {\n throw new Error(\n `❌ Frame validation failed: ${validation.errors.join(\", \")}`,\n );\n }\n const layout: BitmapView = {\n layoutType: LayoutType.BITMAP_VIEW,\n data: bitmapFrame,\n };\n this.sendMessage(this.createDisplayEvent(layout, options?.view));\n }\n\n /**\n * 📊 Shows a dashboard card with left and right text\n *\n * Best for:\n * - Key-value pairs\n * - Dashboard displays\n * - Metrics\n *\n * @param leftText - Left side text (typically label/key)\n * @param rightText - Right side text (typically value)\n * @param options - Optional parameters (view, duration)\n *\n * @example\n * ```typescript\n * layouts.showDashboardCard('Weather', '72°F');\n * ```\n */\n showDashboardCard(\n leftText: string,\n rightText: string,\n options?: { view?: ViewType; durationMs?: number },\n ) {\n const layout: DashboardCard = {\n layoutType: LayoutType.DASHBOARD_CARD,\n leftText,\n rightText,\n };\n this.sendMessage(\n this.createDisplayEvent(\n layout,\n options?.view || ViewType.DASHBOARD,\n options?.durationMs,\n ),\n );\n }\n\n /**\n * 🧹 Clears the display\n *\n * Best for:\n * - Clearing previous content\n * - Resetting display state\n * - Starting fresh\n *\n * @param options - Optional parameters (view)\n *\n * @example\n * ```typescript\n * layouts.clearView();\n * layouts.clearView({ view: ViewType.DASHBOARD });\n * ```\n */\n clearView(options?: { view?: ViewType }) {\n const layout: ClearView = {\n layoutType: LayoutType.CLEAR_VIEW,\n };\n this.sendMessage(this.createDisplayEvent(layout, options?.view));\n }\n\n /**\n * 🎬 Shows an animated sequence of bitmap images (iOS-controlled timing)\n *\n * Sends complete animation package to iOS for device-controlled timing.\n * This provides superior performance and synchronization by letting\n * the device control the display timing directly.\n *\n * Best for:\n * - Smooth high-performance animations\n * - Precise timing control\n * - Synchronized left/right display\n * - EvenDemo-quality performance\n *\n * @param bitmapDataArray - Array of bitmap data strings (hex or base64 encoded)\n * @param intervalMs - Time between frames in milliseconds (default: 1650ms)\n * @param repeat - Whether to loop the animation continuously (default: false)\n * @param options - Optional parameters (view)\n *\n * @example\n * ```typescript\n * // Device-controlled animation (recommended)\n * const frames = ['hexdata1', 'hexdata2', 'hexdata3'];\n * layouts.showBitmapAnimation(frames, 1650, true);\n *\n * // Single-cycle animation\n * layouts.showBitmapAnimation(loadingFrames, 1650, false);\n * ```\n *\n * @returns Animation controller object with stop() method\n */\n showBitmapAnimation(\n bitmapDataArray: string[],\n intervalMs: number = 1650,\n repeat: boolean = false,\n options?: { view?: ViewType },\n ): { stop: () => void } {\n // Validation\n if (!Array.isArray(bitmapDataArray) || bitmapDataArray.length === 0) {\n throw new Error(\n \"showBitmapAnimation requires a non-empty array of bitmap data\",\n );\n }\n\n // Send complete animation package to iOS for device-controlled timing\n const layout: BitmapAnimation = {\n layoutType: LayoutType.BITMAP_ANIMATION,\n frames: bitmapDataArray,\n interval: intervalMs,\n repeat: repeat,\n };\n\n this.sendMessage(this.createDisplayEvent(layout, options?.view));\n\n console.log(\n `🎬 Sent batched animation to iOS: ${\n bitmapDataArray.length\n } frames at ${intervalMs}ms${repeat ? \" (repeating)\" : \"\"}`,\n );\n\n // Return controller for compatibility\n return {\n stop: () => {\n // Send stop command to iOS\n this.clearView();\n console.log(\"🛑 Animation stop requested\");\n },\n };\n }\n}\n",
|
|
28
|
+
"/**\n * 🔧 Settings Manager Module\n *\n * Manages App settings with automatic synchronization and change notifications.\n * Provides type-safe access to settings with default values.\n */\nimport EventEmitter from \"events\";\nimport { AppSetting, AppSettings } from \"../../types\";\nimport { ApiClient } from \"./api-client\";\nimport { logger } from \"../../logging/logger\"; // Adjust import path as needed\n// Note(Isaiah): Let's not import @mentra/utils in the SDK to avoid circular dependencies. Also i'm deprecating it in favor of the new logging system.\n\n/**\n * Change information for a single setting\n */\nexport interface SettingChange {\n oldValue: any;\n newValue: any;\n}\n\n/**\n * Map of setting keys to their change information\n */\nexport type SettingsChangeMap = Record<string, SettingChange>;\n\n/**\n * Callback for when any setting changes\n */\nexport type SettingsChangeHandler = (changes: SettingsChangeMap) => void;\n\n/**\n * Callback for when a specific setting changes\n */\nexport type SettingValueChangeHandler<T = any> = (\n newValue: T,\n oldValue: T,\n) => void;\n\n/**\n * Internal event names\n */\nenum SettingsEvents {\n CHANGE = \"settings:change\",\n VALUE_CHANGE = \"settings:value:\",\n}\n\n/**\n * 🔧 Settings Manager\n *\n * Provides a type-safe interface for accessing and reacting to App settings.\n * Automatically synchronizes with MentraOS Cloud.\n */\nexport class SettingsManager {\n // Current settings values\n private settings: AppSettings = [];\n\n // Event emitter for change notifications\n private emitter = new EventEmitter();\n\n // API client for fetching settings\n private apiClient?: ApiClient;\n\n // --- MentraOS settings event system ---\n private mentraosSettings: Record<string, any> = {};\n private mentraosEmitter = new EventEmitter();\n private subscribeFn?: (streams: string[]) => Promise<void>; // Added for auto-subscriptions\n\n /**\n * Create a new settings manager\n *\n * @param initialSettings Initial settings values (if available)\n * @param packageName Package name for the App\n * @param wsUrl WebSocket URL (for deriving HTTP API URL)\n * @param userId User ID (for authenticated requests)\n * @param subscribeFn Optional function to call to subscribe to streams\n */\n constructor(\n initialSettings: AppSettings = [],\n packageName?: string,\n wsUrl?: string,\n userId?: string,\n subscribeFn?: (streams: string[]) => Promise<void>, // Added parameter\n ) {\n this.settings = [...initialSettings];\n this.subscribeFn = subscribeFn; // Store the subscribe function\n\n // Create API client if we have enough information\n if (packageName) {\n this.apiClient = new ApiClient(packageName, wsUrl, userId);\n }\n }\n\n /**\n * Configure the API client\n *\n * @param packageName Package name for the App\n * @param wsUrl WebSocket URL\n * @param userId User ID\n */\n configureApiClient(packageName: string, wsUrl: string, userId: string): void {\n if (!this.apiClient) {\n this.apiClient = new ApiClient(packageName, wsUrl, userId);\n } else {\n this.apiClient.setWebSocketUrl(wsUrl);\n this.apiClient.setUserId(userId);\n }\n }\n\n /**\n * Update the current settings\n * This is called internally when settings are loaded or changed\n *\n * @param newSettings New settings values\n * @returns Map of changed settings\n */\n updateSettings(newSettings: AppSettings): SettingsChangeMap {\n const changes: SettingsChangeMap = {};\n\n // Copy the new settings\n const updatedSettings = [...newSettings];\n\n // Detect changes comparing old and new settings\n for (const newSetting of updatedSettings) {\n const oldSetting = this.settings.find((s) => s.key === newSetting.key);\n\n // Skip if value hasn't changed\n if (oldSetting && this.areEqual(oldSetting.value, newSetting.value)) {\n continue;\n }\n\n // Record change\n changes[newSetting.key] = {\n oldValue: oldSetting?.value,\n newValue: newSetting.value,\n };\n }\n\n // Check for removed settings\n for (const oldSetting of this.settings) {\n const stillExists = updatedSettings.some((s) => s.key === oldSetting.key);\n\n if (!stillExists) {\n changes[oldSetting.key] = {\n oldValue: oldSetting.value,\n newValue: undefined,\n };\n }\n }\n\n // If there are changes, update the settings and emit events\n if (Object.keys(changes).length > 0) {\n this.settings = updatedSettings;\n this.emitChanges(changes);\n }\n\n return changes;\n }\n\n /**\n * Check if two setting values are equal\n *\n * @param a First value\n * @param b Second value\n * @returns True if the values are equal\n */\n private areEqual(a: any, b: any): boolean {\n // Simple equality check - for objects, this won't do a deep equality check\n // but for most setting values (strings, numbers, booleans) it works\n return a === b;\n }\n\n /**\n * Emit change events for updated settings\n *\n * @param changes Map of changed settings\n */\n private emitChanges(changes: SettingsChangeMap): void {\n // Emit the general change event\n this.emitter.emit(SettingsEvents.CHANGE, changes);\n\n // Emit individual value change events\n for (const [key, change] of Object.entries(changes)) {\n this.emitter.emit(\n `${SettingsEvents.VALUE_CHANGE}${key}`,\n change.newValue,\n change.oldValue,\n );\n }\n }\n\n /**\n * 🔄 Listen for changes to any setting\n *\n * @param handler Function to call when settings change\n * @returns Function to remove the listener\n *\n * @example\n * ```typescript\n * settings.onChange((changes) => {\n * console.log('Settings changed:', changes);\n * });\n * ```\n */\n onChange(handler: SettingsChangeHandler): () => void {\n this.emitter.on(SettingsEvents.CHANGE, handler);\n return () => this.emitter.off(SettingsEvents.CHANGE, handler);\n }\n\n /**\n * 🔄 Listen for changes to a specific setting\n *\n * @param key Setting key to monitor\n * @param handler Function to call when the setting changes\n * @returns Function to remove the listener\n *\n * @example\n * ```typescript\n * settings.onValueChange('transcribe_language', (newValue, oldValue) => {\n * console.log(`Language changed from ${oldValue} to ${newValue}`);\n * });\n * ```\n */\n onValueChange<T = any>(\n key: string,\n handler: SettingValueChangeHandler<T>,\n ): () => void {\n const eventName = `${SettingsEvents.VALUE_CHANGE}${key}`;\n this.emitter.on(eventName, handler);\n return () => this.emitter.off(eventName, handler);\n }\n\n /**\n * 🔍 Check if a setting exists\n *\n * @param key Setting key to check\n * @returns True if the setting exists\n */\n has(key: string): boolean {\n return this.settings.some((s) => s.key === key);\n }\n\n /**\n * 🔍 Get all settings\n *\n * @returns Copy of all settings\n */\n getAll(): AppSettings {\n return [...this.settings];\n }\n\n /**\n * 🔍 Get a setting value with type safety\n *\n * @param key Setting key to get\n * @param defaultValue Default value if setting doesn't exist or is undefined\n * @returns Setting value or default value\n *\n * @example\n * ```typescript\n * const lineWidth = settings.get<number>('line_width', 30);\n * const language = settings.get<string>('transcribe_language', 'English');\n * ```\n */\n get<T = any>(key: string, defaultValue?: T): T {\n const setting = this.settings.find((s) => s.key === key);\n\n if (setting && setting.value !== undefined) {\n return setting.value as T;\n }\n\n return defaultValue as T;\n }\n\n /**\n * 🎛️ Get an MentraOS system setting value with optional default\n *\n * @param key MentraOS setting key (e.g., 'metricSystemEnabled', 'brightness')\n * @param defaultValue Default value to return if the setting is not found\n * @returns The setting value or the default value\n *\n * @example\n * ```typescript\n * const isMetric = settings.getMentraOS<boolean>('metricSystemEnabled', false);\n * const brightness = settings.getMentraOS<number>('brightness', 50);\n * ```\n */\n getMentraOS<T = any>(key: string, defaultValue?: T): T {\n const value = this.mentraosSettings[key];\n\n if (value !== undefined) {\n return value as T;\n }\n\n return defaultValue as T;\n }\n\n /**\n * 🔍 Find a setting by key\n *\n * @param key Setting key to find\n * @returns Setting object or undefined\n */\n getSetting(key: string): AppSetting | undefined {\n return this.settings.find((s) => s.key === key);\n }\n\n /**\n * 🔄 Fetch settings from the cloud\n * This is generally not needed since settings are automatically kept in sync,\n * but can be used to force a refresh if needed.\n *\n * @returns Promise that resolves to the updated settings\n * @throws Error if the API client is not configured or the request fails\n */\n async fetch(): Promise<AppSettings> {\n if (!this.apiClient) {\n throw new Error(\"Settings API client is not configured\");\n }\n\n try {\n const newSettings = await this.apiClient.fetchSettings();\n this.updateSettings(newSettings);\n return this.settings;\n } catch (error) {\n console.error(\"Error fetching settings:\", error);\n throw error;\n }\n }\n\n /**\n * 🎛️ Listen for changes to a specific MentraOS setting (e.g., metricSystemEnabled)\n *\n * @param key The mentraosSettings key to listen for (e.g., 'metricSystemEnabled')\n * @param handler Function to call when the value changes\n * @returns Function to remove the listener\n *\n * @example\n * ```typescript\n * settings.onMentraosChange('metricSystemEnabled', (isMetric, wasMetric) => {\n * console.log(`Units changed: ${wasMetric ? 'metric' : 'imperial'} → ${isMetric ? 'metric' : 'imperial'}`);\n * });\n * ```\n */\n onMentraosChange<T = any>(\n key: string,\n handler: SettingValueChangeHandler<T>,\n ): () => void {\n return this.onMentraosSettingChange(key, handler);\n }\n\n /**\n * Listen for changes to a specific MentraOS setting (e.g., metricSystemEnabled)\n * This is a convenience wrapper for onValueChange for well-known mentraosSettings keys.\n * @param key The mentraosSettings key to listen for (e.g., 'metricSystemEnabled')\n * @param handler Function to call when the value changes\n * @returns Function to remove the listener\n * @deprecated Use onMentraosChange instead\n */\n onMentraosSettingsChange<T = any>(\n key: string,\n handler: SettingValueChangeHandler<T>,\n ): () => void {\n return this.onMentraosSettingChange(key, handler);\n }\n\n /**\n * Update the current MentraOS settings\n * Compares new and old values, emits per-key events, and updates stored values.\n * @param newSettings The new MentraOS settings object\n */\n updateMentraosSettings(newSettings: Record<string, any>): void {\n const oldSettings = this.mentraosSettings;\n logger.debug(\n { newSettings },\n `[SettingsManager] Updating MentraOS settings. New settings`,\n );\n for (const key of Object.keys(newSettings)) {\n const oldValue = oldSettings[key];\n const newValue = newSettings[key];\n if (oldValue !== newValue) {\n logger.info(\n `[SettingsManager] MentraOS setting '${key}' changed: ${oldValue} -> ${newValue}. Emitting event.`,\n );\n this.mentraosEmitter.emit(`augmentos:value:${key}`, newValue, oldValue);\n }\n }\n // Also handle keys that might have been removed from newSettings but existed in oldSettings\n for (const key of Object.keys(oldSettings)) {\n if (!(key in newSettings)) {\n logger.info(\n `[SettingsManager] MentraOS setting '${key}' removed. Old value: ${oldSettings[key]}. Emitting event with undefined newValue.`,\n );\n this.mentraosEmitter.emit(\n `augmentos:value:${key}`,\n undefined,\n oldSettings[key],\n );\n }\n }\n this.mentraosSettings = { ...newSettings };\n logger.debug(\n { mentraosSettings: this.mentraosSettings },\n `[SettingsManager] Finished updating MentraOS settings. Current state:`,\n );\n }\n\n /**\n * Subscribe to changes for a specific MentraOS setting (e.g., 'metricSystemEnabled')\n * @param key The MentraOS setting key to listen for\n * @param handler Function to call when the value changes (newValue, oldValue)\n * @returns Function to remove the listener\n */\n onMentraosSettingChange<T = any>(\n key: string,\n handler: (newValue: T, oldValue: T) => void,\n ): () => void {\n const eventName = `augmentos:value:${key}`;\n logger.info(\n `[SettingsManager] Registering handler for MentraOS setting '${key}' on event '${eventName}'.`,\n );\n this.mentraosEmitter.on(eventName, (...args) => {\n logger.info(\n { args },\n `[SettingsManager] MentraOS setting '${key}' event fired. Args:`,\n );\n handler(...(args as [T, T]));\n });\n\n if (this.subscribeFn) {\n const subscriptionKey = `augmentos:${key}`;\n logger.info(\n `[SettingsManager] Calling subscribeFn for stream '${subscriptionKey}'.`,\n );\n this.subscribeFn([subscriptionKey])\n .then(() => {\n logger.info(\n `[SettingsManager] subscribeFn resolved for stream '${subscriptionKey}'.`,\n );\n })\n .catch((err) => {\n logger.error(\n `[SettingsManager] subscribeFn failed for stream '${subscriptionKey}':`,\n err,\n );\n });\n } else {\n logger.warn(\n `[SettingsManager] 'subscribeFn' not provided. Cannot auto-subscribe for MentraOS setting '${key}'. Manual App subscription might be required.`,\n );\n }\n\n return () => {\n logger.info(\n `[SettingsManager] Unregistering handler for MentraOS setting '${key}' from event '${eventName}'.`,\n );\n this.mentraosEmitter.off(\n eventName,\n handler as (newValue: unknown, oldValue: unknown) => void,\n );\n };\n }\n\n /**\n * Get the current value of an MentraOS setting\n */\n getMentraosSetting<T = any>(key: string, defaultValue?: T): T {\n console.log(\n `[SettingsManager] Getting MentraOS setting '${key}' with settings:`,\n this.mentraosSettings,\n );\n if (key in this.mentraosSettings) {\n return this.mentraosSettings[key] as T;\n }\n return defaultValue as T;\n }\n}\n",
|
|
29
|
+
"/**\n * 🔌 API Client Module\n *\n * Provides HTTP API access to MentraOS Cloud services.\n * Automatically uses the correct server URL derived from the WebSocket URL.\n */\n\n/**\n * Convert a WebSocket URL to a HTTP/HTTPS URL\n *\n * @param wsUrl WebSocket URL to convert\n * @returns HTTP URL equivalent\n */\nexport function wsUrlToHttpUrl(wsUrl?: string): string | undefined {\n if (!wsUrl) return undefined;\n\n try {\n // Parse the WebSocket URL\n const url = new URL(wsUrl);\n\n // Change protocol from ws/wss to http/https\n const protocol = url.protocol === \"wss:\" ? \"https:\" : \"http:\";\n\n // Recreate the URL with the new protocol\n return `${protocol}//${url.host}`;\n } catch (error) {\n console.error(\"Error converting WebSocket URL to HTTP URL:\", error);\n return undefined;\n }\n}\n\n/**\n * API client class for making HTTP requests to MentraOS Cloud\n */\nexport class ApiClient {\n private baseUrl: string | undefined;\n private packageName: string;\n private userId: string | undefined;\n\n /**\n * Create a new API client\n *\n * @param packageName App package name\n * @param wsUrl WebSocket URL (optional, can be set later)\n * @param userId User ID (optional, for authenticated requests)\n */\n constructor(packageName: string, wsUrl?: string, userId?: string) {\n this.packageName = packageName;\n this.userId = userId;\n\n if (wsUrl) {\n this.baseUrl = wsUrlToHttpUrl(wsUrl);\n }\n }\n\n /**\n * Set the WebSocket URL to derive the HTTP base URL\n *\n * @param wsUrl WebSocket URL\n */\n setWebSocketUrl(wsUrl: string): void {\n this.baseUrl = wsUrlToHttpUrl(wsUrl);\n }\n\n /**\n * Set the user ID for authenticated requests\n *\n * @param userId User ID\n */\n setUserId(userId: string): void {\n this.userId = userId;\n }\n\n /**\n * Fetch settings from MentraOS Cloud\n *\n * @returns Promise resolving to settings array\n * @throws Error if client is not configured correctly or if request fails\n */\n async fetchSettings(): Promise<any[]> {\n if (!this.baseUrl) {\n throw new Error(\"API client is not configured with a base URL\");\n }\n\n if (!this.userId) {\n throw new Error(\"User ID is required for fetching settings\");\n }\n\n const url = `${this.baseUrl}/appsettings/user/${this.packageName}`;\n\n try {\n const response = await fetch(url, {\n method: \"GET\",\n headers: {\n Authorization: `Bearer ${this.userId}`,\n \"Content-Type\": \"application/json\",\n },\n });\n\n if (!response.ok) {\n throw new Error(\n `Failed to fetch settings: ${response.status} ${response.statusText}`,\n );\n }\n\n const data = (await response.json()) as { settings: any[] };\n return data.settings || [];\n } catch (error) {\n console.error(\"Error fetching settings:\", error);\n throw error;\n }\n }\n}\n",
|
|
30
|
+
"import pino from \"pino\"\n\n// Constants and configuration\nconst BETTERSTACK_SOURCE_TOKEN = process.env.BETTERSTACK_SOURCE_TOKEN\nconst BETTERSTACK_ENDPOINT = process.env.BETTERSTACK_ENDPOINT || \"https://s1311181.eu-nbg-2.betterstackdata.com\"\nconst NODE_ENV = process.env.NODE_ENV || \"development\"\nconst PORTER_APP_NAME = process.env.PORTER_APP_NAME || \"cloud-local\"\n\n// Determine log level based on environment\nconst LOG_LEVEL = NODE_ENV === \"production\" ? \"info\" : \"debug\"\n\n// Setup streams array for Pino multistream\nconst streams: pino.StreamEntry[] = []\n\n// Use pretty print in development for better readability\n// if (PRETTY_PRINT && NODE_ENV !== 'production') {\n// Pretty transport for development\nconst prettyTransport = pino.transport({\n target: \"pino-pretty\",\n options: {\n colorize: true,\n translateTime: \"SYS:standard\",\n ignore: \"pid,hostname,env,module,server\",\n messageFormat: \"{msg}\",\n errorProps: \"*\",\n customPrettifiers: {\n // Add custom prettifiers here if needed\n },\n },\n})\n\nstreams.push({\n stream: prettyTransport,\n level: LOG_LEVEL,\n})\n// } else {\n// // Plain console in production (JSON format)\n// streams.push({\n// stream: process.stdout,\n// level: LOG_LEVEL,\n// });\n// }\n\n// Add BetterStack transport if token is provided\nif (BETTERSTACK_SOURCE_TOKEN) {\n try {\n const betterStackTransport = pino.transport({\n target: \"@logtail/pino\",\n options: {\n sourceToken: BETTERSTACK_SOURCE_TOKEN,\n options: {endpoint: BETTERSTACK_ENDPOINT},\n },\n })\n\n streams.push({\n stream: betterStackTransport,\n level: LOG_LEVEL,\n })\n } catch (error) {\n console.warn(\"BetterStack logging unavailable:\", error instanceof Error ? error.message : error)\n }\n}\n\n// Create multistream\nconst multistream = pino.multistream(streams)\n\n/**\n * Configuration for the root logger\n */\nconst baseLoggerOptions: pino.LoggerOptions = {\n level: LOG_LEVEL,\n base: {\n env: NODE_ENV,\n server: PORTER_APP_NAME,\n },\n timestamp: pino.stdTimeFunctions.isoTime,\n}\n\n// Create the root logger with multiple streams\nexport const logger = pino(baseLoggerOptions, multistream)\n\n// Flush logger on process exit\n// process.on('beforeExit', async () => {\n// logger.flush(); // Flush the root logger\n// console.log('Logger flushed before exit');\n// });\n\n// Default export is the logger\nexport default logger\n",
|
|
31
|
+
"import {locationWarnLog} from \"../../../utils/permissions-utils\"\n\nimport {AppSession} from \"..\"\nimport {AppToCloudMessageType, LocationUpdate, LocationStreamRequest} from \"../../../types\"\n\nexport class LocationManager {\n private session: AppSession\n private lastLocationCleanupHandler: () => void = () => {}\n\n constructor(session: AppSession) {\n this.session = session\n }\n\n // subscribes to the continuous location stream with a specified accuracy tier\n public subscribeToStream(\n options: {\n accuracy:\n | \"standard\"\n | \"high\"\n | \"realtime\"\n | \"tenMeters\"\n | \"hundredMeters\"\n | \"kilometer\"\n | \"threeKilometers\"\n | \"reduced\"\n },\n handler: (data: LocationUpdate) => void,\n ): () => void {\n //Checking for permission location from dev console:\n locationWarnLog(this.session.getHttpsServerUrl() || \"\", this.session.getPackageName(), this.subscribeToStream.name)\n\n const subscription: LocationStreamRequest = {\n stream: \"location_stream\",\n rate: options.accuracy,\n }\n this.session.subscribe(subscription)\n this.lastLocationCleanupHandler = this.session.events.onLocation(handler)\n return this.lastLocationCleanupHandler\n }\n\n // unsubscribes from the continuous location stream\n public unsubscribeFromStream(): void {\n if (this.lastLocationCleanupHandler) {\n this.lastLocationCleanupHandler()\n this.lastLocationCleanupHandler = () => {}\n } else {\n this.session.unsubscribe(\"location_stream\")\n }\n }\n\n // performs a one-time, intelligent poll for a location fix\n public async getLatestLocation(options: {accuracy: string}): Promise<LocationUpdate> {\n return new Promise((resolve, reject) => {\n const requestId = `poll_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`\n\n // listens for a location update with a matching correlationId\n const unsubscribe = this.session.events.on(\"location_update\", (data: LocationUpdate) => {\n if (data.correlationId === requestId) {\n unsubscribe() // clean up the listener\n resolve(data)\n }\n })\n\n // sends the poll request message to the cloud\n this.session.sendMessage({\n type: AppToCloudMessageType.LOCATION_POLL_REQUEST,\n correlationId: requestId,\n packageName: this.session.getPackageName(),\n sessionId: this.session.getSessionId(),\n accuracy: options.accuracy,\n })\n\n // sets a timeout to prevent the promise from hanging indefinitely\n setTimeout(() => {\n unsubscribe()\n reject(\"Location poll request timed out\")\n }, 15000) // 15 second timeout\n })\n }\n}\n",
|
|
32
|
+
"/**\n * 📷 Camera Module\n *\n * Unified camera functionality for App Sessions.\n * Handles both photo requests and RTMP streaming from connected glasses.\n */\n\nimport {\n PhotoRequest,\n PhotoData,\n AppToCloudMessageType,\n RtmpStreamRequest,\n RtmpStreamStopRequest,\n RtmpStreamStatus,\n isRtmpStreamStatus,\n ManagedStreamStatus,\n StreamStatusCheckResponse,\n} from \"../../../types\"\nimport {VideoConfig, AudioConfig, StreamConfig, StreamStatusHandler} from \"../../../types/rtmp-stream\"\nimport {StreamType} from \"../../../types/streams\"\nimport {Logger} from \"pino\"\nimport {CameraManagedExtension, ManagedStreamOptions, ManagedStreamResult} from \"./camera-managed-extension\"\nimport {cameraWarnLog} from \"../../../utils/permissions-utils\"\n\n/**\n * Options for photo requests\n */\nexport interface PhotoRequestOptions {\n /** Whether to save the photo to the device gallery */\n saveToGallery?: boolean\n /** Custom webhook URL to override the TPA's default webhookUrl */\n customWebhookUrl?: string\n /** Authentication token for custom webhook authentication */\n authToken?: string\n /**\n * Desired photo size. All sizes are optimized for fast transfer.\n * - small: 640x480 (VGA) - ultra-fast transfers\n * - medium: 1280x720 (720p) - good balance (default)\n * - large: 1920x1080 (1080p) - high quality\n * - full: native sensor resolution - maximum detail (slower transfer)\n */\n size?: \"small\" | \"medium\" | \"large\" | \"full\"\n /** Image compression level for upload optimization. Defaults to \"none\". */\n compress?: \"none\" | \"medium\" | \"heavy\"\n}\n\n/**\n * Configuration options for an RTMP stream\n */\nexport interface RtmpStreamOptions {\n /** The RTMP URL to stream to (e.g., rtmp://server.example.com/live/stream-key) */\n rtmpUrl: string\n /** Optional video configuration settings */\n video?: VideoConfig\n /** Optional audio configuration settings */\n audio?: AudioConfig\n /** Optional stream configuration settings */\n stream?: StreamConfig\n}\n\n/**\n * 📷 Camera Module Implementation\n *\n * Unified camera management for App Sessions.\n * Provides methods for:\n * - 📸 Requesting photos from glasses\n * - 📹 Starting/stopping RTMP streams\n * - 🔍 Monitoring photo and stream status\n * - 🧹 Cleanup and cancellation\n *\n * @example\n * ```typescript\n * // Request a photo\n * const photoData = await session.camera.requestPhoto({ saveToGallery: true });\n *\n * // Start streaming\n * await session.camera.startStream({ rtmpUrl: 'rtmp://example.com/live/key' });\n *\n * // Monitor stream status\n * session.camera.onStreamStatus((status) => {\n * console.log('Stream status:', status.status);\n * });\n *\n * // Stop streaming\n * await session.camera.stopStream();\n * ```\n */\nexport class CameraModule {\n private session: any // Reference to AppSession\n private packageName: string\n private sessionId: string\n private logger: Logger\n\n // Photo functionality\n /** Map to store pending photo request promises */\n private pendingPhotoRequests = new Map<\n string,\n {\n resolve: (value: PhotoData) => void\n reject: (reason?: string) => void\n }\n >()\n\n // Streaming functionality\n private isStreaming: boolean = false\n private currentStreamUrl?: string\n private currentStreamState?: RtmpStreamStatus\n\n // Managed streaming extension\n private managedExtension: CameraManagedExtension\n\n /**\n * Create a new CameraModule\n *\n * @param session - Reference to the parent AppSession\n * @param packageName - The App package name\n * @param sessionId - The current session ID\n * @param logger - Logger instance for debugging\n */\n constructor(session: any, packageName: string, sessionId: string, logger?: Logger) {\n this.session = session\n this.packageName = packageName\n this.sessionId = sessionId\n this.logger = logger || (console as any)\n\n // Initialize managed extension\n this.managedExtension = new CameraManagedExtension(session, packageName, sessionId, this.logger)\n }\n\n // =====================================\n // 📸 Photo Functionality\n // =====================================\n\n /**\n * 📸 Request a photo from the connected glasses\n *\n * @param options - Optional configuration for the photo request\n * @returns Promise that resolves with the actual photo data\n *\n * @example\n * ```typescript\n * // Request a photo\n * const photo = await session.camera.requestPhoto();\n *\n * // Request a photo with custom webhook URL and authentication\n * const photo = await session.camera.requestPhoto({\n * customWebhookUrl: 'https://my-custom-endpoint.com/photo-upload',\n * authToken: 'your-auth-token-here'\n * });\n * ```\n */\n async requestPhoto(options?: PhotoRequestOptions): Promise<PhotoData> {\n return new Promise((resolve, reject) => {\n const baseUrl = this.session?.getHttpsServerUrl?.() || \"\"\n cameraWarnLog(baseUrl, this.packageName, \"requestPhoto\")\n try {\n console.log(\"DEBUG: requestPhoto options:\", options)\n\n // Generate unique request ID\n const requestId = `photo_req_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`\n\n // Store promise resolvers for when we get the response\n this.pendingPhotoRequests.set(requestId, {resolve, reject})\n\n // Create photo request message\n const message: PhotoRequest = {\n type: AppToCloudMessageType.PHOTO_REQUEST,\n packageName: this.packageName,\n sessionId: this.sessionId,\n requestId,\n timestamp: new Date(),\n saveToGallery: options?.saveToGallery || false,\n customWebhookUrl: options?.customWebhookUrl,\n authToken: options?.authToken,\n size: options?.size || \"medium\",\n compress: options?.compress || \"none\",\n }\n\n // Send request to cloud\n this.session.sendMessage(message)\n\n this.logger.info(\n {\n requestId,\n saveToGallery: options?.saveToGallery,\n hasCustomWebhook: !!options?.customWebhookUrl,\n hasAuthToken: !!options?.authToken,\n },\n `📸 Photo request sent`,\n )\n\n // If using custom webhook URL, resolve immediately since photo will be uploaded directly to custom endpoint\n if (options?.customWebhookUrl) {\n this.logger.info(\n {requestId, customWebhookUrl: options.customWebhookUrl},\n `📸 Using custom webhook URL - resolving promise immediately since photo will be uploaded directly to custom endpoint`,\n )\n\n // Create a mock PhotoData object for custom webhook URLs\n const mockPhotoData: PhotoData = {\n buffer: Buffer.from([]), // Empty buffer since we don't have the actual photo\n mimeType: \"image/jpeg\",\n filename: \"photo.jpg\",\n requestId,\n size: 0,\n timestamp: new Date(),\n }\n\n // Resolve immediately and clean up\n this.pendingPhotoRequests.delete(requestId)\n resolve(mockPhotoData)\n return\n }\n\n // Set timeout to avoid hanging promises (only for non-custom webhook requests)\n const timeoutMs = 30000 // 30 seconds\n if (this.session && this.session.resources) {\n // Use session's resource tracker for automatic cleanup\n this.session.resources.setTimeout(() => {\n if (this.pendingPhotoRequests.has(requestId)) {\n this.pendingPhotoRequests.get(requestId)!.reject(\"Photo request timed out\")\n this.pendingPhotoRequests.delete(requestId)\n this.logger.warn({requestId}, `📸 Photo request timed out`)\n }\n }, timeoutMs)\n } else {\n // Fallback to regular setTimeout if session not available\n setTimeout(() => {\n if (this.pendingPhotoRequests.has(requestId)) {\n this.pendingPhotoRequests.get(requestId)!.reject(\"Photo request timed out\")\n this.pendingPhotoRequests.delete(requestId)\n this.logger.warn({requestId}, `📸 Photo request timed out`)\n }\n }, timeoutMs)\n }\n } catch (error: unknown) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n reject(`Failed to request photo: ${errorMessage}`)\n }\n })\n }\n\n /**\n * 📥 Handle photo received from /photo-upload endpoint\n *\n * This method is called internally when a photo response is received.\n * It resolves the corresponding pending promise with the photo data.\n *\n * @param photoData - The photo data received\n * @internal This method is used internally by AppSession\n */\n handlePhotoReceived(photoData: PhotoData): void {\n const {requestId} = photoData\n const pendingRequest = this.pendingPhotoRequests.get(requestId)\n\n if (pendingRequest) {\n this.logger.info({requestId}, `📸 Photo received for request ${requestId}`)\n\n // Resolve the promise with the photo data\n pendingRequest.resolve(photoData)\n\n // Clean up\n this.pendingPhotoRequests.delete(requestId)\n } else {\n this.logger.warn({requestId}, `📸 Received photo for unknown request ID: ${requestId}`)\n }\n }\n\n /**\n * ❌ Handle photo error from /photo-upload endpoint\n *\n * This method is called internally when a photo error response is received.\n * It rejects the corresponding pending promise with the error information.\n *\n * @param errorResponse - The error response received\n * @internal This method is used internally by AppSession\n */\n handlePhotoError(errorResponse: {\n requestId: string\n success: false\n error: {\n code: string\n message: string\n }\n }): void {\n const {requestId, error} = errorResponse\n const pendingRequest = this.pendingPhotoRequests.get(requestId)\n\n if (pendingRequest) {\n this.logger.error(\n {requestId, errorCode: error.code, errorMessage: error.message},\n `📸 Photo capture failed: ${error.code} - ${error.message}`,\n )\n\n // Reject the promise with the error information\n pendingRequest.reject(`${error.code}: ${error.message}`)\n\n // Clean up\n this.pendingPhotoRequests.delete(requestId)\n } else {\n this.logger.warn(\n {requestId, errorCode: error.code, errorMessage: error.message},\n `📸 Received photo error for unknown request ID: ${requestId}`,\n )\n }\n }\n\n /**\n * 🔍 Check if there's a pending photo request for the given request ID\n *\n * @param requestId - The request ID to check\n * @returns true if there's a pending request\n */\n hasPhotoPendingRequest(requestId: string): boolean {\n return this.pendingPhotoRequests.has(requestId)\n }\n\n /**\n * 📊 Get the number of pending photo requests\n *\n * @returns Number of pending photo requests\n */\n getPhotoPendingRequestCount(): number {\n return this.pendingPhotoRequests.size\n }\n\n /**\n * 📋 Get all pending photo request IDs\n *\n * @returns Array of pending request IDs\n */\n getPhotoPendingRequestIds(): string[] {\n return Array.from(this.pendingPhotoRequests.keys())\n }\n\n /**\n * ❌ Cancel a pending photo request\n *\n * @param requestId - The request ID to cancel\n * @returns true if the request was cancelled, false if it wasn't found\n */\n cancelPhotoRequest(requestId: string): boolean {\n const pendingRequest = this.pendingPhotoRequests.get(requestId)\n if (pendingRequest) {\n pendingRequest.reject(\"Photo request cancelled\")\n this.pendingPhotoRequests.delete(requestId)\n this.logger.info({requestId}, `📸 Photo request cancelled`)\n return true\n }\n return false\n }\n\n /**\n * 🧹 Cancel all pending photo requests\n *\n * @returns Number of requests that were cancelled\n */\n cancelAllPhotoRequests(): number {\n const count = this.pendingPhotoRequests.size\n\n for (const [requestId, {reject}] of this.pendingPhotoRequests) {\n reject(\"Photo request cancelled - session cleanup\")\n this.logger.info({requestId}, `📸 Photo request cancelled during cleanup`)\n }\n\n this.pendingPhotoRequests.clear()\n return count\n }\n\n // =====================================\n // 📹 Streaming Functionality\n // =====================================\n\n /**\n * 📹 Start an RTMP stream to the specified URL\n *\n * @param options - Configuration options for the stream\n * @returns Promise that resolves when the stream request is sent (not when streaming begins)\n *\n * @example\n * ```typescript\n * await session.camera.startStream({\n * rtmpUrl: 'rtmp://live.example.com/stream/key',\n * video: { resolution: '1920x1080', bitrate: 5000 },\n * audio: { bitrate: 128 }\n * });\n * ```\n */\n async startStream(options: RtmpStreamOptions): Promise<void> {\n this.logger.info({rtmpUrl: options.rtmpUrl}, `📹 RTMP stream request starting`)\n\n cameraWarnLog(this.session.getHttpsServerUrl?.(), this.packageName, \"startStream\")\n\n if (!options.rtmpUrl) {\n throw new Error(\"rtmpUrl is required\")\n }\n\n if (this.isStreaming) {\n this.logger.error(\n {\n currentStreamUrl: this.currentStreamUrl,\n requestedUrl: options.rtmpUrl,\n },\n `📹 Already streaming error`,\n )\n throw new Error(\"Already streaming. Stop the current stream before starting a new one.\")\n }\n\n // Create stream request message\n const message: RtmpStreamRequest = {\n type: AppToCloudMessageType.RTMP_STREAM_REQUEST,\n packageName: this.packageName,\n sessionId: this.sessionId,\n rtmpUrl: options.rtmpUrl,\n video: options.video,\n audio: options.audio,\n stream: options.stream,\n timestamp: new Date(),\n }\n\n // Save stream URL for reference\n this.currentStreamUrl = options.rtmpUrl\n\n // Send the request\n try {\n this.session.sendMessage(message)\n this.isStreaming = true\n\n this.logger.info({rtmpUrl: options.rtmpUrl}, `📹 RTMP stream request sent successfully`)\n return Promise.resolve()\n } catch (error) {\n this.logger.error({error, rtmpUrl: options.rtmpUrl}, `📹 Failed to send RTMP stream request`)\n const errorMessage = error instanceof Error ? error.message : String(error)\n return Promise.reject(`Failed to request RTMP stream: ${errorMessage}`)\n }\n }\n\n /**\n * 🛑 Stop the current RTMP stream\n *\n * @returns Promise that resolves when the stop request is sent\n *\n * @example\n * ```typescript\n * await session.camera.stopStream();\n * ```\n */\n async stopStream(): Promise<void> {\n this.logger.info(\n {\n isCurrentlyStreaming: this.isStreaming,\n currentStreamUrl: this.currentStreamUrl,\n },\n `📹 RTMP stream stop request`,\n )\n\n if (!this.isStreaming) {\n this.logger.info(`📹 Not streaming - no-op`)\n // Not an error - just a no-op if not streaming\n return Promise.resolve()\n }\n\n // Create stop request message\n const message: RtmpStreamStopRequest = {\n type: AppToCloudMessageType.RTMP_STREAM_STOP,\n packageName: this.packageName,\n sessionId: this.sessionId,\n streamId: this.currentStreamState?.streamId, // Include streamId if available\n timestamp: new Date(),\n }\n\n // Send the request\n try {\n this.session.sendMessage(message)\n return Promise.resolve()\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n return Promise.reject(`Failed to stop RTMP stream: ${errorMessage}`)\n }\n }\n\n /**\n * 🔍 Check if currently streaming\n *\n * @returns True if a stream is active or initializing\n */\n isCurrentlyStreaming(): boolean {\n return this.isStreaming\n }\n\n /**\n * 📍 Get the URL of the current stream (if any)\n *\n * @returns The RTMP URL of the current stream, or undefined if not streaming\n */\n getCurrentStreamUrl(): string | undefined {\n return this.currentStreamUrl\n }\n\n /**\n * 📊 Get the current stream status\n *\n * @returns The current stream status, or undefined if not available\n */\n getStreamStatus(): RtmpStreamStatus | undefined {\n return this.currentStreamState\n }\n\n /**\n * 📺 Subscribe to RTMP stream status updates\n * This uses the standard stream subscription mechanism\n */\n subscribeToStreamStatusUpdates(): void {\n if (this.session) {\n this.session.subscribe(StreamType.RTMP_STREAM_STATUS)\n } else {\n this.logger.error(\"Cannot subscribe to status updates: session reference not available\")\n }\n }\n\n /**\n * 📺 Unsubscribe from RTMP stream status updates\n */\n unsubscribeFromStreamStatusUpdates(): void {\n if (this.session) {\n this.session.unsubscribe(StreamType.RTMP_STREAM_STATUS)\n }\n }\n\n /**\n * 👂 Listen for stream status updates using the standard event system\n * @param handler - Function to call when stream status changes\n * @returns Cleanup function to remove the handler\n *\n * @example\n * ```typescript\n * const cleanup = session.camera.onStreamStatus((status) => {\n * console.log('Stream status:', status.status);\n * if (status.status === 'error') {\n * console.error('Stream error:', status.errorDetails);\n * }\n * });\n *\n * // Later, cleanup the listener\n * cleanup();\n * ```\n */\n onStreamStatus(handler: StreamStatusHandler): () => void {\n if (!this.session) {\n this.logger.error(\"Cannot listen for status updates: session reference not available\")\n return () => {}\n }\n\n this.subscribeToStreamStatusUpdates()\n return this.session.on(StreamType.RTMP_STREAM_STATUS, handler)\n }\n\n /**\n * 🔄 Update internal stream state based on a status message\n * For internal use by AppSession\n * @param message - The status message from the cloud\n * @internal This method is used internally by AppSession\n */\n updateStreamState(message: any): void {\n this.logger.debug(\n {\n messageType: message?.type,\n messageStatus: message?.status,\n currentIsStreaming: this.isStreaming,\n },\n `📹 Stream state update`,\n )\n\n // Verify this is a valid stream response\n if (!isRtmpStreamStatus(message)) {\n this.logger.warn({message}, `📹 Received invalid stream status message`)\n return\n }\n\n // Convert to StreamStatus format\n const status: RtmpStreamStatus = {\n type: message.type,\n streamId: message.streamId,\n status: message.status,\n errorDetails: message.errorDetails,\n appId: message.appId,\n stats: message.stats,\n timestamp: message.timestamp || new Date(),\n }\n\n this.logger.info(\n {\n streamId: status.streamId,\n oldStatus: this.currentStreamState?.status,\n newStatus: status.status,\n wasStreaming: this.isStreaming,\n },\n `📹 Stream status processed`,\n )\n\n // Update local state based on status\n if (status.status === \"stopped\" || status.status === \"error\" || status.status === \"timeout\") {\n this.logger.info(\n {\n status: status.status,\n wasStreaming: this.isStreaming,\n },\n `📹 Stream stopped - updating local state`,\n )\n this.isStreaming = false\n this.currentStreamUrl = undefined\n }\n\n // Save the latest status\n this.currentStreamState = status\n }\n\n // =====================================\n // 📹 Managed Streaming Functionality\n // =====================================\n\n /**\n * 📹 Start a managed stream\n *\n * The cloud handles the RTMP endpoint and returns HLS/DASH URLs for viewing.\n * Multiple apps can consume the same managed stream simultaneously.\n *\n * @param options - Configuration options for the managed stream\n * @returns Promise that resolves with viewing URLs when the stream is ready\n *\n * @example\n * ```typescript\n * const urls = await session.camera.startManagedStream({\n * quality: '720p',\n * enableWebRTC: true\n * });\n * console.log('HLS URL:', urls.hlsUrl);\n * ```\n */\n async startManagedStream(options?: ManagedStreamOptions): Promise<ManagedStreamResult> {\n return this.managedExtension.startManagedStream(options)\n }\n\n /**\n * 🛑 Stop the current managed stream\n *\n * This will stop streaming for this app only. If other apps are consuming\n * the same managed stream, it will continue for them.\n *\n * @returns Promise that resolves when the stop request is sent\n */\n async stopManagedStream(): Promise<void> {\n return this.managedExtension.stopManagedStream()\n }\n\n /**\n * 🔔 Register a handler for managed stream status updates\n *\n * @param handler - Function to call when stream status changes\n * @returns Cleanup function to unregister the handler\n */\n onManagedStreamStatus(handler: (status: ManagedStreamStatus) => void): () => void {\n return this.managedExtension.onManagedStreamStatus(handler)\n }\n\n /**\n * 📊 Check if currently managed streaming\n *\n * @returns true if a managed stream is active\n */\n isManagedStreamActive(): boolean {\n return this.managedExtension.isManagedStreamActive()\n }\n\n /**\n * 🔗 Get current managed stream URLs\n *\n * @returns Current stream URLs or undefined if not streaming\n */\n getManagedStreamUrls(): ManagedStreamResult | undefined {\n return this.managedExtension.getManagedStreamUrls()\n }\n\n /**\n * 🔍 Check for any existing streams (managed or unmanaged) for the current user\n *\n * This method checks if there's already an active stream for the current user,\n * which is useful to avoid conflicts and to reconnect to existing streams.\n *\n * @returns Promise that resolves with stream information if a stream exists\n *\n * @example\n * ```typescript\n * const streamInfo = await session.camera.checkExistingStream();\n * if (streamInfo.hasActiveStream) {\n * console.log('Stream type:', streamInfo.streamInfo?.type);\n * if (streamInfo.streamInfo?.type === 'managed') {\n * console.log('HLS URL:', streamInfo.streamInfo.hlsUrl);\n * } else {\n * console.log('RTMP URL:', streamInfo.streamInfo.rtmpUrl);\n * }\n * }\n * ```\n */\n async checkExistingStream(): Promise<{\n hasActiveStream: boolean\n streamInfo?: {\n type: \"managed\" | \"unmanaged\"\n streamId: string\n status: string\n createdAt: Date\n // For managed streams\n hlsUrl?: string\n dashUrl?: string\n webrtcUrl?: string\n previewUrl?: string\n thumbnailUrl?: string\n activeViewers?: number\n // For unmanaged streams\n rtmpUrl?: string\n requestingAppId?: string\n }\n }> {\n return this.managedExtension.checkExistingStream()\n }\n\n /**\n * Handle incoming stream status check response\n * @internal\n */\n handleStreamCheckResponse(response: StreamStatusCheckResponse): void {\n this.managedExtension.handleStreamCheckResponse(response)\n }\n\n /**\n * Handle incoming managed stream status messages\n * @internal\n */\n handleManagedStreamStatus(message: ManagedStreamStatus): void {\n this.managedExtension.handleManagedStreamStatus(message)\n }\n\n // =====================================\n // 🔧 General Utilities\n // =====================================\n\n /**\n * 🔧 Update the session ID (used when reconnecting)\n *\n * @param newSessionId - The new session ID\n * @internal This method is used internally by AppSession\n */\n updateSessionId(newSessionId: string): void {\n this.sessionId = newSessionId\n }\n\n /**\n * 🧹 Cancel all pending requests and clean up resources\n *\n * @returns Object with counts of cancelled requests\n */\n cancelAllRequests(): {photoRequests: number} {\n const photoRequests = this.cancelAllPhotoRequests()\n\n // Stop streaming if active\n if (this.isStreaming) {\n this.stopStream().catch((error) => {\n this.logger.error({error}, \"Error stopping stream during cleanup\")\n })\n }\n\n // Clean up managed extension\n this.managedExtension.cleanup()\n\n return {photoRequests}\n }\n}\n\n// Re-export types for convenience\nexport {VideoConfig, AudioConfig, StreamConfig, StreamStatusHandler}\n",
|
|
33
|
+
"/**\n * 📷 Camera Module Managed Streaming Extension\n *\n * Extends the camera module with managed streaming capabilities.\n * Apps can request managed streams and receive HLS/DASH URLs without managing RTMP endpoints.\n */\n\nimport {\n ManagedStreamRequest,\n ManagedStreamStopRequest,\n ManagedStreamStatus,\n StreamStatusCheckRequest,\n StreamStatusCheckResponse,\n AppToCloudMessageType,\n StreamType,\n RestreamDestination,\n} from \"../../../types\"\nimport {VideoConfig, AudioConfig, StreamConfig} from \"../../../types/rtmp-stream\"\nimport {Logger} from \"pino\"\n\n/**\n * Configuration options for a managed stream\n */\nexport interface ManagedStreamOptions {\n /** Stream quality preset */\n quality?: \"720p\" | \"1080p\"\n /** Enable WebRTC for ultra-low latency viewing */\n enableWebRTC?: boolean\n /** Optional video configuration settings */\n video?: VideoConfig\n /** Optional audio configuration settings */\n audio?: AudioConfig\n /** Optional stream configuration settings */\n stream?: StreamConfig\n /** Optional RTMP destinations to re-stream to (YouTube, Twitch, etc) */\n restreamDestinations?: RestreamDestination[]\n}\n\n/**\n * Result returned when starting a managed stream\n */\nexport interface ManagedStreamResult {\n /** HLS URL for viewing the stream */\n hlsUrl: string\n /** DASH URL for viewing the stream */\n dashUrl: string\n /** WebRTC URL if enabled */\n webrtcUrl?: string\n /** Cloudflare Stream player/preview URL for embedding */\n previewUrl?: string\n /** Thumbnail image URL */\n thumbnailUrl?: string\n /** Internal stream ID */\n streamId: string\n}\n\n/**\n * 📹 Managed Streaming Extension for Camera Module\n *\n * Provides managed streaming capabilities where the cloud handles\n * RTMP endpoints and returns HLS/DASH URLs for viewing.\n *\n * @example\n * ```typescript\n * // Start a managed stream\n * const urls = await session.camera.startManagedStream({\n * quality: '720p',\n * enableWebRTC: true\n * });\n * console.log('HLS URL:', urls.hlsUrl);\n * console.log('DASH URL:', urls.dashUrl);\n * console.log('Player URL:', urls.previewUrl);\n * console.log('Thumbnail:', urls.thumbnailUrl);\n *\n * // Monitor managed stream status\n * session.camera.onManagedStreamStatus((status) => {\n * console.log('Managed stream status:', status.status);\n * });\n *\n * // Stop managed stream\n * await session.camera.stopManagedStream();\n * ```\n */\nexport class CameraManagedExtension {\n private session: any\n private packageName: string\n private sessionId: string\n private logger: Logger\n\n // Managed streaming state\n private isManagedStreaming: boolean = false\n private currentManagedStreamId?: string\n private currentManagedStreamUrls?: ManagedStreamResult\n private managedStreamStatus?: ManagedStreamStatus\n\n // For tracking pending stream check requests\n private pendingStreamChecks?: Map<\n string,\n {\n resolve: (value: any) => void\n timeoutId: NodeJS.Timeout\n }\n >\n\n // Promise tracking for managed stream initialization\n private pendingManagedStreamRequest?: {\n resolve: (value: ManagedStreamResult) => void\n reject: (reason?: any) => void\n }\n\n constructor(session: any, packageName: string, sessionId: string, logger: Logger) {\n this.session = session\n this.packageName = packageName\n this.sessionId = sessionId\n this.logger = logger.child({module: \"CameraManagedExtension\"})\n }\n\n /**\n * 📹 Start a managed stream\n *\n * The cloud will handle the RTMP endpoint and return HLS/DASH URLs for viewing.\n * Multiple apps can consume the same managed stream simultaneously.\n *\n * @param options - Configuration options for the managed stream\n * @returns Promise that resolves with viewing URLs when the stream is ready\n *\n * @example\n * ```typescript\n * const urls = await session.camera.startManagedStream({\n * quality: '1080p',\n * enableWebRTC: true,\n * video: { fps: 30 },\n * audio: { sampleRate: 48000 }\n * });\n *\n * // Access all available URLs\n * console.log('HLS URL:', urls.hlsUrl);\n * console.log('DASH URL:', urls.dashUrl);\n * console.log('Player URL:', urls.previewUrl); // Embeddable player\n * console.log('Thumbnail:', urls.thumbnailUrl);\n * ```\n */\n async startManagedStream(options: ManagedStreamOptions = {}): Promise<ManagedStreamResult> {\n this.logger.info({options}, \"📹 Managed stream request starting\")\n\n if (this.isManagedStreaming) {\n this.logger.error(\n {\n currentStreamId: this.currentManagedStreamId,\n },\n \"📹 Already managed streaming error\",\n )\n throw new Error(\"Already streaming. Stop the current managed stream before starting a new one.\")\n }\n\n // Create the request message\n const request: ManagedStreamRequest = {\n type: AppToCloudMessageType.MANAGED_STREAM_REQUEST,\n packageName: this.packageName,\n quality: options.quality,\n enableWebRTC: options.enableWebRTC,\n video: options.video,\n audio: options.audio,\n stream: options.stream,\n restreamDestinations: options.restreamDestinations,\n }\n\n // Send the request\n this.session.sendMessage(request)\n this.isManagedStreaming = true\n\n // Create promise to wait for URLs\n return new Promise((resolve, reject) => {\n this.pendingManagedStreamRequest = {resolve, reject}\n\n // Set a timeout\n setTimeout(() => {\n if (this.pendingManagedStreamRequest) {\n this.pendingManagedStreamRequest = undefined\n this.isManagedStreaming = false\n reject(new Error(\"Managed stream request timeout\"))\n }\n }, 30000) // 30 second timeout\n })\n }\n\n /**\n * 🛑 Stop the current managed stream\n *\n * This will stop streaming for this app only. If other apps are consuming\n * the same managed stream, it will continue for them.\n *\n * @returns Promise that resolves when the stop request is sent\n */\n async stopManagedStream(): Promise<void> {\n // Always send the stop request - the cloud will handle whether there's actually\n // a stream to stop. This ensures the stop works even after reload/reconnection\n this.logger.info(\n {\n streamId: this.currentManagedStreamId,\n hasInternalState: this.isManagedStreaming,\n },\n \"📹 Sending managed stream stop request\",\n )\n\n const request: ManagedStreamStopRequest = {\n type: AppToCloudMessageType.MANAGED_STREAM_STOP,\n packageName: this.packageName,\n }\n\n this.session.sendMessage(request)\n\n // Don't clean up state immediately - wait for the 'stopped' status from cloud\n // This ensures we can retry stop if needed and maintains accurate state\n }\n\n /**\n * 🔍 Check for any existing streams (managed or unmanaged) for the current user\n *\n * @returns Promise that resolves with stream information if a stream exists\n *\n * @example\n * ```typescript\n * const streamInfo = await session.camera.checkExistingStream();\n * if (streamInfo.hasActiveStream) {\n * console.log('Stream type:', streamInfo.streamInfo?.type);\n * if (streamInfo.streamInfo?.type === 'managed') {\n * console.log('HLS URL:', streamInfo.streamInfo.hlsUrl);\n * }\n * }\n * ```\n */\n async checkExistingStream(): Promise<{\n hasActiveStream: boolean\n streamInfo?: {\n type: \"managed\" | \"unmanaged\"\n streamId: string\n status: string\n createdAt: Date\n // For managed streams\n hlsUrl?: string\n dashUrl?: string\n webrtcUrl?: string\n previewUrl?: string\n thumbnailUrl?: string\n activeViewers?: number\n // For unmanaged streams\n rtmpUrl?: string\n requestingAppId?: string\n }\n }> {\n return new Promise((resolve) => {\n // Store the resolver for the response\n const requestId = `stream_check_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`\n\n // Store a pending request that will be resolved when we get the response\n if (!this.pendingStreamChecks) {\n this.pendingStreamChecks = new Map()\n }\n\n const timeoutId = setTimeout(() => {\n this.pendingStreamChecks?.delete(requestId)\n resolve({hasActiveStream: false})\n }, 5000) // 5 second timeout\n\n this.pendingStreamChecks.set(requestId, {\n resolve,\n timeoutId,\n })\n\n // Send the check request with the requestId\n const request: StreamStatusCheckRequest = {\n type: AppToCloudMessageType.STREAM_STATUS_CHECK,\n packageName: this.packageName,\n sessionId: this.sessionId,\n }\n\n this.session.sendMessage(request)\n })\n }\n\n /**\n * 📊 Check if currently managed streaming\n *\n * @returns true if a managed stream is active\n */\n isManagedStreamActive(): boolean {\n return this.isManagedStreaming\n }\n\n /**\n * 🔗 Get current managed stream URLs\n *\n * @returns Current stream URLs or undefined if not streaming\n */\n getManagedStreamUrls(): ManagedStreamResult | undefined {\n return this.currentManagedStreamUrls\n }\n\n /**\n * 📊 Get current managed stream status\n *\n * @returns Current stream status or undefined\n */\n getManagedStreamStatus(): ManagedStreamStatus | undefined {\n return this.managedStreamStatus\n }\n\n /**\n * 🔔 Register a handler for managed stream status updates\n *\n * @param handler - Function to call when stream status changes\n * @returns Cleanup function to unregister the handler\n *\n * @example\n * ```typescript\n * const cleanup = session.camera.onManagedStreamStatus((status) => {\n * console.log('Status:', status.status);\n * if (status.status === 'active') {\n * console.log('Stream is live!');\n * }\n * });\n *\n * // Later, unregister the handler\n * cleanup();\n * ```\n */\n onManagedStreamStatus(handler: (status: ManagedStreamStatus) => void): () => void {\n if (!this.session) {\n this.logger.error(\"Cannot listen for managed status updates: session reference not available\")\n return () => {}\n }\n\n this.session.subscribe(StreamType.MANAGED_STREAM_STATUS)\n\n // Register the handler using the session's event system\n return this.session.on(StreamType.MANAGED_STREAM_STATUS, handler)\n }\n\n /**\n * Handle incoming stream status check response\n * Called by the parent AppSession when a response is received\n */\n handleStreamCheckResponse(response: StreamStatusCheckResponse): void {\n // Find and resolve any pending stream check\n if (this.pendingStreamChecks && this.pendingStreamChecks.size > 0) {\n const firstEntry = this.pendingStreamChecks.entries().next()\n if (!firstEntry.done && firstEntry.value) {\n const [requestId, pending] = firstEntry.value\n if (pending) {\n clearTimeout(pending.timeoutId)\n this.pendingStreamChecks.delete(requestId)\n pending.resolve(response)\n }\n }\n }\n }\n\n /**\n * Handle incoming managed stream status messages\n * Called by the parent AppSession when messages are received\n */\n handleManagedStreamStatus(status: ManagedStreamStatus): void {\n this.logger.info(\n {\n status: status.status,\n streamId: status.streamId,\n },\n \"📹 Received managed stream status\",\n )\n\n this.managedStreamStatus = status\n\n // Handle initializing status - stream is starting\n if (status.status === \"initializing\" && status.streamId) {\n this.isManagedStreaming = true\n this.currentManagedStreamId = status.streamId\n }\n\n // Handle initial stream ready status\n if (status.status === \"active\") {\n // Always update state when stream is active\n this.isManagedStreaming = true\n this.currentManagedStreamId = status.streamId\n\n if (status.hlsUrl && status.dashUrl) {\n const result: ManagedStreamResult = {\n hlsUrl: status.hlsUrl,\n dashUrl: status.dashUrl,\n webrtcUrl: status.webrtcUrl,\n previewUrl: status.previewUrl,\n thumbnailUrl: status.thumbnailUrl,\n streamId: status.streamId || \"\",\n }\n\n this.currentManagedStreamUrls = result\n\n // Resolve pending promise if exists\n if (this.pendingManagedStreamRequest) {\n this.pendingManagedStreamRequest.resolve(result)\n this.pendingManagedStreamRequest = undefined\n }\n }\n }\n\n // Handle error status\n if ((status.status === \"error\" || status.status === \"stopped\") && this.pendingManagedStreamRequest) {\n this.pendingManagedStreamRequest.reject(new Error(status.message || \"Managed stream failed\"))\n this.pendingManagedStreamRequest = undefined\n this.isManagedStreaming = false\n this.currentManagedStreamId = undefined\n this.currentManagedStreamUrls = undefined\n }\n\n // Clean up on stopped status\n if (status.status === \"stopped\") {\n this.isManagedStreaming = false\n this.currentManagedStreamId = undefined\n this.currentManagedStreamUrls = undefined\n }\n\n // Clean up local state on error regardless of pending request state\n if (status.status === \"error\") {\n this.isManagedStreaming = false\n this.currentManagedStreamId = undefined\n this.currentManagedStreamUrls = undefined\n }\n\n // Notify handlers (would use event emitter in real implementation)\n // this.emit('managedStreamStatus', status);\n }\n\n /**\n * 🧹 Clean up all managed streaming state\n */\n cleanup(): void {\n if (this.pendingManagedStreamRequest) {\n this.pendingManagedStreamRequest.reject(new Error(\"Camera module cleanup\"))\n this.pendingManagedStreamRequest = undefined\n }\n\n this.isManagedStreaming = false\n this.currentManagedStreamId = undefined\n this.currentManagedStreamUrls = undefined\n this.managedStreamStatus = undefined\n\n this.logger.info(\"📹 Managed streaming extension cleaned up\")\n }\n}\n",
|
|
34
|
+
"/**\n * 💡 LED Control Module\n *\n * Provides RGB LED control functionality for App Sessions.\n * Controls the RGB LEDs on smart glasses with custom colors and timing patterns.\n */\n\nimport {RgbLedControlRequest, AppToCloudMessageType, LedColor} from \"../../../types\"\nimport {Logger} from \"pino\"\n\n/**\n * Options for LED control\n */\nexport interface LedControlOptions {\n /** LED color */\n color?: LedColor\n /** LED on duration in milliseconds */\n ontime?: number\n /** LED off duration in milliseconds */\n offtime?: number\n /** Number of on/off cycles */\n count?: number\n}\n\n/**\n * 💡 LED Control Module Implementation\n *\n * Provides methods for controlling LEDs on smart glasses.\n * Supports both low-level on/off control and higher-level pattern methods.\n *\n * @example\n * ```typescript\n * // General LED control\n * await session.led.turnOn({ color: 'red', ontime: 1000, count: 1 });\n *\n * // Blink green LED\n * await session.led.blink('green', 500, 500, 5);\n *\n * // Turn off all LEDs\n * await session.led.turnOff();\n * ```\n */\nexport class LedModule {\n private session: any\n private packageName: string\n private sessionId: string\n private logger: Logger\n\n /**\n * Create a new LedModule\n *\n * @param session - Reference to the parent AppSession\n * @param packageName - The App package name\n * @param sessionId - The current session ID\n * @param logger - Logger instance for debugging\n */\n constructor(session: any, packageName: string, sessionId: string, logger?: Logger) {\n this.session = session\n this.packageName = packageName\n this.sessionId = sessionId\n this.logger = logger || (console as any)\n }\n\n // =====================================\n // 💡 Low-level LED Control\n // =====================================\n\n /**\n * 💡 Turn on an LED with specified timing parameters\n *\n * @param options - LED control options (color, timing, count)\n * @returns Promise that resolves immediately after sending the command\n *\n * @example\n * ```typescript\n * // Solid red for 2 seconds\n * await session.led.turnOn({ color: 'red', ontime: 2000, count: 1 });\n *\n * // Blink white 3 times\n * await session.led.turnOn({ color: 'white', ontime: 500, offtime: 500, count: 3 });\n * ```\n */\n async turnOn(options: LedControlOptions): Promise<void> {\n try {\n // Generate unique request ID for tracking\n const requestId = `led_req_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`\n\n // Create LED control request message\n const message: RgbLedControlRequest = {\n type: AppToCloudMessageType.RGB_LED_CONTROL,\n packageName: this.packageName,\n sessionId: this.sessionId,\n requestId,\n timestamp: new Date(),\n action: \"on\",\n color: options.color || \"red\",\n ontime: options.ontime || 1000,\n offtime: options.offtime || 0,\n count: options.count || 1,\n }\n\n // Send request to cloud (fire-and-forget)\n this.session.sendMessage(message)\n\n this.logger.info(\n {\n requestId,\n color: options.color,\n ontime: options.ontime,\n offtime: options.offtime,\n count: options.count,\n },\n `💡 LED control request sent`,\n )\n\n // Resolve immediately - no waiting for response\n return Promise.resolve()\n } catch (error) {\n this.logger.error({error, options}, \"❌ Error in LED turnOn request\")\n throw error\n }\n }\n\n /**\n * 💡 Turn off all LEDs\n *\n * @returns Promise that resolves immediately after sending the command\n *\n * @example\n * ```typescript\n * await session.led.turnOff();\n * ```\n */\n async turnOff(): Promise<void> {\n try {\n // Generate unique request ID for tracking\n const requestId = `led_req_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`\n\n // Create LED control request message\n const message: RgbLedControlRequest = {\n type: AppToCloudMessageType.RGB_LED_CONTROL,\n packageName: this.packageName,\n sessionId: this.sessionId,\n requestId,\n timestamp: new Date(),\n action: \"off\",\n }\n\n // Send request to cloud (fire-and-forget)\n this.session.sendMessage(message)\n\n this.logger.info({requestId}, `💡 LED turn off request sent`)\n\n // Resolve immediately - no waiting for response\n return Promise.resolve()\n } catch (error) {\n this.logger.error({error}, \"❌ Error in LED turnOff request\")\n throw error\n }\n }\n\n /**\n * 💡 Get available LED capabilities\n *\n * @returns Array of available LED configurations\n *\n * @example\n * ```typescript\n * const capabilities = session.led.getCapabilities();\n * console.log('Available LEDs:', capabilities);\n * ```\n */\n getCapabilities(): Array<{\n id: string\n purpose: string\n isFullColor: boolean\n color?: string\n position?: string\n }> {\n // This would need to be implemented with access to session capabilities\n // For now, return empty array - this would be populated from session.capabilities\n return []\n }\n\n // =====================================\n // 💡 Pattern Methods (SDK-generated)\n // =====================================\n\n /**\n * 💡 Blink an LED with specified timing\n *\n * @param color - LED color to use\n * @param ontime - How long LED stays on (ms)\n * @param offtime - How long LED stays off (ms)\n * @param count - Number of blink cycles\n * @returns Promise that resolves immediately after sending the command\n *\n * @example\n * ```typescript\n * // Blink red LED 5 times (500ms on, 500ms off)\n * await session.led.blink('red', 500, 500, 5);\n * ```\n */\n async blink(color: LedColor, ontime: number, offtime: number, count: number): Promise<void> {\n return this.turnOn({\n color,\n ontime,\n offtime,\n count,\n })\n }\n\n /**\n * 💡 Solid LED mode - LED stays on continuously for specified duration\n *\n * Creates a solid LED effect with no off time, perfect for continuous illumination.\n *\n * @param color - LED color to use\n * @param duration - How long LED stays on (ms)\n * @returns Promise that resolves when the command is sent\n *\n * @example\n * ```typescript\n * // Solid red LED for 5 seconds\n * await session.led.solid('red', 5000);\n *\n * // Solid white LED for 30 seconds\n * await session.led.solid('white', 30000);\n * ```\n */\n async solid(color: LedColor, duration: number): Promise<void> {\n return this.turnOn({\n color,\n ontime: duration,\n offtime: 0, // No off time for solid mode\n count: 1, // Single cycle\n })\n }\n\n // =====================================\n // 💡 Cleanup\n // =====================================\n\n /**\n * Clean up LED module\n *\n * @internal\n */\n cleanup(): void {\n this.logger.info(\"🧹 LED module cleaned up\")\n }\n}\n",
|
|
35
|
+
"/**\n * 🔊 Audio Module\n *\n * Audio functionality for App Sessions.\n * Handles audio playback on connected glasses.\n */\n\nimport {AudioPlayRequest, AudioPlayResponse, AudioStopRequest, AppToCloudMessageType} from \"../../../types\"\nimport {Logger} from \"pino\"\n\n/**\n * Options for audio playback\n */\nexport interface AudioPlayOptions {\n /** URL to audio file for download and play */\n audioUrl: string\n /** Volume level 0.0-1.0, defaults to 1.0 */\n volume?: number\n /** Whether to stop other audio playback, defaults to true */\n stopOtherAudio?: boolean\n /**\n * Track ID for audio playback (defaults to 0)\n * - 0: speaker (default audio playback)\n * - 1: app_audio (app-specific audio)\n * - 2: tts (text-to-speech audio)\n * Use different track IDs to play multiple audio streams simultaneously (mixing)\n */\n trackId?: number\n}\n\n/**\n * Options for text-to-speech\n */\nexport interface SpeakOptions {\n /** Voice ID to use (optional, defaults to server's ELEVENLABS_DEFAULT_VOICE_ID) */\n voice_id?: string\n /** Model ID to use (optional, defaults to eleven_flash_v2_5) */\n model_id?: string\n /** Voice settings object (optional) */\n voice_settings?: {\n stability?: number\n similarity_boost?: number\n style?: number\n use_speaker_boost?: boolean\n speed?: number\n }\n /** Volume level 0.0-1.0, defaults to 1.0 */\n volume?: number\n /** Whether to stop other audio playback, defaults to true */\n stopOtherAudio?: boolean\n /**\n * Track ID for audio playback (defaults to 2 for TTS)\n * - 0: speaker (default audio playback)\n * - 1: app_audio (app-specific audio)\n * - 2: tts (text-to-speech audio)\n * Use different track IDs to play multiple audio streams simultaneously (mixing)\n */\n trackId?: number\n}\n\n/**\n * Result of audio playback attempt\n */\nexport interface AudioPlayResult {\n /** Whether the audio playback was successful */\n success: boolean\n /** Error message if playback failed */\n error?: string\n /** Duration of the audio file in seconds (if available) */\n duration?: number\n}\n\n/**\n * 🔊 Audio Module Implementation\n *\n * Audio management for App Sessions.\n * Provides methods for:\n * - 🎵 Playing audio on glasses\n * - ⏹️ Stopping audio playback\n * - 🔍 Monitoring audio request status\n * - 🧹 Cleanup and cancellation\n *\n * @example\n * ```typescript\n * // Play audio\n * const result = await session.audio.playAudio({\n * audioUrl: 'https://example.com/sound.mp3',\n * volume: 0.8\n * });\n *\n * // Stop all audio\n * session.audio.stopAudio();\n * ```\n */\nexport class AudioManager {\n private session: any // Reference to AppSession\n private packageName: string\n private sessionId: string\n private logger: Logger\n\n /** Map to store pending audio play request promises */\n private pendingAudioRequests = new Map<\n string,\n {\n resolve: (value: AudioPlayResult) => void\n reject: (reason?: string) => void\n }\n >()\n\n /**\n * Create a new AudioManager\n *\n * @param packageName - The App package name\n * @param sessionId - The current session ID\n * @param send - Function to send messages to the cloud\n * @param session - Reference to the parent AppSession (optional)\n * @param logger - Logger instance for debugging\n */\n constructor(session: any, packageName: string, sessionId: string, logger?: Logger) {\n this.session = session\n this.packageName = packageName\n this.sessionId = sessionId\n this.logger = logger || (console as any)\n }\n\n // =====================================\n // 🎵 Audio Playback Functionality\n // =====================================\n\n /**\n * 🔊 Play audio on the connected glasses\n * @param options - Audio playback configuration\n * @returns Promise that resolves with playback result\n *\n * @example\n * ```typescript\n * // Play audio from URL\n * const result = await session.audio.playAudio({\n * audioUrl: 'https://example.com/sound.mp3',\n * volume: 0.8\n * });\n * ```\n */\n async playAudio(options: AudioPlayOptions): Promise<AudioPlayResult> {\n return new Promise((resolve, reject) => {\n try {\n // Validate input\n if (!options.audioUrl) {\n reject(\"audioUrl must be provided\")\n return\n }\n\n // Generate unique request ID\n const requestId = `audio_req_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`\n\n const stopOtherAudio = options.stopOtherAudio ?? true\n\n // CRITICAL: When stopOtherAudio=false (concurrent/mixing mode),\n // resolve immediately after sending the request (fire-and-forget)\n // This allows multiple audio streams to play simultaneously\n if (!stopOtherAudio) {\n // Create audio play request message\n const message: AudioPlayRequest = {\n type: AppToCloudMessageType.AUDIO_PLAY_REQUEST,\n packageName: this.packageName,\n sessionId: this.sessionId,\n requestId,\n timestamp: new Date(),\n audioUrl: options.audioUrl,\n volume: options.volume ?? 1.0,\n stopOtherAudio: false,\n trackId: options.trackId ?? 0, // Default to track 0 (speaker)\n }\n\n // Send request to cloud\n this.session.sendMessage(message)\n\n // Resolve immediately for concurrent playback (fire-and-forget)\n // The audio will play in the background without blocking\n this.logger.debug({requestId}, `🔊 Audio playback started in non-blocking mode (concurrent)`)\n resolve({\n success: true,\n duration: undefined, // Duration unknown in fire-and-forget mode\n })\n return\n }\n\n // For stopOtherAudio=true (blocking/interrupt mode),\n // wait for the COMPLETED/FAILED event before resolving\n this.pendingAudioRequests.set(requestId, {resolve, reject})\n\n // Create audio play request message\n const message: AudioPlayRequest = {\n type: AppToCloudMessageType.AUDIO_PLAY_REQUEST,\n packageName: this.packageName,\n sessionId: this.sessionId,\n requestId,\n timestamp: new Date(),\n audioUrl: options.audioUrl,\n volume: options.volume ?? 1.0,\n stopOtherAudio: true,\n trackId: options.trackId ?? 0, // Default to track 0 (speaker)\n }\n\n // Send request to cloud\n this.session.sendMessage(message)\n\n // Set timeout to avoid hanging promises (only for blocking mode)\n const timeoutMs = 60000 // 60 seconds\n if (this.session && this.session.resources) {\n // Use session's resource tracker for automatic cleeanup\n this.session.resources.setTimeout(() => {\n if (this.pendingAudioRequests.has(requestId)) {\n this.pendingAudioRequests.get(requestId)!.reject(\"Audio play request timed out\")\n this.pendingAudioRequests.delete(requestId)\n this.logger.warn({requestId}, `🔊 Audio play request timed out`)\n }\n }, timeoutMs)\n } else {\n // Fallback to regular setTimeout if session not available\n setTimeout(() => {\n if (this.pendingAudioRequests.has(requestId)) {\n this.pendingAudioRequests.get(requestId)!.reject(\"Audio play request timed out\")\n this.pendingAudioRequests.delete(requestId)\n this.logger.warn({requestId}, `🔊 Audio play request timed out`)\n }\n }, timeoutMs)\n }\n } catch (error: unknown) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n reject(`Failed to play audio: ${errorMessage}`)\n }\n })\n }\n\n /**\n * 🔇 Stop audio playback on the connected glasses\n * @param trackId - Optional track ID to stop (0=speaker, 1=app_audio, 2=tts). If omitted, stops all tracks.\n *\n * @example\n * ```typescript\n * // Stop all currently playing audio\n * session.audio.stopAudio();\n *\n * // Stop only the speaker track (track_id 0)\n * session.audio.stopAudio(0);\n *\n * // Stop only TTS track (track_id 2)\n * session.audio.stopAudio(2);\n * ```\n */\n stopAudio(trackId?: number): void {\n try {\n // Create audio stop request message\n const message: AudioStopRequest = {\n type: AppToCloudMessageType.AUDIO_STOP_REQUEST,\n packageName: this.packageName,\n sessionId: this.sessionId,\n trackId,\n timestamp: new Date(),\n }\n\n // Send request to cloud (one-way, no response expected)\n this.session.sendMessage(message)\n\n const trackInfo = trackId !== undefined ? ` (track ${trackId})` : \" (all tracks)\"\n this.logger.info(`🔇 Audio stop request sent${trackInfo}`)\n } catch (error: unknown) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n this.logger.error(`Failed to stop audio: ${errorMessage}`)\n }\n }\n\n /**\n * 🗣️ Convert text to speech and play it on the connected glasses\n * @param text - Text to convert to speech (required)\n * @param options - Text-to-speech configuration (optional)\n * @returns Promise that resolves with playback result\n *\n * @example\n * ```typescript\n * // Basic text-to-speech\n * const result = await session.audio.speak('Hello, world!');\n *\n * // With custom voice settings\n * const result = await session.audio.speak('Hello, world!', {\n * voice_id: 'your_voice_id',\n * voice_settings: {\n * stability: 0.5,\n * speed: 1.2\n * },\n * volume: 0.8\n * });\n *\n * // Play TTS without stopping other audio\n * const result = await session.audio.speak('Hello, world!', {\n * stopOtherAudio: false\n * });\n * ```\n */\n async speak(text: string, options: SpeakOptions = {}): Promise<AudioPlayResult> {\n // Validate input\n if (!text) {\n throw new Error(\"text must be provided\")\n }\n\n // Get the HTTPS server URL from the session\n const baseUrl = this.session?.getHttpsServerUrl?.()\n if (!baseUrl) {\n throw new Error(\"Cannot determine server URL for TTS endpoint\")\n }\n\n // Build query parameters for the TTS endpoint\n const queryParams = new URLSearchParams()\n queryParams.append(\"text\", text)\n\n if (options.voice_id) {\n queryParams.append(\"voice_id\", options.voice_id)\n }\n\n if (options.model_id) {\n queryParams.append(\"model_id\", options.model_id)\n }\n\n if (options.voice_settings) {\n queryParams.append(\"voice_settings\", JSON.stringify(options.voice_settings))\n }\n\n // Construct the TTS URL\n const ttsUrl = `${baseUrl}/api/tts?${queryParams.toString()}`\n\n this.logger.info({text, ttsUrl}, `🗣️ Generating speech from text`)\n\n // IMPORTANT: Don't call stopAudio() here - it closes tracks completely!\n // The backend will handle stopping any ongoing playback when it receives\n // the new audio play request (via stopOtherAudio flag)\n\n // Use the existing playAudio method to play the TTS audio\n // The stopOtherAudio flag will cancel ongoing playback without closing tracks\n return this.playAudio({\n audioUrl: ttsUrl,\n volume: options.volume,\n stopOtherAudio: options.stopOtherAudio ?? true, // This flag tells backend to stop current playback\n trackId: options.trackId ?? 2, // Default to track 2 (tts)\n })\n }\n\n // =====================================\n // 📥 Response Handling\n // =====================================\n\n /**\n * 📥 Handle audio play response from cloud\n *\n * This method is called internally when an audio play response is received.\n * It resolves the corresponding pending promise with the response data.\n *\n * @param response - The audio play response received\n * @internal This method is used internally by AppSession\n */\n handleAudioPlayResponse(response: AudioPlayResponse): void {\n const pendingRequest = this.pendingAudioRequests.get(response.requestId)\n\n if (pendingRequest) {\n // Resolve the promise with the response data\n pendingRequest.resolve({\n success: response.success,\n error: response.error,\n duration: response.duration,\n })\n\n // Clean up\n this.pendingAudioRequests.delete(response.requestId)\n\n this.logger.info(\n {\n requestId: response.requestId,\n success: response.success,\n duration: response.duration,\n },\n `🔊 Audio play response received`,\n )\n } else {\n this.logger.warn({requestId: response.requestId}, `🔊 Received audio play response for unknown request ID`)\n }\n }\n\n // =====================================\n // 🔍 Status and Management\n // =====================================\n\n /**\n * 🔍 Check if there are pending audio requests\n * @param requestId - Optional specific request ID to check\n * @returns True if there are pending requests (or specific request exists)\n */\n hasPendingRequest(requestId?: string): boolean {\n if (requestId) {\n return this.pendingAudioRequests.has(requestId)\n }\n return this.pendingAudioRequests.size > 0\n }\n\n /**\n * 📊 Get the number of pending audio requests\n * @returns Number of pending requests\n */\n getPendingRequestCount(): number {\n return this.pendingAudioRequests.size\n }\n\n /**\n * 📋 Get all pending request IDs\n * @returns Array of pending request IDs\n */\n getPendingRequestIds(): string[] {\n return Array.from(this.pendingAudioRequests.keys())\n }\n\n /**\n * ❌ Cancel a specific audio request\n * @param requestId - The request ID to cancel\n * @returns True if the request was found and cancelled\n */\n cancelAudioRequest(requestId: string): boolean {\n const pendingRequest = this.pendingAudioRequests.get(requestId)\n if (pendingRequest) {\n pendingRequest.reject(\"Audio request cancelled\")\n this.pendingAudioRequests.delete(requestId)\n this.logger.info({requestId}, `🔊 Audio request cancelled`)\n return true\n }\n return false\n }\n\n /**\n * 🧹 Cancel all pending audio requests\n * @returns Number of requests that were cancelled\n */\n cancelAllAudioRequests(): number {\n const count = this.pendingAudioRequests.size\n this.pendingAudioRequests.forEach((request, requestId) => {\n request.reject(\"Audio request cancelled due to cleanup\")\n this.logger.debug({requestId}, `🔊 Audio request cancelled during cleanup`)\n })\n this.pendingAudioRequests.clear()\n\n if (count > 0) {\n this.logger.info({cancelledCount: count}, `🧹 Cancelled all pending audio requests`)\n }\n\n return count\n }\n\n // =====================================\n // 🔧 Internal Management\n // =====================================\n\n /**\n * 🔄 Update the session ID when reconnecting\n * @param newSessionId - The new session ID\n * @internal Used by AppSession during reconnection\n */\n updateSessionId(newSessionId: string): void {\n this.sessionId = newSessionId\n this.logger.debug({newSessionId}, `🔄 Audio module session ID updated`)\n }\n\n /**\n * 🧹 Cancel all pending requests (cleanup)\n * @returns Object with count of cancelled requests\n * @internal Used by AppSession during cleanup\n */\n cancelAllRequests(): {audioRequests: number} {\n const audioRequests = this.cancelAllAudioRequests()\n return {audioRequests}\n }\n}\n",
|
|
36
|
+
"/**\n * Resource Tracker\n * \n * A utility class for tracking and automatically cleaning up resources\n * like timers, event listeners, and other disposable objects.\n * \n * This helps prevent memory leaks by ensuring that all resources are\n * properly disposed when they're no longer needed.\n */\n\n/**\n * Type for a cleanup function that doesn't take any arguments\n */\nexport type CleanupFunction = () => void;\n\n/**\n * Type for any object with a dispose or close method\n */\nexport interface Disposable {\n dispose?: () => void;\n close?: () => void;\n}\n\n/**\n * Manages resources to prevent memory leaks\n */\nexport class ResourceTracker {\n // Collection of cleanup functions to call when dispose() is called\n private cleanupFunctions: CleanupFunction[] = [];\n \n // Flag to track if this resource tracker has been disposed\n private isDisposed = false;\n \n /**\n * Add a cleanup function to be executed when dispose() is called\n * \n * @param cleanup - The cleanup function to register\n * @returns A function that will remove this cleanup function\n */\n track(cleanup: CleanupFunction): CleanupFunction {\n if (this.isDisposed) {\n throw new Error('Cannot track resources on a disposed ResourceTracker');\n }\n \n this.cleanupFunctions.push(cleanup);\n \n // Return a function that will remove this cleanup function\n return () => {\n const index = this.cleanupFunctions.indexOf(cleanup);\n if (index !== -1) {\n this.cleanupFunctions.splice(index, 1);\n }\n };\n }\n \n /**\n * Track a disposable object (anything with a dispose or close method)\n * \n * @param disposable - The object to track\n * @returns A function that will remove this disposable\n */\n trackDisposable(disposable: Disposable): CleanupFunction {\n return this.track(() => {\n if (typeof disposable.dispose === 'function') {\n disposable.dispose();\n } else if (typeof disposable.close === 'function') {\n disposable.close();\n }\n });\n }\n \n /**\n * Track a timer and ensure it gets cleared\n * \n * @param timerId - The timer ID to track\n * @param isInterval - Whether this is an interval (true) or timeout (false)\n * @returns A function that will remove this timer\n */\n trackTimer(timerId: NodeJS.Timeout, isInterval = false): CleanupFunction {\n return this.track(() => {\n if (isInterval) {\n clearInterval(timerId);\n } else {\n clearTimeout(timerId);\n }\n });\n }\n \n /**\n * Track a timeout and ensure it gets cleared\n * \n * @param timerId - The timeout ID to track\n * @returns A function that will remove this timeout\n */\n trackTimeout(timerId: NodeJS.Timeout): CleanupFunction {\n return this.trackTimer(timerId, false);\n }\n \n /**\n * Track an interval and ensure it gets cleared\n * \n * @param timerId - The interval ID to track\n * @returns A function that will remove this interval\n */\n trackInterval(timerId: NodeJS.Timeout): CleanupFunction {\n return this.trackTimer(timerId, true);\n }\n \n /**\n * Create a tracked timeout\n * \n * @param callback - Function to call when the timeout expires\n * @param ms - Milliseconds to wait\n * @returns The timeout ID\n */\n setTimeout(callback: (...args: any[]) => void, ms: number): NodeJS.Timeout {\n const timerId = setTimeout(callback, ms);\n this.trackTimeout(timerId);\n return timerId;\n }\n \n /**\n * Create a tracked interval\n * \n * @param callback - Function to call at each interval\n * @param ms - Milliseconds between intervals\n * @returns The interval ID\n */\n setInterval(callback: (...args: any[]) => void, ms: number): NodeJS.Timeout {\n const timerId = setInterval(callback, ms);\n this.trackInterval(timerId);\n return timerId;\n }\n \n /**\n * Dispose of all tracked resources\n */\n dispose(): void {\n if (this.isDisposed) {\n return;\n }\n \n // Run all cleanup functions\n for (const cleanup of this.cleanupFunctions) {\n try {\n cleanup();\n } catch (error) {\n console.error('Error during resource cleanup:', error);\n }\n }\n \n // Clear the array\n this.cleanupFunctions = [];\n this.isDisposed = true;\n }\n \n /**\n * Check if this tracker has been disposed\n */\n get disposed(): boolean {\n return this.isDisposed;\n }\n}\n\n/**\n * Create a new ResourceTracker instance\n * \n * @returns A new ResourceTracker\n */\nexport function createResourceTracker(): ResourceTracker {\n return new ResourceTracker();\n}",
|
|
37
|
+
"/**\n * Simple Storage SDK Module for MentraOS Apps\n * Provides localStorage-like API with cloud synchronization\n *\n * Mental Model: App server RAM = source of truth, MongoDB = crash recovery backup\n * - User interactions read/write to RAM instantly\n * - Changes are debounced and batched to MongoDB (3s idle / 10s max)\n * - On disconnect, pending writes are flushed automatically\n */\n\nimport {AppSession} from \"..\"\n\n/**\n * Response types for Simple Storage API\n */\ninterface StorageResponse {\n success: boolean\n data?: Record<string, string>\n}\n\ninterface StorageOperationResponse {\n success: boolean\n}\n\n/**\n * Key-value storage with local caching and debounced cloud sync\n * Data is isolated by userId and packageName\n */\nexport class SimpleStorage {\n private storage: Record<string, string> | null = null\n private appSession: AppSession\n private userId: string\n private packageName: string\n private baseUrl: string\n\n // Debounce batching state\n private pendingWrites = new Map<string, string>()\n private debounceTimer?: NodeJS.Timeout\n private maxWaitTimer?: NodeJS.Timeout\n private firstWriteTime?: number\n\n // Constants\n private static readonly MAX_VALUE_SIZE = 100_000 // 100KB\n private static readonly DEBOUNCE_MS = 3_000 // 3 seconds idle\n private static readonly MAX_WAIT_MS = 10_000 // 10 seconds max\n\n constructor(appSession: AppSession) {\n this.appSession = appSession\n this.userId = appSession.userId\n this.packageName = appSession.getPackageName()\n this.baseUrl = this.getBaseUrl()\n }\n\n // Convert WebSocket URL to HTTP for API calls\n private getBaseUrl(): string {\n const serverUrl = this.appSession.getServerUrl()\n if (!serverUrl) return \"http://localhost:8002\"\n return serverUrl.replace(/\\/app-ws$/, \"\").replace(/^ws/, \"http\")\n }\n\n // Generate auth headers for API requests\n private getAuthHeaders() {\n const apiKey = (this.appSession as any).config?.apiKey || \"unknown-api-key\"\n return {\n \"Authorization\": `Bearer ${this.packageName}:${apiKey}`,\n \"Content-Type\": \"application/json\",\n }\n }\n\n // Fetch all data from cloud and cache locally\n private async fetchStorageFromCloud(): Promise<void> {\n try {\n const response = await fetch(`${this.baseUrl}/api/sdk/simple-storage/${encodeURIComponent(this.userId)}`, {\n headers: this.getAuthHeaders(),\n })\n\n if (response.ok) {\n const result = (await response.json()) as StorageResponse\n if (result.success && result.data) {\n this.storage = result.data\n } else {\n this.storage = {}\n }\n } else {\n console.error(\"Failed to fetch storage from cloud:\", await response.text())\n this.storage = {}\n }\n } catch (error) {\n console.error(\"Error fetching storage from cloud:\", error)\n this.storage = {}\n }\n }\n\n // Get item from cache or cloud\n public async get(key: string): Promise<string | undefined> {\n try {\n if (this.storage !== null && this.storage !== undefined) {\n return this.storage[key]\n }\n\n await this.fetchStorageFromCloud()\n return this.storage?.[key]\n } catch (error) {\n console.error(\"Error getting item:\", error)\n return undefined\n }\n }\n\n // Set item with size validation and debounced batching\n // RAM updated immediately, MongoDB synced after 3s idle or 10s max\n public async set(key: string, value: string): Promise<void> {\n try {\n // Validate value size\n if (value.length > SimpleStorage.MAX_VALUE_SIZE) {\n throw new Error(\n `SimpleStorage value exceeds 100KB limit (${value.length} bytes). ` +\n `For large files, use your own S3 bucket storage.`,\n )\n }\n\n if (this.storage === null || this.storage === undefined) {\n await this.fetchStorageFromCloud()\n }\n\n // Optimistic update - RAM is source of truth (instant)\n if (this.storage) {\n this.storage[key] = value\n }\n\n // Add to pending batch for MongoDB persistence\n this.pendingWrites.set(key, value)\n\n // Schedule debounced flush\n this.scheduleFlush()\n } catch (error) {\n console.error(\"Error setting item:\", error)\n throw error\n }\n }\n\n /**\n * Schedule flush with debounce (3s idle) and max wait (10s)\n */\n private scheduleFlush(): void {\n // Track first write time for max wait\n if (!this.firstWriteTime) {\n this.firstWriteTime = Date.now()\n }\n\n // Clear existing debounce timer\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer)\n }\n\n // Calculate time until max wait\n const elapsedMs = Date.now() - this.firstWriteTime\n const remainingMaxWaitMs = SimpleStorage.MAX_WAIT_MS - elapsedMs\n\n // If we've hit max wait, flush immediately\n if (remainingMaxWaitMs <= 0) {\n this.flush().catch((err) => {\n console.error(\"Error flushing SimpleStorage:\", err)\n })\n return\n }\n\n // Set debounce timer (3s idle)\n this.debounceTimer = setTimeout(\n () => {\n this.flush().catch((err) => {\n console.error(\"Error flushing SimpleStorage:\", err)\n })\n },\n Math.min(SimpleStorage.DEBOUNCE_MS, remainingMaxWaitMs),\n )\n\n // Set max wait timer if not already set\n if (!this.maxWaitTimer && remainingMaxWaitMs > 0) {\n this.maxWaitTimer = setTimeout(() => {\n this.flush().catch((err) => {\n console.error(\"Error flushing SimpleStorage (max wait):\", err)\n })\n }, remainingMaxWaitMs)\n }\n }\n\n /**\n * Flush pending writes immediately\n * Called by: debounce timeout, max wait timeout, disconnect, or explicit flush()\n */\n public async flush(): Promise<void> {\n if (this.pendingWrites.size === 0) return\n\n // Clear all timers\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer)\n this.debounceTimer = undefined\n }\n if (this.maxWaitTimer) {\n clearTimeout(this.maxWaitTimer)\n this.maxWaitTimer = undefined\n }\n this.firstWriteTime = undefined\n\n const batch = Object.fromEntries(this.pendingWrites)\n this.pendingWrites.clear()\n\n try {\n // Persist to MongoDB (backup for crash recovery)\n const response = await fetch(`${this.baseUrl}/api/sdk/simple-storage/${encodeURIComponent(this.userId)}`, {\n method: \"PUT\",\n headers: this.getAuthHeaders(),\n body: JSON.stringify({data: batch}),\n })\n\n if (!response.ok) {\n const error = await response.text()\n console.error(\"Failed to persist SimpleStorage to MongoDB:\", error)\n\n // Re-throw with helpful message\n if (response.status === 413) {\n throw new Error(\"SimpleStorage total size exceeds 1MB limit. Delete unused keys.\")\n }\n if (response.status === 429) {\n throw new Error(\"SimpleStorage rate limit exceeded.\")\n }\n throw new Error(`SimpleStorage flush failed: ${error}`)\n }\n } catch (error) {\n console.error(\"Error flushing SimpleStorage:\", error)\n throw error\n }\n }\n\n // Delete item from cache and debounced sync to cloud\n public async delete(key: string): Promise<boolean> {\n try {\n if (this.storage === null || this.storage === undefined) {\n await this.fetchStorageFromCloud()\n }\n\n // Remove from cache (RAM = source of truth)\n if (this.storage) {\n delete this.storage[key]\n }\n\n // Remove from pending writes if exists\n this.pendingWrites.delete(key)\n\n // For deletes, we flush immediately to ensure consistency\n // (could batch this too, but deletes are rare)\n const response = await fetch(\n `${this.baseUrl}/api/sdk/simple-storage/${encodeURIComponent(this.userId)}/${encodeURIComponent(key)}`,\n {\n method: \"DELETE\",\n headers: this.getAuthHeaders(),\n },\n )\n\n if (response.ok) {\n const result = (await response.json()) as StorageOperationResponse\n return result.success\n } else {\n console.error(\"Failed to delete item from cloud:\", await response.text())\n return false\n }\n } catch (error) {\n console.error(\"Error deleting item:\", error)\n return false\n }\n }\n\n // Clear all data from cache and cloud\n public async clear(): Promise<boolean> {\n try {\n this.storage = {}\n\n const response = await fetch(`${this.baseUrl}/api/sdk/simple-storage/${encodeURIComponent(this.userId)}`, {\n method: \"DELETE\",\n headers: this.getAuthHeaders(),\n })\n\n if (response.ok) {\n const result = (await response.json()) as StorageOperationResponse\n return result.success\n } else {\n console.error(\"Failed to clear storage from cloud:\", await response.text())\n return false\n }\n } catch (error) {\n console.error(\"Error clearing storage:\", error)\n return false\n }\n }\n\n // Get all storage keys\n public async keys(): Promise<string[]> {\n try {\n if (this.storage === null || this.storage === undefined) {\n await this.fetchStorageFromCloud()\n }\n return Object.keys(this.storage || {})\n } catch (error) {\n console.error(\"Error getting keys:\", error)\n return []\n }\n }\n\n // Get number of stored items\n public async size(): Promise<number> {\n try {\n if (this.storage === null || this.storage === undefined) {\n await this.fetchStorageFromCloud()\n }\n return Object.keys(this.storage || {}).length\n } catch (error) {\n console.error(\"Error getting storage size:\", error)\n return 0\n }\n }\n\n // Check if key exists\n public async hasKey(key: string): Promise<boolean> {\n try {\n if (this.storage === null || this.storage === undefined) {\n await this.fetchStorageFromCloud()\n }\n return key in (this.storage || {})\n } catch (error) {\n console.error(\"Error checking key:\", error)\n return false\n }\n }\n\n // Get copy of all stored data\n public async getAllData(): Promise<Record<string, string>> {\n try {\n if (this.storage === null || this.storage === undefined) {\n await this.fetchStorageFromCloud()\n }\n return {...(this.storage || {})}\n } catch (error) {\n console.error(\"Error getting all data:\", error)\n return {}\n }\n }\n\n // Set multiple items at once with validation\n public async setMultiple(data: Record<string, string>): Promise<void> {\n try {\n // Validate all values first\n for (const [key, value] of Object.entries(data)) {\n if (value.length > SimpleStorage.MAX_VALUE_SIZE) {\n throw new Error(`SimpleStorage value for key \"${key}\" exceeds 100KB limit (${value.length} bytes)`)\n }\n }\n\n if (this.storage === null || this.storage === undefined) {\n await this.fetchStorageFromCloud()\n }\n\n // Update cache (RAM = source of truth)\n if (this.storage) {\n Object.assign(this.storage, data)\n }\n\n // Add all to pending batch\n for (const [key, value] of Object.entries(data)) {\n this.pendingWrites.set(key, value)\n }\n\n // Schedule debounced flush\n this.scheduleFlush()\n } catch (error) {\n console.error(\"Error setting multiple items:\", error)\n throw error\n }\n }\n}\n",
|
|
38
|
+
"// src/app/webview/index.ts\nimport axios from \"axios\";\nimport { Response, NextFunction } from \"express\";\nimport { AuthenticatedRequest } from \"../../types\";\n// Note: Your Express app needs to use cookie-parser middleware for this to work\n// Example: app.use(require('cookie-parser')());\nimport * as crypto from \"crypto\";\nimport { KEYUTIL, KJUR, RSAKey } from \"jsrsasign\";\nimport { AppSession } from \"../session\";\n\nconst userTokenPublicKey =\n process.env.MENTRAOS_CLOUD_USER_TOKEN_PUBLIC_KEY ||\n \"-----BEGIN PUBLIC KEY-----\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0Yt2RtNOdeKQxWMY0c84\\nADpY1Jy58YWZhaEgP2A5tBwFUKgy/TH9gQLWZjQ3dQ/6XXO8qq0kluoYFqM7ZDRF\\nzJ0E4Yi0WQncioLRcCx4q8pDmqY9vPKgv6PruJdFWca0l0s3gZ3BqSeWum/C23xK\\nFPHPwi8gvRdc6ALrkcHeciM+7NykU8c0EY8PSitNL+Tchti95kGu+j6APr5vNewi\\nzRpQGOdqaLWe+ahHmtj6KtUZjm8o6lan4f/o08C6litizguZXuw2Nn/Kd9fFI1xF\\nIVNJYMy9jgGaOi71+LpGw+vIpwAawp/7IvULDppvY3DdX5nt05P1+jvVJXPxMKzD\\nTQIDAQAB\\n-----END PUBLIC KEY-----\";\n\n/**\n * Extracts the temporary token from a URL string.\n * @param url The URL string, typically window.location.href.\n * @returns The token string or null if not found.\n */\nexport function extractTempToken(url: string): string | null {\n try {\n const parsedUrl = new URL(url);\n return parsedUrl.searchParams.get(\"aos_temp_token\");\n } catch (e) {\n console.error(\"Error parsing URL for temp token:\", e);\n return null;\n }\n}\n\n/**\n * Exchanges a temporary token for a user ID with the MentraOS Cloud.\n * This should be called from the App's backend server.\n * @param cloudApiUrl The base URL of the MentraOS Cloud API.\n * @param tempToken The temporary token obtained from the webview URL.\n * @param apiKey Your App's secret API key.\n * @returns A Promise that resolves with an object containing the userId.\n * @throws Throws an error if the exchange fails (e.g., invalid token, expired, network error).\n */\nexport async function exchangeToken(\n cloudApiUrl: string,\n tempToken: string,\n apiKey: string,\n packageName: string,\n): Promise<{ userId: string }> {\n const endpoint = `${cloudApiUrl}/api/auth/exchange-user-token`;\n console.log(`Exchanging token for user at ${endpoint}`);\n try {\n const response = await axios.post(\n endpoint,\n { aos_temp_token: tempToken, packageName: packageName },\n {\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${apiKey}`,\n },\n timeout: 10000, // 10 second timeout\n },\n );\n\n if (\n response.status === 200 &&\n response.data.success &&\n response.data.userId\n ) {\n return { userId: response.data.userId };\n } else {\n // Handle specific error messages from the server if available\n const errorMessage =\n response.data?.error || `Failed with status ${response.status}`;\n throw new Error(errorMessage);\n }\n } catch (error) {\n if (axios.isAxiosError(error)) {\n const status = error.response?.status;\n const data = error.response?.data;\n const message =\n data?.error || error.message || \"Unknown error during token exchange\";\n console.error(`Token exchange failed with status ${status}: ${message}`);\n throw new Error(`Token exchange failed: ${message}`);\n } else {\n console.error(\"Unexpected error during token exchange:\", error);\n throw new Error(\"An unexpected error occurred during token exchange.\");\n }\n }\n}\n\n/**\n * Signs a user ID to create a secure session token.\n * @param userId The user ID to sign\n * @param secret The secret key used for signing\n * @returns A signed session token string\n */\nfunction signSession(userId: string, secret: string): string {\n // Format: userId.timestamp.signature\n const timestamp = Date.now();\n const data = `${userId}|${timestamp}`;\n const signature = crypto\n .createHmac(\"sha256\", secret)\n .update(data)\n .digest(\"hex\");\n\n return `${data}|${signature}`;\n}\n\n/**\n * Verifies and extracts the user ID from a signed session token.\n * @param token The signed session token\n * @param secret The secret key used for verification\n * @param maxAge The maximum age of the token in milliseconds\n * @returns The extracted user ID if valid, or null if invalid\n */\nfunction verifySession(\n token: string,\n secret: string,\n maxAge?: number,\n): string | null {\n try {\n const parts = token.split(\"|\");\n if (parts.length !== 3) return null;\n\n const [userId, timestampStr, signature] = parts;\n const timestamp = parseInt(timestampStr, 10);\n\n // Check if token has expired\n if (maxAge && Date.now() - timestamp > maxAge) {\n console.log(\n `Session token expired: ${token}. Parsed date is ${timestamp}, meaning age is ${Date.now() - timestamp}, but maxAge is ${maxAge}`,\n );\n return null;\n }\n\n // Verify signature\n const data = `${userId}|${timestamp}`;\n const expectedSignature = crypto\n .createHmac(\"sha256\", secret)\n .update(data)\n .digest(\"hex\");\n\n if (signature !== expectedSignature) {\n console.log(\n `Session token signature mismatch: ${signature} !== ${expectedSignature}`,\n );\n return null;\n }\n\n return userId;\n } catch (error) {\n console.error(\"Session verification failed:\", error);\n return null;\n }\n}\n\n/**\n * Verifies a signed user token and extracts the user ID from it\n * @param signedUserToken The JWT token to verify\n * @returns The user ID (subject) from the token, or null if invalid\n */\nasync function verifySignedUserToken(\n signedUserToken: string,\n): Promise<string | null> {\n try {\n // 1. Parse the PEM public key into a jsrsasign key object\n const publicKeyObj = KEYUTIL.getKey(userTokenPublicKey) as RSAKey;\n // 2. Verify JWT signature + claims (issuer, exp, iat) with 2-min tolerance\n const isValid = KJUR.jws.JWS.verifyJWT(signedUserToken, publicKeyObj, {\n alg: [\"RS256\"],\n iss: [\"https://prod.augmentos.cloud\"],\n verifyAt: KJUR.jws.IntDate.get(\"now\"),\n gracePeriod: 120,\n });\n if (!isValid) return null;\n\n // 3. Decode payload and return the subject (user ID)\n const parsed = KJUR.jws.JWS.parse(signedUserToken);\n return (parsed.payloadObj as { sub: string }).sub || null;\n } catch (e) {\n console.error(\"[verifySignedUserToken] Error verifying token:\", e);\n return null;\n }\n}\n\n/**\n * Verifies a frontend token by comparing it to a secure hash of the API key\n * @param frontendToken The token to verify (should be a hash of the API key)\n * @param apiKey The API key to hash and compare against\n * @param userId Optional user ID that may be embedded in the token format\n * @returns The user ID if the token is valid, or null if invalid\n */\nfunction verifyFrontendToken(\n frontendToken: string,\n apiKey: string,\n): string | null {\n try {\n // Check if the token contains a user ID and hash separated by a colon\n const tokenParts = frontendToken.split(\":\");\n\n if (tokenParts.length === 2) {\n // Format: userId:hash\n const [tokenUserId, tokenHash] = tokenParts;\n\n // Align the hashing algorithm with server-side `hashWithApiKey`\n // 1. Hash the API key first (server only stores the hashed version)\n const hashedApiKey = crypto\n .createHash(\"sha256\")\n .update(apiKey)\n .digest(\"hex\");\n // 2. Create the expected hash using userId + hashedApiKey (same order & update calls)\n const expectedHash = crypto\n .createHash(\"sha256\")\n .update(tokenUserId)\n .update(hashedApiKey)\n .digest(\"hex\");\n\n if (tokenHash === expectedHash) {\n return tokenUserId;\n }\n } else {\n throw new Error(\"Invalid frontend token format\");\n }\n\n return null;\n } catch (error) {\n console.error(\"Frontend token verification failed:\", error);\n return null;\n }\n}\n\nfunction validateCloudApiUrlChecksum(\n checksum: string,\n cloudApiUrl: string,\n apiKey: string,\n): boolean {\n const hashedApiKey = crypto.createHash(\"sha256\").update(apiKey).digest(\"hex\");\n const expectedChecksum = crypto\n .createHash(\"sha256\")\n .update(cloudApiUrl)\n .update(hashedApiKey)\n .digest(\"hex\");\n\n return expectedChecksum === checksum;\n}\n/**\n * Express middleware for automatically handling the token exchange.\n * Assumes API key and Cloud URL are available (e.g., via environment variables).\n * Adds `req.authUserId` if successful.\n *\n * @param options Configuration options.\n * @param options.cloudApiUrl The base URL of the MentraOS Cloud API.\n * @param options.apiKey Your App's secret API key.\n * @param options.tokenQueryParam The name of the query parameter containing the token (default: 'aos_temp_token').\n * @param options.cookieName The name of the cookie to store the session token (default: 'aos_session').\n * @param options.cookieSecret Secret key used to sign the session cookie. MUST be provided and kept secure.\n * @param options.cookieOptions Options for the session cookie (default: { httpOnly: true, secure: process.env.NODE_ENV === 'production' }).\n */\nexport function createAuthMiddleware(options: {\n apiKey: string;\n packageName: string;\n cookieName?: string;\n cookieSecret: string;\n getAppSessionForUser?: (userId: string) => AppSession | null;\n cookieOptions?: {\n httpOnly?: boolean;\n secure?: boolean;\n maxAge?: number;\n sameSite?: boolean | \"lax\" | \"strict\" | \"none\";\n path?: string;\n };\n}) {\n const {\n apiKey,\n packageName,\n cookieName = \"aos_session\",\n cookieSecret,\n getAppSessionForUser,\n cookieOptions = {\n httpOnly: true,\n secure: process.env.NODE_ENV === \"production\",\n maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days by default\n sameSite: process.env.NODE_ENV === \"production\" ? \"none\" : \"lax\",\n path: \"/\",\n },\n } = options;\n\n if (!apiKey) {\n throw new Error(\"API Key are required for the auth middleware.\");\n }\n\n if (\n !cookieSecret ||\n typeof cookieSecret !== \"string\" ||\n cookieSecret.length < 8\n ) {\n throw new Error(\n \"A strong cookieSecret (at least 8 characters) is required for secure session management.\",\n );\n }\n\n return async (\n req: AuthenticatedRequest,\n res: Response,\n next: NextFunction,\n ) => {\n // First check for temporary token in the query string\n const tempToken = req.query[\"aos_temp_token\"] as string;\n const frontendToken =\n (req.headers.authorization?.replace(\"Bearer \", \"\") as string) ||\n (req.query[\"aos_frontend_token\"] as string);\n const signedUserToken = req.query[\"aos_signed_user_token\"] as string;\n\n // first check for signed user token\n if (signedUserToken) {\n const userId = await verifySignedUserToken(signedUserToken);\n if (userId) {\n // Set the user ID on the request\n req.authUserId = userId;\n if (getAppSessionForUser) {\n const appSession = getAppSessionForUser(userId);\n if (appSession) {\n req.activeSession = appSession;\n } else {\n req.activeSession = null;\n }\n }\n\n // Create a signed session token and store it in a cookie\n const signedSession = signSession(userId, cookieSecret);\n res.cookie(cookieName, signedSession, cookieOptions);\n\n console.log(\n \"[auth.middleware] User ID verified from signed user token: \",\n userId,\n );\n return next();\n } else {\n console.log(\"[auth.middleware] Signed user token invalid\");\n }\n }\n // If temporary token exists, authenticate with it\n if (tempToken) {\n try {\n let cloudApiUrl = `https://api.mentra.glass`;\n const cloudApiUrlFromQuery = req.query[\"cloudApiUrl\"] as string;\n if (cloudApiUrlFromQuery) {\n const cloudApiUrlChecksum = req.query[\n \"cloudApiUrlChecksum\"\n ] as string;\n\n if (\n validateCloudApiUrlChecksum(\n cloudApiUrlChecksum,\n cloudApiUrlFromQuery,\n apiKey,\n )\n ) {\n console.log(\n `Cloud API is being routed to alternate url at request of the server: ${cloudApiUrlFromQuery}`,\n );\n cloudApiUrl = cloudApiUrlFromQuery;\n } else {\n console.error(\n `Server requested alternate cloud url of ${cloudApiUrlFromQuery} but the checksum is invalid (checksum: ${cloudApiUrlChecksum}). Using default cloud url of ${cloudApiUrl} instead.`,\n );\n }\n }\n\n const { userId } = await exchangeToken(\n cloudApiUrl,\n tempToken,\n apiKey,\n packageName,\n );\n\n // Set the user ID on the request\n req.authUserId = userId;\n if (getAppSessionForUser) {\n const appSession = getAppSessionForUser(userId);\n if (appSession) {\n req.activeSession = appSession;\n }\n }\n\n // Create a signed session token and store it in a cookie\n const signedSession = signSession(userId, cookieSecret);\n res.cookie(cookieName, signedSession, cookieOptions);\n\n console.log(\n \"[auth.middleware] User ID verified from temporary token: \",\n userId,\n );\n\n return next();\n } catch (error) {\n console.error(\"Webview token exchange failed:\", error);\n // Temporary token is invalid\n }\n }\n\n if (frontendToken) {\n // Check for user ID in headers if not embedded in token\n const userId = verifyFrontendToken(frontendToken, apiKey);\n\n if (userId) {\n req.authUserId = userId;\n if (getAppSessionForUser) {\n const appSession = getAppSessionForUser(userId);\n if (appSession) {\n req.activeSession = appSession;\n }\n }\n // Create a signed session token and store it in a cookie\n const signedSession = signSession(userId, cookieSecret);\n res.cookie(cookieName, signedSession, cookieOptions);\n console.log(\n \"[auth.middleware] User ID verified from frontend user token: \",\n userId,\n );\n return next();\n } else {\n console.log(\"[auth.middleware] Frontend token invalid\");\n }\n }\n\n // No valid temporary token, check for existing session cookie\n const sessionCookie = req.cookies?.[cookieName];\n\n if (sessionCookie) {\n try {\n // Verify the signed session cookie and extract the user ID\n const userId = verifySession(\n sessionCookie,\n cookieSecret,\n cookieOptions.maxAge,\n );\n if (userId) {\n req.authUserId = userId;\n if (getAppSessionForUser) {\n const appSession = getAppSessionForUser(userId);\n if (appSession) {\n req.activeSession = appSession;\n }\n }\n return next();\n }\n\n // Invalid or expired session, clear the cookie\n res.clearCookie(cookieName, { path: cookieOptions.path });\n } catch (error) {\n console.error(\"Invalid session cookie:\", error);\n // Clear the invalid cookie\n res.clearCookie(cookieName, { path: cookieOptions.path });\n }\n }\n\n // No valid authentication method found, proceed without setting req.authUserId\n next();\n };\n}\n",
|
|
39
|
+
"/**\n * updates.ts\n *\n * This file defines constant messages that should be displayed\n * in the terminal to notify developers about new SDK releases.\n *\n * Each function generates a stylized ASCII message (banner-style)\n * that highlights the latest SDK version and provides the npm install command.\n * https://patorjk.com/software/taag/\n *\n * These messages are intended to be logged to the console or shown in\n * terminal output so developers are aware of updates in a clear\n * and visually distinct way.\n *\n *\n */\n\nimport chalk from \"chalk\";\nimport boxen from \"boxen\";\nimport { mentraLogo_1, newUpdateText } from \"./logos\";\n\nconst createUpdateNotification = (versionNumb: string): string => {\n const line = chalk.bold.gray(\"-------------------------------\");\n const logo = chalk.cyan(mentraLogo_1);\n const title = chalk.bold.cyan(newUpdateText);\n const versionMessage = `Version ${chalk.bold.cyan(`SDK VERSION V${versionNumb} is out! 🎉`)}`;\n const currentNote = chalk.yellow(\"You are running an older version\");\n const instructions = chalk.yellow(\"Update to the latest version with:\");\n const command = chalk.green.bold(\"bun install @mentra/sdk@latest\");\n\n const content = [\n logo,\n title,\n line,\n versionMessage,\n currentNote,\n line,\n instructions,\n command,\n ].join(\"\\n\");\n\n return boxen(content, {\n padding: 1,\n margin: 1,\n borderStyle: \"round\",\n borderColor: \"cyan\",\n textAlignment: \"left\",\n });\n};\n\nexport const newSDKUpdate = (versionNumb: string): string => {\n return createUpdateNotification(versionNumb);\n};\n",
|
|
40
|
+
"/**\n * 🔐 App Token Module\n *\n * Provides utilities for working with App tokens.\n */\nexport * from './utils';"
|
|
41
|
+
],
|
|
42
|
+
"mappings": "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",
|
|
43
|
+
"debugId": "AD30D8A3207C691A64756E2164756E21",
|
|
44
|
+
"names": []
|
|
45
|
+
}
|