@mentra/sdk 2.1.13 → 2.1.14

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.
@@ -26,11 +26,11 @@ const node_fetch_1 = __importDefault(require("node-fetch"));
26
26
  const cloud_to_app_1 = require("../../types/messages/cloud-to-app");
27
27
  // List of event types that should never be subscribed to as streams
28
28
  const APP_TO_APP_EVENT_TYPES = [
29
- 'app_message_received',
30
- 'app_user_joined',
31
- 'app_user_left',
32
- 'app_room_updated',
33
- 'app_direct_message_response'
29
+ "app_message_received",
30
+ "app_user_joined",
31
+ "app_user_left",
32
+ "app_room_updated",
33
+ "app_direct_message_response",
34
34
  ];
35
35
  /**
36
36
  * 🚀 App Session Implementation
@@ -95,18 +95,21 @@ class AppSession {
95
95
  autoReconnect: true, // Enable auto-reconnection by default for better reliability
96
96
  maxReconnectAttempts: 3, // Default to 3 reconnection attempts for better resilience
97
97
  reconnectDelay: 1000, // Start with 1 second delay (uses exponential backoff)
98
- ...config
98
+ ...config,
99
99
  };
100
100
  this.appServer = this.config.appServer;
101
- this.logger = this.appServer.logger.child({ userId: this.config.userId, service: 'app-session' });
101
+ this.logger = this.appServer.logger.child({
102
+ userId: this.config.userId,
103
+ service: "app-session",
104
+ });
102
105
  this.userId = this.config.userId;
103
106
  // Make sure the URL is correctly formatted to prevent double protocol issues
104
107
  if (this.config.mentraOSWebsocketUrl) {
105
108
  try {
106
109
  const url = new URL(this.config.mentraOSWebsocketUrl);
107
- if (!['ws:', 'wss:'].includes(url.protocol)) {
110
+ if (!["ws:", "wss:"].includes(url.protocol)) {
108
111
  // Fix URLs with incorrect protocol (e.g., 'ws://http://host')
109
- const fixedUrl = this.config.mentraOSWebsocketUrl.replace(/^ws:\/\/http:\/\//, 'ws://');
112
+ const fixedUrl = this.config.mentraOSWebsocketUrl.replace(/^ws:\/\/http:\/\//, "ws://");
110
113
  this.config.mentraOSWebsocketUrl = fixedUrl;
111
114
  this.logger.warn(`⚠️ [${this.config.packageName}] Fixed malformed WebSocket URL: ${fixedUrl}`);
112
115
  }
@@ -123,7 +126,7 @@ class AppSession {
123
126
  if (this.config.mentraOSWebsocketUrl) {
124
127
  try {
125
128
  const url = new URL(this.config.mentraOSWebsocketUrl);
126
- if (!['ws:', 'wss:'].includes(url.protocol)) {
129
+ if (!["ws:", "wss:"].includes(url.protocol)) {
127
130
  this.logger.error({ config: this.config }, `⚠️ [${this.config.packageName}] Invalid WebSocket URL protocol: ${url.protocol}. Should be ws: or wss:`);
128
131
  }
129
132
  }
@@ -156,14 +159,14 @@ class AppSession {
156
159
  });
157
160
  // Initialize dashboard API with this session instance
158
161
  // Import DashboardManager dynamically to avoid circular dependency
159
- const { DashboardManager } = require('./dashboard');
162
+ const { DashboardManager } = require("./dashboard");
160
163
  this.dashboard = new DashboardManager(this, this.send.bind(this));
161
164
  // Initialize camera module with session reference
162
- this.camera = new camera_1.CameraModule(this.config.packageName, this.sessionId || 'unknown-session-id', this.send.bind(this), this, // Pass session reference
163
- this.logger.child({ module: 'camera' }));
165
+ this.camera = new camera_1.CameraModule(this.config.packageName, this.sessionId || "unknown-session-id", this.send.bind(this), this, // Pass session reference
166
+ this.logger.child({ module: "camera" }));
164
167
  // Initialize audio module with session reference
165
- this.audio = new audio_1.AudioManager(this.config.packageName, this.sessionId || 'unknown-session-id', this.send.bind(this), this, // Pass session reference
166
- this.logger.child({ module: 'audio' }));
168
+ this.audio = new audio_1.AudioManager(this.config.packageName, this.sessionId || "unknown-session-id", this.send.bind(this), this, // Pass session reference
169
+ this.logger.child({ module: "audio" }));
167
170
  this.location = new location_1.LocationManager(this, this.send.bind(this));
168
171
  }
169
172
  /**
@@ -171,7 +174,7 @@ class AppSession {
171
174
  * @returns The current session ID or 'unknown-session-id' if not connected
172
175
  */
173
176
  getSessionId() {
174
- return this.sessionId || 'unknown-session-id';
177
+ return this.sessionId || "unknown-session-id";
175
178
  }
176
179
  /**
177
180
  * Get the package name for this App
@@ -197,8 +200,8 @@ class AppSession {
197
200
  * @throws Error if language code is invalid
198
201
  * @deprecated Use session.events.onTranscriptionForLanguage() instead
199
202
  */
200
- onTranscriptionForLanguage(language, handler) {
201
- return this.events.onTranscriptionForLanguage(language, handler);
203
+ onTranscriptionForLanguage(language, handler, disableLanguageIdentification = false) {
204
+ return this.events.onTranscriptionForLanguage(language, handler, disableLanguageIdentification);
202
205
  }
203
206
  /**
204
207
  * 🌐 Listen for speech translation events for a specific language pair
@@ -278,7 +281,7 @@ class AppSession {
278
281
  subscribe(sub) {
279
282
  let type;
280
283
  let rate;
281
- if (typeof sub === 'string') {
284
+ if (typeof sub === "string") {
282
285
  type = sub;
283
286
  }
284
287
  else {
@@ -304,7 +307,7 @@ class AppSession {
304
307
  */
305
308
  unsubscribe(sub) {
306
309
  let type;
307
- if (typeof sub === 'string') {
310
+ if (typeof sub === "string") {
308
311
  type = sub;
309
312
  }
310
313
  else {
@@ -340,7 +343,7 @@ class AppSession {
340
343
  this.sessionId = sessionId;
341
344
  // Configure settings API client with the WebSocket URL and session ID
342
345
  // This allows settings to be fetched from the correct server
343
- this.settings.configureApiClient(this.config.packageName, this.config.mentraOSWebsocketUrl || '', sessionId);
346
+ this.settings.configureApiClient(this.config.packageName, this.config.mentraOSWebsocketUrl || "", sessionId);
344
347
  // Update the sessionId in the camera module
345
348
  if (this.camera) {
346
349
  this.camera.updateSessionId(sessionId);
@@ -354,15 +357,16 @@ class AppSession {
354
357
  // Clear previous resources if reconnecting
355
358
  if (this.ws) {
356
359
  // Don't call full dispose() as that would clear subscriptions
357
- if (this.ws.readyState !== 3) { // 3 = CLOSED
360
+ if (this.ws.readyState !== 3) {
361
+ // 3 = CLOSED
358
362
  this.ws.close();
359
363
  }
360
364
  this.ws = null;
361
365
  }
362
366
  // Validate WebSocket URL before attempting connection
363
367
  if (!this.config.mentraOSWebsocketUrl) {
364
- this.logger.error('WebSocket URL is missing or undefined');
365
- reject(new Error('WebSocket URL is required'));
368
+ this.logger.error("WebSocket URL is missing or undefined");
369
+ reject(new Error("WebSocket URL is required"));
366
370
  return;
367
371
  }
368
372
  // Add debug logging for connection attempts
@@ -371,18 +375,19 @@ class AppSession {
371
375
  this.ws = new ws_1.WebSocket(this.config.mentraOSWebsocketUrl);
372
376
  // Track WebSocket for automatic cleanup
373
377
  this.resources.track(() => {
374
- if (this.ws && this.ws.readyState !== 3) { // 3 = CLOSED
378
+ if (this.ws && this.ws.readyState !== 3) {
379
+ // 3 = CLOSED
375
380
  this.ws.close();
376
381
  }
377
382
  });
378
- this.ws.on('open', () => {
383
+ this.ws.on("open", () => {
379
384
  try {
380
385
  this.sendConnectionInit();
381
386
  }
382
387
  catch (error) {
383
- this.logger.error(error, 'Error during connection initialization');
388
+ this.logger.error(error, "Error during connection initialization");
384
389
  const errorMessage = error instanceof Error ? error.message : String(error);
385
- this.events.emit('error', new Error(`Connection initialization failed: ${errorMessage}`));
390
+ this.events.emit("error", new Error(`Connection initialization failed: ${errorMessage}`));
386
391
  reject(error);
387
392
  }
388
393
  });
@@ -394,7 +399,7 @@ class AppSession {
394
399
  try {
395
400
  // Validate buffer before processing
396
401
  if (data.length === 0) {
397
- this.events.emit('error', new Error('Received empty binary data'));
402
+ this.events.emit("error", new Error("Received empty binary data"));
398
403
  return;
399
404
  }
400
405
  // Convert Node.js Buffer to ArrayBuffer safely
@@ -403,15 +408,15 @@ class AppSession {
403
408
  const audioChunk = {
404
409
  type: types_1.StreamType.AUDIO_CHUNK,
405
410
  arrayBuffer: arrayBuf,
406
- timestamp: new Date() // Ensure timestamp is present
411
+ timestamp: new Date(), // Ensure timestamp is present
407
412
  };
408
413
  this.handleMessage(audioChunk);
409
414
  return;
410
415
  }
411
416
  catch (error) {
412
- this.logger.error(error, 'Error processing binary message:');
417
+ this.logger.error(error, "Error processing binary message:");
413
418
  const errorMessage = error instanceof Error ? error.message : String(error);
414
- this.events.emit('error', new Error(`Failed to process binary message: ${errorMessage}`));
419
+ this.events.emit("error", new Error(`Failed to process binary message: ${errorMessage}`));
415
420
  return;
416
421
  }
417
422
  }
@@ -423,59 +428,61 @@ class AppSession {
423
428
  try {
424
429
  // Convert string data to JSON safely
425
430
  let jsonData;
426
- if (typeof data === 'string') {
431
+ if (typeof data === "string") {
427
432
  jsonData = data;
428
433
  }
429
434
  else if (Buffer.isBuffer(data)) {
430
- jsonData = data.toString('utf8');
435
+ jsonData = data.toString("utf8");
431
436
  }
432
437
  else {
433
- throw new Error('Unknown message format');
438
+ throw new Error("Unknown message format");
434
439
  }
435
440
  // Validate JSON before parsing
436
- if (!jsonData || jsonData.trim() === '') {
437
- this.events.emit('error', new Error('Received empty JSON message'));
441
+ if (!jsonData || jsonData.trim() === "") {
442
+ this.events.emit("error", new Error("Received empty JSON message"));
438
443
  return;
439
444
  }
440
445
  // Parse JSON with error handling
441
446
  const message = JSON.parse(jsonData);
442
447
  // Basic schema validation
443
- if (!message || typeof message !== 'object' || !('type' in message)) {
444
- this.events.emit('error', new Error('Malformed message: missing type property'));
448
+ if (!message ||
449
+ typeof message !== "object" ||
450
+ !("type" in message)) {
451
+ this.events.emit("error", new Error("Malformed message: missing type property"));
445
452
  return;
446
453
  }
447
454
  // Process the validated message
448
455
  this.handleMessage(message);
449
456
  }
450
457
  catch (error) {
451
- this.logger.error(error, 'JSON parsing error');
458
+ this.logger.error(error, "JSON parsing error");
452
459
  const errorMessage = error instanceof Error ? error.message : String(error);
453
- this.events.emit('error', new Error(`Failed to parse JSON message: ${errorMessage}`));
460
+ this.events.emit("error", new Error(`Failed to parse JSON message: ${errorMessage}`));
454
461
  }
455
462
  }
456
463
  catch (error) {
457
464
  // Final catch - should never reach here if individual handlers work correctly
458
- this.logger.error({ error }, 'Unhandled message processing error');
465
+ this.logger.error({ error }, "Unhandled message processing error");
459
466
  const errorMessage = error instanceof Error ? error.message : String(error);
460
- this.events.emit('error', new Error(`Unhandled message error: ${errorMessage}`));
467
+ this.events.emit("error", new Error(`Unhandled message error: ${errorMessage}`));
461
468
  }
462
469
  };
463
- this.ws.on('message', messageHandler);
470
+ this.ws.on("message", messageHandler);
464
471
  // Track event handler removal for automatic cleanup
465
472
  this.resources.track(() => {
466
473
  if (this.ws) {
467
- this.ws.off('message', messageHandler);
474
+ this.ws.off("message", messageHandler);
468
475
  }
469
476
  });
470
477
  // Connection closure handler
471
478
  const closeHandler = (code, reason) => {
472
- const reasonStr = reason ? `: ${reason}` : '';
479
+ const reasonStr = reason ? `: ${reason}` : "";
473
480
  const closeInfo = `Connection closed (code: ${code})${reasonStr}`;
474
481
  // Emit the disconnected event with structured data for better handling
475
- this.events.emit('disconnected', {
482
+ this.events.emit("disconnected", {
476
483
  message: closeInfo,
477
484
  code: code,
478
- reason: reason || '',
485
+ reason: reason || "",
479
486
  wasClean: code === 1000 || code === 1001,
480
487
  });
481
488
  // Only attempt reconnection for abnormal closures
@@ -483,8 +490,8 @@ class AppSession {
483
490
  // 1000 (Normal Closure) and 1001 (Going Away) are normal
484
491
  // 1002-1015 are abnormal, and reason "App stopped" means intentional closure
485
492
  // 1008 usually when the userSession no longer exists on server. i.e user disconnected from cloud.
486
- const isNormalClosure = (code === 1000 || code === 1001 || code === 1008);
487
- const isManualStop = reason && reason.includes('App stopped');
493
+ const isNormalClosure = code === 1000 || code === 1001 || code === 1008;
494
+ const isManualStop = reason && reason.includes("App stopped");
488
495
  // Log closure details for diagnostics
489
496
  this.logger.debug(`🔌 [${this.config.packageName}] WebSocket closed with code ${code}${reasonStr}`);
490
497
  this.logger.debug(`🔌 [${this.config.packageName}] isNormalClosure: ${isNormalClosure}, isManualStop: ${isManualStop}`);
@@ -496,27 +503,27 @@ class AppSession {
496
503
  this.logger.debug(`🔌 [${this.config.packageName}] Normal closure detected, not attempting reconnection`);
497
504
  }
498
505
  };
499
- this.ws.on('close', closeHandler);
506
+ this.ws.on("close", closeHandler);
500
507
  // Track event handler removal
501
508
  this.resources.track(() => {
502
509
  if (this.ws) {
503
- this.ws.off('close', closeHandler);
510
+ this.ws.off("close", closeHandler);
504
511
  }
505
512
  });
506
513
  // Connection error handler
507
514
  const errorHandler = (error) => {
508
- this.logger.error(error, 'WebSocket error');
509
- this.events.emit('error', error);
515
+ this.logger.error(error, "WebSocket error");
516
+ this.events.emit("error", error);
510
517
  };
511
518
  // Enhanced error handler with detailed logging
512
- this.ws.on('error', (error) => {
519
+ this.ws.on("error", (error) => {
513
520
  this.logger.error(error, `⛔️⛔️⛔️ [${this.config.packageName}] WebSocket connection error: ${error.message}`);
514
521
  // Try to provide more context
515
- const errMsg = error.message || '';
516
- if (errMsg.includes('ECONNREFUSED')) {
522
+ const errMsg = error.message || "";
523
+ if (errMsg.includes("ECONNREFUSED")) {
517
524
  this.logger.error(`⛔️⛔️⛔️ [${this.config.packageName}] Connection refused - Check if the server is running at the specified URL`);
518
525
  }
519
- else if (errMsg.includes('ETIMEDOUT')) {
526
+ else if (errMsg.includes("ETIMEDOUT")) {
520
527
  this.logger.error(`⛔️⛔️⛔️ [${this.config.packageName}] Connection timed out - Check network connectivity and firewall rules`);
521
528
  }
522
529
  errorHandler(error);
@@ -524,7 +531,7 @@ class AppSession {
524
531
  // Track event handler removal
525
532
  this.resources.track(() => {
526
533
  if (this.ws) {
527
- this.ws.off('error', errorHandler);
534
+ this.ws.off("error", errorHandler);
528
535
  }
529
536
  });
530
537
  // Set up connection success handler
@@ -538,10 +545,10 @@ class AppSession {
538
545
  this.logger.error({
539
546
  config: this.config,
540
547
  sessionId: this.sessionId,
541
- timeoutMs
548
+ timeoutMs,
542
549
  }, `⏱️⏱️⏱️ [${this.config.packageName}] Connection timeout after ${timeoutMs}ms`);
543
- this.events.emit('error', new Error(`Connection timeout after ${timeoutMs}ms`));
544
- reject(new Error('Connection timeout'));
550
+ this.events.emit("error", new Error(`Connection timeout after ${timeoutMs}ms`));
551
+ reject(new Error("Connection timeout"));
545
552
  }, timeoutMs);
546
553
  // Clear timeout on successful connection
547
554
  const timeoutCleanup = this.events.onConnected(() => {
@@ -552,7 +559,7 @@ class AppSession {
552
559
  this.resources.track(timeoutCleanup);
553
560
  }
554
561
  catch (error) {
555
- this.logger.error(error, 'Connection setup error');
562
+ this.logger.error(error, "Connection setup error");
556
563
  const errorMessage = error instanceof Error ? error.message : String(error);
557
564
  reject(new Error(`Failed to setup connection: ${errorMessage}`));
558
565
  }
@@ -621,7 +628,7 @@ class AppSession {
621
628
  const newSubscriptions = this.subscriptionSettingsHandler(this.settingsData);
622
629
  // Update all subscriptions at once
623
630
  this.subscriptions.clear();
624
- newSubscriptions.forEach(subscription => {
631
+ newSubscriptions.forEach((subscription) => {
625
632
  this.subscriptions.add(subscription);
626
633
  });
627
634
  // Send subscription update to cloud if connected
@@ -630,9 +637,9 @@ class AppSession {
630
637
  }
631
638
  }
632
639
  catch (error) {
633
- this.logger.error(error, 'Error updating subscriptions from settings');
640
+ this.logger.error(error, "Error updating subscriptions from settings");
634
641
  const errorMessage = error instanceof Error ? error.message : String(error);
635
- this.events.emit('error', new Error(`Failed to update subscriptions: ${errorMessage}`));
642
+ this.events.emit("error", new Error(`Failed to update subscriptions: ${errorMessage}`));
636
643
  }
637
644
  }
638
645
  /**
@@ -645,7 +652,7 @@ class AppSession {
645
652
  // Update the settings manager with the new settings
646
653
  this.settings.updateSettings(newSettings);
647
654
  // Emit update event for backwards compatibility
648
- this.events.emit('settings_update', this.settingsData);
655
+ this.events.emit("settings_update", this.settingsData);
649
656
  // Check if we should update subscriptions
650
657
  if (this.shouldUpdateSubscriptionsOnSettingsChange) {
651
658
  this.updateSubscriptionsFromSettings();
@@ -665,7 +672,7 @@ class AppSession {
665
672
  return parsedConfig;
666
673
  }
667
674
  else {
668
- throw new Error('Invalid App configuration format');
675
+ throw new Error("Invalid App configuration format");
669
676
  }
670
677
  }
671
678
  catch (error) {
@@ -695,11 +702,11 @@ class AppSession {
695
702
  }
696
703
  static convertToHttps(rawUrl) {
697
704
  if (!rawUrl)
698
- return '';
705
+ return "";
699
706
  // Remove ws:// or wss://
700
- let url = rawUrl.replace(/^wss?:\/\//, '');
707
+ let url = rawUrl.replace(/^wss?:\/\//, "");
701
708
  // Remove trailing /app-ws
702
- url = url.replace(/\/app-ws$/, '');
709
+ url = url.replace(/\/app-ws$/, "");
703
710
  // Prepend https://
704
711
  return `https://${url}`;
705
712
  }
@@ -710,13 +717,13 @@ class AppSession {
710
717
  */
711
718
  getDefaultSettings() {
712
719
  if (!this.appConfig) {
713
- throw new Error('App configuration not loaded. Call loadConfigFromJson first.');
720
+ throw new Error("App configuration not loaded. Call loadConfigFromJson first.");
714
721
  }
715
722
  return this.appConfig.settings
716
- .filter((s) => s.type !== 'group')
723
+ .filter((s) => s.type !== "group")
717
724
  .map((s) => ({
718
725
  ...s,
719
- value: s.defaultValue // Set value to defaultValue
726
+ value: s.defaultValue, // Set value to defaultValue
720
727
  }));
721
728
  }
722
729
  /**
@@ -727,7 +734,7 @@ class AppSession {
727
734
  getSettingSchema(key) {
728
735
  if (!this.appConfig)
729
736
  return undefined;
730
- const setting = this.appConfig.settings.find((s) => s.type !== 'group' && 'key' in s && s.key === key);
737
+ const setting = this.appConfig.settings.find((s) => s.type !== "group" && "key" in s && s.key === key);
731
738
  return setting;
732
739
  }
733
740
  // =====================================
@@ -740,7 +747,7 @@ class AppSession {
740
747
  try {
741
748
  // Validate message before processing
742
749
  if (!this.validateMessage(message)) {
743
- this.events.emit('error', new Error('Invalid message format received'));
750
+ this.events.emit("error", new Error("Invalid message format received"));
744
751
  return;
745
752
  }
746
753
  // Handle binary data (audio or video)
@@ -764,7 +771,7 @@ class AppSession {
764
771
  this.settingsData = this.getDefaultSettings();
765
772
  }
766
773
  catch (error) {
767
- this.logger.warn('Failed to load default settings from config:', error);
774
+ this.logger.warn("Failed to load default settings from config:", error);
768
775
  }
769
776
  }
770
777
  // Update the settings manager with the new settings
@@ -787,18 +794,20 @@ class AppSession {
787
794
  this.logger.debug(`[AppSession] No capabilities provided in CONNECTION_ACK`);
788
795
  }
789
796
  // Emit connected event with settings
790
- this.events.emit('connected', this.settingsData);
797
+ this.events.emit("connected", this.settingsData);
791
798
  // Update subscriptions (normal flow)
792
799
  this.updateSubscriptions();
793
800
  // If settings-based subscriptions are enabled, update those too
794
- if (this.shouldUpdateSubscriptionsOnSettingsChange && this.settingsData.length > 0) {
801
+ if (this.shouldUpdateSubscriptionsOnSettingsChange &&
802
+ this.settingsData.length > 0) {
795
803
  this.updateSubscriptionsFromSettings();
796
804
  }
797
805
  }
798
- else if ((0, types_1.isAppConnectionError)(message) || message.type === 'connection_error') {
806
+ else if ((0, types_1.isAppConnectionError)(message) ||
807
+ message.type === "connection_error") {
799
808
  // Handle both App-specific connection_error and standard connection_error
800
- const errorMessage = message.message || 'Unknown connection error';
801
- this.events.emit('error', new Error(errorMessage));
809
+ const errorMessage = message.message || "Unknown connection error";
810
+ this.events.emit("error", new Error(errorMessage));
802
811
  }
803
812
  else if (message.type === types_1.StreamType.AUDIO_CHUNK) {
804
813
  if (this.subscriptions.has(types_1.StreamType.AUDIO_CHUNK)) {
@@ -808,19 +817,18 @@ class AppSession {
808
817
  }
809
818
  else if ((0, types_1.isDataStream)(message)) {
810
819
  // Ensure streamType exists before emitting the event
811
- let messageStreamType = message.streamType;
812
- if (message.streamType === types_1.StreamType.TRANSCRIPTION) {
813
- const transcriptionData = message.data;
814
- if (transcriptionData.transcribeLanguage) {
815
- messageStreamType = (0, types_1.createTranscriptionStream)(transcriptionData.transcribeLanguage);
816
- }
817
- }
818
- else if (message.streamType === types_1.StreamType.TRANSLATION) {
819
- const translationData = message.data;
820
- if (translationData.transcribeLanguage && translationData.translateLanguage) {
821
- messageStreamType = (0, types_1.createTranslationStream)(translationData.transcribeLanguage, translationData.translateLanguage);
822
- }
823
- }
820
+ const messageStreamType = message.streamType;
821
+ // if (message.streamType === StreamType.TRANSCRIPTION) {
822
+ // const transcriptionData = message.data as TranscriptionData;
823
+ // if (transcriptionData.transcribeLanguage) {
824
+ // messageStreamType = createTranscriptionStream(transcriptionData.transcribeLanguage) as ExtendedStreamType;
825
+ // }
826
+ // } else if (message.streamType === StreamType.TRANSLATION) {
827
+ // const translationData = message.data as TranslationData;
828
+ // if (translationData.transcribeLanguage && translationData.translateLanguage) {
829
+ // messageStreamType = createTranslationStream(translationData.transcribeLanguage, translationData.translateLanguage) as ExtendedStreamType;
830
+ // }
831
+ // }
824
832
  if (messageStreamType && this.subscriptions.has(messageStreamType)) {
825
833
  const sanitizedData = this.sanitizeEventData(messageStreamType, message.data);
826
834
  this.events.emit(messageStreamType, sanitizedData);
@@ -850,16 +858,16 @@ class AppSession {
850
858
  // Update the settings manager with the new settings
851
859
  const changes = this.settings.updateSettings(this.settingsData);
852
860
  // Emit settings update event (for backwards compatibility)
853
- this.events.emit('settings_update', this.settingsData);
861
+ this.events.emit("settings_update", this.settingsData);
854
862
  // --- MentraOS settings update logic ---
855
863
  // If the message.settings looks like MentraOS settings (object with known keys), update mentraosSettings
856
- if (message.settings && typeof message.settings === 'object') {
864
+ if (message.settings && typeof message.settings === "object") {
857
865
  this.settings.updateMentraosSettings(message.settings);
858
866
  }
859
867
  // Check if we should update subscriptions
860
868
  if (this.shouldUpdateSubscriptionsOnSettingsChange) {
861
869
  // Check if any subscription trigger settings changed
862
- const shouldUpdateSubs = this.subscriptionUpdateTriggers.some(key => {
870
+ const shouldUpdateSubs = this.subscriptionUpdateTriggers.some((key) => {
863
871
  return key in changes;
864
872
  });
865
873
  if (shouldUpdateSubs) {
@@ -867,11 +875,23 @@ class AppSession {
867
875
  }
868
876
  }
869
877
  }
878
+ else if ((0, types_1.isCapabilitiesUpdate)(message)) {
879
+ // Update device capabilities
880
+ const capabilitiesMessage = message;
881
+ this.capabilities = capabilitiesMessage.capabilities;
882
+ this.logger.info(capabilitiesMessage.capabilities, `[AppSession] Capabilities updated for model: ${capabilitiesMessage.modelName}`);
883
+ // Emit capabilities update event for applications to handle
884
+ this.events.emit("capabilities_update", {
885
+ capabilities: capabilitiesMessage.capabilities,
886
+ modelName: capabilitiesMessage.modelName,
887
+ timestamp: capabilitiesMessage.timestamp,
888
+ });
889
+ }
870
890
  else if ((0, types_1.isAppStopped)(message)) {
871
- const reason = message.reason || 'unknown';
891
+ const reason = message.reason || "unknown";
872
892
  const displayReason = `App stopped: ${reason}`;
873
893
  // Emit disconnected event with clean closure info to prevent reconnection attempts
874
- this.events.emit('disconnected', {
894
+ this.events.emit("disconnected", {
875
895
  message: displayReason,
876
896
  code: 1000, // Normal closure code
877
897
  reason: displayReason,
@@ -884,14 +904,14 @@ class AppSession {
884
904
  else if ((0, types_1.isDashboardModeChanged)(message)) {
885
905
  try {
886
906
  // Use proper type
887
- const mode = message.mode || 'none';
907
+ const mode = message.mode || "none";
888
908
  // Update dashboard state in the API
889
- if (this.dashboard && 'content' in this.dashboard) {
909
+ if (this.dashboard && "content" in this.dashboard) {
890
910
  this.dashboard.content.setCurrentMode(mode);
891
911
  }
892
912
  }
893
913
  catch (error) {
894
- this.logger.error(error, 'Error handling dashboard mode change');
914
+ this.logger.error(error, "Error handling dashboard mode change");
895
915
  }
896
916
  }
897
917
  // Handle always-on dashboard state changes
@@ -900,75 +920,78 @@ class AppSession {
900
920
  // Use proper type
901
921
  const enabled = !!message.enabled;
902
922
  // Update dashboard state in the API
903
- if (this.dashboard && 'content' in this.dashboard) {
923
+ if (this.dashboard && "content" in this.dashboard) {
904
924
  this.dashboard.content.setAlwaysOnEnabled(enabled);
905
925
  }
906
926
  }
907
927
  catch (error) {
908
- this.logger.error(error, 'Error handling dashboard always-on change');
928
+ this.logger.error(error, "Error handling dashboard always-on change");
909
929
  }
910
930
  }
911
931
  // Handle custom messages
912
932
  else if (message.type === types_1.CloudToAppMessageType.CUSTOM_MESSAGE) {
913
- this.events.emit('custom_message', message);
933
+ this.events.emit("custom_message", message);
914
934
  return;
915
935
  }
916
936
  // Handle App-to-App communication messages
917
- else if (message.type === 'app_message_received') {
918
- this.appEvents.emit('app_message_received', message);
937
+ else if (message.type === "app_message_received") {
938
+ this.appEvents.emit("app_message_received", message);
919
939
  }
920
- else if (message.type === 'app_user_joined') {
921
- this.appEvents.emit('app_user_joined', message);
940
+ else if (message.type === "app_user_joined") {
941
+ this.appEvents.emit("app_user_joined", message);
922
942
  }
923
- else if (message.type === 'app_user_left') {
924
- this.appEvents.emit('app_user_left', message);
943
+ else if (message.type === "app_user_left") {
944
+ this.appEvents.emit("app_user_left", message);
925
945
  }
926
- else if (message.type === 'app_room_updated') {
927
- this.appEvents.emit('app_room_updated', message);
946
+ else if (message.type === "app_room_updated") {
947
+ this.appEvents.emit("app_room_updated", message);
928
948
  }
929
- else if (message.type === 'app_direct_message_response') {
949
+ else if (message.type === "app_direct_message_response") {
930
950
  const response = message;
931
- if (response.messageId && this.pendingDirectMessages.has(response.messageId)) {
951
+ if (response.messageId &&
952
+ this.pendingDirectMessages.has(response.messageId)) {
932
953
  const { resolve } = this.pendingDirectMessages.get(response.messageId);
933
954
  resolve(response.success);
934
955
  this.pendingDirectMessages.delete(response.messageId);
935
956
  }
936
957
  }
937
- else if (message.type === 'augmentos_settings_update') {
958
+ else if (message.type === "augmentos_settings_update") {
938
959
  const mentraosMsg = message;
939
- if (mentraosMsg.settings && typeof mentraosMsg.settings === 'object') {
960
+ if (mentraosMsg.settings &&
961
+ typeof mentraosMsg.settings === "object") {
940
962
  this.settings.updateMentraosSettings(mentraosMsg.settings);
941
963
  }
942
964
  }
943
965
  // Handle 'connection_error' as a specific case if cloud sends this string literal
944
- else if (message.type === 'connection_error') {
966
+ else if (message.type === "connection_error") {
945
967
  // Treat 'connection_error' (string literal) like AppConnectionError
946
968
  // This handles cases where the cloud might send the type as a direct string
947
969
  // instead of the enum's 'tpa_connection_error' value.
948
- const errorMessage = message.message || 'Unknown connection error (type: connection_error)';
970
+ const errorMessage = message.message ||
971
+ "Unknown connection error (type: connection_error)";
949
972
  this.logger.warn(`Received 'connection_error' type directly. Consider aligning cloud to send 'tpa_connection_error'. Message: ${errorMessage}`);
950
- this.events.emit('error', new Error(errorMessage));
973
+ this.events.emit("error", new Error(errorMessage));
951
974
  }
952
- else if (message.type === 'permission_error') {
975
+ else if (message.type === "permission_error") {
953
976
  // Handle permission errors from cloud
954
- this.logger.warn('Permission error received:', {
977
+ this.logger.warn("Permission error received:", {
955
978
  message: message.message,
956
979
  details: message.details,
957
980
  detailsCount: message.details?.length || 0,
958
- rejectedStreams: message.details?.map(d => d.stream) || []
981
+ rejectedStreams: message.details?.map((d) => d.stream) || [],
959
982
  });
960
983
  // Emit permission error event for application handling
961
- this.events.emit('permission_error', {
984
+ this.events.emit("permission_error", {
962
985
  message: message.message,
963
986
  details: message.details,
964
- timestamp: message.timestamp
987
+ timestamp: message.timestamp,
965
988
  });
966
989
  // Optionally emit individual permission denied events for each stream
967
- message.details?.forEach(detail => {
968
- this.events.emit('permission_denied', {
990
+ message.details?.forEach((detail) => {
991
+ this.events.emit("permission_denied", {
969
992
  stream: detail.stream,
970
993
  requiredPermission: detail.requiredPermission,
971
- message: detail.message
994
+ message: detail.message,
972
995
  });
973
996
  });
974
997
  }
@@ -981,26 +1004,28 @@ class AppSession {
981
1004
  else if ((0, cloud_to_app_1.isPhotoResponse)(message)) {
982
1005
  // Legacy photo response handling - now photos come directly via webhook
983
1006
  // This branch can be removed in the future as all photos now go through /photo-upload
984
- this.logger.warn('Received legacy photo response - photos should now come via /photo-upload webhook');
1007
+ this.logger.warn("Received legacy photo response - photos should now come via /photo-upload webhook");
985
1008
  }
986
1009
  // Handle unrecognized message types gracefully
987
1010
  else {
988
1011
  this.logger.warn(`Unrecognized message type: ${message.type}`);
989
- this.events.emit('error', new Error(`Unrecognized message type: ${message.type}`));
1012
+ this.events.emit("error", new Error(`Unrecognized message type: ${message.type}`));
990
1013
  }
991
1014
  }
992
1015
  catch (processingError) {
993
1016
  // Catch any errors during message processing to prevent App crashes
994
- this.logger.error(processingError, 'Error processing message:');
995
- const errorMessage = processingError instanceof Error ? processingError.message : String(processingError);
996
- this.events.emit('error', new Error(`Error processing message: ${errorMessage}`));
1017
+ this.logger.error(processingError, "Error processing message:");
1018
+ const errorMessage = processingError instanceof Error
1019
+ ? processingError.message
1020
+ : String(processingError);
1021
+ this.events.emit("error", new Error(`Error processing message: ${errorMessage}`));
997
1022
  }
998
1023
  }
999
1024
  catch (error) {
1000
1025
  // Final safety net to ensure the App doesn't crash on any unexpected errors
1001
- this.logger.error(error, 'Unexpected error in message handler');
1026
+ this.logger.error(error, "Unexpected error in message handler");
1002
1027
  const errorMessage = error instanceof Error ? error.message : String(error);
1003
- this.events.emit('error', new Error(`Unexpected error in message handler: ${errorMessage}`));
1028
+ this.events.emit("error", new Error(`Unexpected error in message handler: ${errorMessage}`));
1004
1029
  }
1005
1030
  }
1006
1031
  /**
@@ -1018,7 +1043,7 @@ class AppSession {
1018
1043
  return false;
1019
1044
  }
1020
1045
  // Check if message has a type property
1021
- if (!('type' in message)) {
1046
+ if (!("type" in message)) {
1022
1047
  return false;
1023
1048
  }
1024
1049
  // All other message types should be objects with a type property
@@ -1036,7 +1061,7 @@ class AppSession {
1036
1061
  }
1037
1062
  // Validate buffer has content before processing
1038
1063
  if (!buffer || buffer.byteLength === 0) {
1039
- this.events.emit('error', new Error('Received empty binary message'));
1064
+ this.events.emit("error", new Error("Received empty binary message"));
1040
1065
  return;
1041
1066
  }
1042
1067
  // Create a safety wrapped audio chunk with proper defaults
@@ -1044,15 +1069,15 @@ class AppSession {
1044
1069
  type: types_1.StreamType.AUDIO_CHUNK,
1045
1070
  timestamp: new Date(),
1046
1071
  arrayBuffer: buffer,
1047
- sampleRate: 16000 // Default sample rate
1072
+ sampleRate: 16000, // Default sample rate
1048
1073
  };
1049
1074
  // Emit to subscribers
1050
1075
  this.events.emit(types_1.StreamType.AUDIO_CHUNK, audioChunk);
1051
1076
  }
1052
1077
  catch (error) {
1053
- this.logger.error(error, 'Error processing binary message');
1078
+ this.logger.error(error, "Error processing binary message");
1054
1079
  const errorMessage = error instanceof Error ? error.message : String(error);
1055
- this.events.emit('error', new Error(`Error processing binary message: ${errorMessage}`));
1080
+ this.events.emit("error", new Error(`Error processing binary message: ${errorMessage}`));
1056
1081
  }
1057
1082
  }
1058
1083
  /**
@@ -1071,12 +1096,12 @@ class AppSession {
1071
1096
  switch (streamType) {
1072
1097
  case types_1.StreamType.TRANSCRIPTION:
1073
1098
  // Ensure text field exists and is a string
1074
- if (typeof data.text !== 'string') {
1099
+ if (typeof data.text !== "string") {
1075
1100
  return {
1076
- text: '',
1101
+ text: "",
1077
1102
  isFinal: true,
1078
1103
  startTime: Date.now(),
1079
- endTime: Date.now()
1104
+ endTime: Date.now(),
1080
1105
  };
1081
1106
  }
1082
1107
  break;
@@ -1084,15 +1109,19 @@ class AppSession {
1084
1109
  // Ensure position data has required numeric fields
1085
1110
  // Handle HeadPosition - Note the property position instead of x,y,z
1086
1111
  const pos = data;
1087
- if (typeof pos?.position !== 'string') {
1088
- return { position: 'up', timestamp: new Date() };
1112
+ if (typeof pos?.position !== "string") {
1113
+ return { position: "up", timestamp: new Date() };
1089
1114
  }
1090
1115
  break;
1091
1116
  case types_1.StreamType.BUTTON_PRESS:
1092
1117
  // Ensure button type is valid
1093
1118
  const btn = data;
1094
1119
  if (!btn.buttonId || !btn.pressType) {
1095
- return { buttonId: 'unknown', pressType: 'short', timestamp: new Date() };
1120
+ return {
1121
+ buttonId: "unknown",
1122
+ pressType: "short",
1123
+ timestamp: new Date(),
1124
+ };
1096
1125
  }
1097
1126
  break;
1098
1127
  }
@@ -1113,7 +1142,7 @@ class AppSession {
1113
1142
  sessionId: this.sessionId,
1114
1143
  packageName: this.config.packageName,
1115
1144
  apiKey: this.config.apiKey,
1116
- timestamp: new Date()
1145
+ timestamp: new Date(),
1117
1146
  };
1118
1147
  this.send(message);
1119
1148
  }
@@ -1123,10 +1152,10 @@ class AppSession {
1123
1152
  updateSubscriptions() {
1124
1153
  this.logger.info(`[AppSession] updateSubscriptions: sending subscriptions to cloud:`, Array.from(this.subscriptions));
1125
1154
  // [MODIFIED] builds the array of SubscriptionRequest objects to send to the cloud
1126
- const subscriptionPayload = Array.from(this.subscriptions).map(stream => {
1155
+ const subscriptionPayload = Array.from(this.subscriptions).map((stream) => {
1127
1156
  const rate = this.streamRates.get(stream);
1128
1157
  if (rate && stream === types_1.StreamType.LOCATION_STREAM) {
1129
- return { stream: 'location_stream', rate: rate };
1158
+ return { stream: "location_stream", rate: rate };
1130
1159
  }
1131
1160
  return stream;
1132
1161
  });
@@ -1135,7 +1164,7 @@ class AppSession {
1135
1164
  packageName: this.config.packageName,
1136
1165
  subscriptions: subscriptionPayload, // [MODIFIED]
1137
1166
  sessionId: this.sessionId,
1138
- timestamp: new Date()
1167
+ timestamp: new Date(),
1139
1168
  };
1140
1169
  this.send(message);
1141
1170
  }
@@ -1145,7 +1174,7 @@ class AppSession {
1145
1174
  async handleReconnection() {
1146
1175
  // Check if reconnection is allowed
1147
1176
  if (!this.config.autoReconnect || !this.sessionId) {
1148
- this.logger.debug(`🔄 Reconnection skipped: autoReconnect=${this.config.autoReconnect}, sessionId=${this.sessionId ? 'valid' : 'invalid'}`);
1177
+ this.logger.debug(`🔄 Reconnection skipped: autoReconnect=${this.config.autoReconnect}, sessionId=${this.sessionId ? "valid" : "invalid"}`);
1149
1178
  return;
1150
1179
  }
1151
1180
  // Check if we've exceeded the maximum attempts
@@ -1153,12 +1182,12 @@ class AppSession {
1153
1182
  if (this.reconnectAttempts >= maxAttempts) {
1154
1183
  this.logger.info(`🔄 Maximum reconnection attempts (${maxAttempts}) reached, giving up`);
1155
1184
  // Emit a permanent disconnection event to trigger onStop in the App server
1156
- this.events.emit('disconnected', {
1185
+ this.events.emit("disconnected", {
1157
1186
  message: `Connection permanently lost after ${maxAttempts} failed reconnection attempts`,
1158
1187
  code: 4000, // Custom code for max reconnection attempts exhausted
1159
- reason: 'Maximum reconnection attempts exceeded',
1188
+ reason: "Maximum reconnection attempts exceeded",
1160
1189
  wasClean: false,
1161
- permanent: true // Flag this as a permanent disconnection
1190
+ permanent: true, // Flag this as a permanent disconnection
1162
1191
  });
1163
1192
  return;
1164
1193
  }
@@ -1168,7 +1197,7 @@ class AppSession {
1168
1197
  this.reconnectAttempts++;
1169
1198
  this.logger.debug(`🔄 [${this.config.packageName}] Reconnection attempt ${this.reconnectAttempts}/${maxAttempts} in ${delay}ms`);
1170
1199
  // Use the resource tracker for the timeout
1171
- await new Promise(resolve => {
1200
+ await new Promise((resolve) => {
1172
1201
  this.resources.setTimeout(() => resolve(), delay);
1173
1202
  });
1174
1203
  try {
@@ -1180,17 +1209,17 @@ class AppSession {
1180
1209
  catch (error) {
1181
1210
  const errorMessage = error instanceof Error ? error.message : String(error);
1182
1211
  this.logger.error(error, `❌ [${this.config.packageName}] Reconnection failed for user ${this.userId}`);
1183
- this.events.emit('error', new Error(`Reconnection failed: ${errorMessage}`));
1212
+ this.events.emit("error", new Error(`Reconnection failed: ${errorMessage}`));
1184
1213
  // Check if this was the last attempt
1185
1214
  if (this.reconnectAttempts >= maxAttempts) {
1186
1215
  this.logger.debug(`🔄 [${this.config.packageName}] Final reconnection attempt failed, emitting permanent disconnection`);
1187
1216
  // Emit permanent disconnection event after the last failed attempt
1188
- this.events.emit('disconnected', {
1217
+ this.events.emit("disconnected", {
1189
1218
  message: `Connection permanently lost after ${maxAttempts} failed reconnection attempts`,
1190
1219
  code: 4000, // Custom code for max reconnection attempts exhausted
1191
- reason: 'Maximum reconnection attempts exceeded',
1220
+ reason: "Maximum reconnection attempts exceeded",
1192
1221
  wasClean: false,
1193
- permanent: true // Flag this as a permanent disconnection
1222
+ permanent: true, // Flag this as a permanent disconnection
1194
1223
  });
1195
1224
  }
1196
1225
  }
@@ -1203,27 +1232,27 @@ class AppSession {
1203
1232
  try {
1204
1233
  // Verify WebSocket connection is valid
1205
1234
  if (!this.ws) {
1206
- throw new Error('WebSocket connection not established');
1235
+ throw new Error("WebSocket connection not established");
1207
1236
  }
1208
1237
  if (this.ws.readyState !== 1) {
1209
1238
  const stateMap = {
1210
- 0: 'CONNECTING',
1211
- 1: 'OPEN',
1212
- 2: 'CLOSING',
1213
- 3: 'CLOSED'
1239
+ 0: "CONNECTING",
1240
+ 1: "OPEN",
1241
+ 2: "CLOSING",
1242
+ 3: "CLOSED",
1214
1243
  };
1215
- const stateName = stateMap[this.ws.readyState] || 'UNKNOWN';
1244
+ const stateName = stateMap[this.ws.readyState] || "UNKNOWN";
1216
1245
  throw new Error(`WebSocket not connected (current state: ${stateName})`);
1217
1246
  }
1218
1247
  // Validate message before sending
1219
- if (!message || typeof message !== 'object') {
1220
- throw new Error('Invalid message: must be an object');
1248
+ if (!message || typeof message !== "object") {
1249
+ throw new Error("Invalid message: must be an object");
1221
1250
  }
1222
- if (!('type' in message)) {
1251
+ if (!("type" in message)) {
1223
1252
  throw new Error('Invalid message: missing "type" property');
1224
1253
  }
1225
1254
  // Ensure message format is consistent
1226
- if (!('timestamp' in message) || !(message.timestamp instanceof Date)) {
1255
+ if (!("timestamp" in message) || !(message.timestamp instanceof Date)) {
1227
1256
  message.timestamp = new Date();
1228
1257
  }
1229
1258
  // Try to send with error handling
@@ -1238,13 +1267,13 @@ class AppSession {
1238
1267
  }
1239
1268
  catch (error) {
1240
1269
  // Log the error and emit an event so App developers are aware
1241
- this.logger.error(error, 'Message send error');
1270
+ this.logger.error(error, "Message send error");
1242
1271
  // Ensure we always emit an Error object
1243
1272
  if (error instanceof Error) {
1244
- this.events.emit('error', error);
1273
+ this.events.emit("error", error);
1245
1274
  }
1246
1275
  else {
1247
- this.events.emit('error', new Error(String(error)));
1276
+ this.events.emit("error", new Error(String(error)));
1248
1277
  }
1249
1278
  // Re-throw to maintain the original function behavior
1250
1279
  throw error;
@@ -1257,11 +1286,13 @@ class AppSession {
1257
1286
  async getInstructions() {
1258
1287
  try {
1259
1288
  const baseUrl = this.getServerUrl();
1260
- const response = await axios_1.default.get(`${baseUrl}/api/instructions`, { params: { userId: this.userId } });
1289
+ const response = await axios_1.default.get(`${baseUrl}/api/instructions`, {
1290
+ params: { userId: this.userId },
1291
+ });
1261
1292
  return response.data.instructions || null;
1262
1293
  }
1263
1294
  catch (err) {
1264
- this.logger.error('Error fetching instructions from backend:', err);
1295
+ this.logger.error("Error fetching instructions from backend:", err);
1265
1296
  return null;
1266
1297
  }
1267
1298
  }
@@ -1276,26 +1307,26 @@ class AppSession {
1276
1307
  async discoverAppUsers(domain, includeProfiles = false) {
1277
1308
  // Use the domain argument as the base URL if provided
1278
1309
  if (!domain) {
1279
- throw new Error('Domain (API base URL) is required for user discovery');
1310
+ throw new Error("Domain (API base URL) is required for user discovery");
1280
1311
  }
1281
1312
  const url = `${domain}/api/app-communication/discover-users`;
1282
1313
  // Use the user's core token for authentication
1283
1314
  const appApiKey = this.config.apiKey; // This may need to be updated if you store the core token elsewhere
1284
1315
  if (!appApiKey) {
1285
- throw new Error('Core token (apiKey) is required for user discovery');
1316
+ throw new Error("Core token (apiKey) is required for user discovery");
1286
1317
  }
1287
1318
  const body = {
1288
1319
  packageName: this.config.packageName,
1289
1320
  userId: this.userId,
1290
- includeUserProfiles: includeProfiles
1321
+ includeUserProfiles: includeProfiles,
1291
1322
  };
1292
1323
  const response = await (0, node_fetch_1.default)(url, {
1293
- method: 'POST',
1324
+ method: "POST",
1294
1325
  headers: {
1295
- 'Authorization': `Bearer ${appApiKey}`,
1296
- 'Content-Type': 'application/json'
1326
+ Authorization: `Bearer ${appApiKey}`,
1327
+ "Content-Type": "application/json",
1297
1328
  },
1298
- body: JSON.stringify(body)
1329
+ body: JSON.stringify(body),
1299
1330
  });
1300
1331
  if (!response.ok) {
1301
1332
  const errorText = await response.text();
@@ -1310,11 +1341,11 @@ class AppSession {
1310
1341
  */
1311
1342
  async isUserActive(userId) {
1312
1343
  try {
1313
- const userList = await this.discoverAppUsers('', false);
1344
+ const userList = await this.discoverAppUsers("", false);
1314
1345
  return userList.users.some((user) => user.userId === userId);
1315
1346
  }
1316
1347
  catch (error) {
1317
- this.logger.error({ error, userId }, 'Error checking if user is active');
1348
+ this.logger.error({ error, userId }, "Error checking if user is active");
1318
1349
  return false;
1319
1350
  }
1320
1351
  }
@@ -1328,7 +1359,7 @@ class AppSession {
1328
1359
  return userList.totalUsers;
1329
1360
  }
1330
1361
  catch (error) {
1331
- this.logger.error(error, 'Error getting user count');
1362
+ this.logger.error(error, "Error getting user count");
1332
1363
  return 0;
1333
1364
  }
1334
1365
  }
@@ -1342,13 +1373,13 @@ class AppSession {
1342
1373
  try {
1343
1374
  const messageId = this.generateMessageId();
1344
1375
  const message = {
1345
- type: 'app_broadcast_message',
1376
+ type: "app_broadcast_message",
1346
1377
  packageName: this.config.packageName,
1347
1378
  sessionId: this.sessionId,
1348
1379
  payload,
1349
1380
  messageId,
1350
1381
  senderUserId: this.userId,
1351
- timestamp: new Date()
1382
+ timestamp: new Date(),
1352
1383
  };
1353
1384
  this.send(message);
1354
1385
  }
@@ -1370,21 +1401,23 @@ class AppSession {
1370
1401
  // Store promise resolver
1371
1402
  this.pendingDirectMessages.set(messageId, { resolve, reject });
1372
1403
  const message = {
1373
- type: 'app_direct_message',
1404
+ type: "app_direct_message",
1374
1405
  packageName: this.config.packageName,
1375
1406
  sessionId: this.sessionId,
1376
1407
  targetUserId,
1377
1408
  payload,
1378
1409
  messageId,
1379
1410
  senderUserId: this.userId,
1380
- timestamp: new Date()
1411
+ timestamp: new Date(),
1381
1412
  };
1382
1413
  this.send(message);
1383
1414
  // Set timeout to avoid hanging promises
1384
1415
  const timeoutMs = 15000; // 15 seconds
1385
1416
  this.resources.setTimeout(() => {
1386
1417
  if (this.pendingDirectMessages.has(messageId)) {
1387
- this.pendingDirectMessages.get(messageId).reject(new Error('Direct message timed out'));
1418
+ this.pendingDirectMessages
1419
+ .get(messageId)
1420
+ .reject(new Error("Direct message timed out"));
1388
1421
  this.pendingDirectMessages.delete(messageId);
1389
1422
  }
1390
1423
  }, timeoutMs);
@@ -1404,12 +1437,12 @@ class AppSession {
1404
1437
  async joinAppRoom(roomId, roomConfig) {
1405
1438
  try {
1406
1439
  const message = {
1407
- type: 'app_room_join',
1440
+ type: "app_room_join",
1408
1441
  packageName: this.config.packageName,
1409
1442
  sessionId: this.sessionId,
1410
1443
  roomId,
1411
1444
  roomConfig,
1412
- timestamp: new Date()
1445
+ timestamp: new Date(),
1413
1446
  };
1414
1447
  this.send(message);
1415
1448
  }
@@ -1426,11 +1459,11 @@ class AppSession {
1426
1459
  async leaveAppRoom(roomId) {
1427
1460
  try {
1428
1461
  const message = {
1429
- type: 'app_room_leave',
1462
+ type: "app_room_leave",
1430
1463
  packageName: this.config.packageName,
1431
1464
  sessionId: this.sessionId,
1432
1465
  roomId,
1433
- timestamp: new Date()
1466
+ timestamp: new Date(),
1434
1467
  };
1435
1468
  this.send(message);
1436
1469
  }
@@ -1445,8 +1478,8 @@ class AppSession {
1445
1478
  * @returns Cleanup function to remove the handler
1446
1479
  */
1447
1480
  onAppMessage(handler) {
1448
- this.appEvents.on('app_message_received', handler);
1449
- return () => this.appEvents.off('app_message_received', handler);
1481
+ this.appEvents.on("app_message_received", handler);
1482
+ return () => this.appEvents.off("app_message_received", handler);
1450
1483
  }
1451
1484
  /**
1452
1485
  * 👋 Listen for user join events
@@ -1454,8 +1487,8 @@ class AppSession {
1454
1487
  * @returns Cleanup function to remove the handler
1455
1488
  */
1456
1489
  onAppUserJoined(handler) {
1457
- this.appEvents.on('app_user_joined', handler);
1458
- return () => this.appEvents.off('app_user_joined', handler);
1490
+ this.appEvents.on("app_user_joined", handler);
1491
+ return () => this.appEvents.off("app_user_joined", handler);
1459
1492
  }
1460
1493
  /**
1461
1494
  * 🚪 Listen for user leave events
@@ -1463,8 +1496,8 @@ class AppSession {
1463
1496
  * @returns Cleanup function to remove the handler
1464
1497
  */
1465
1498
  onAppUserLeft(handler) {
1466
- this.appEvents.on('app_user_left', handler);
1467
- return () => this.appEvents.off('app_user_left', handler);
1499
+ this.appEvents.on("app_user_left", handler);
1500
+ return () => this.appEvents.off("app_user_left", handler);
1468
1501
  }
1469
1502
  /**
1470
1503
  * 🏠 Listen for room update events
@@ -1472,8 +1505,8 @@ class AppSession {
1472
1505
  * @returns Cleanup function to remove the handler
1473
1506
  */
1474
1507
  onAppRoomUpdated(handler) {
1475
- this.appEvents.on('app_room_updated', handler);
1476
- return () => this.appEvents.off('app_room_updated', handler);
1508
+ this.appEvents.on("app_room_updated", handler);
1509
+ return () => this.appEvents.off("app_room_updated", handler);
1477
1510
  }
1478
1511
  /**
1479
1512
  * 🔧 Generate unique message ID
@@ -1501,8 +1534,8 @@ class TpaSession extends AppSession {
1501
1534
  constructor(config) {
1502
1535
  super(config);
1503
1536
  // Emit a deprecation warning to help developers migrate
1504
- console.warn('⚠️ DEPRECATION WARNING: TpaSession is deprecated and will be removed in a future version. ' +
1505
- 'Please use AppSession instead. ' +
1537
+ console.warn("⚠️ DEPRECATION WARNING: TpaSession is deprecated and will be removed in a future version. " +
1538
+ "Please use AppSession instead. " +
1506
1539
  'Simply replace "TpaSession" with "AppSession" in your code.');
1507
1540
  }
1508
1541
  }