@mentra/sdk 2.1.31-beta.4 โ†’ 2.1.31-beta.6

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.
@@ -8,7 +8,23 @@ import { type Express } from "express";
8
8
  import { AppSession } from "../session/index";
9
9
  import { ToolCall } from "../../types";
10
10
  import { Logger } from "pino";
11
+ import { PhotoData } from "../../types/photo-data";
11
12
  export declare const GIVE_APP_CONTROL_OF_TOOL_RESPONSE: string;
13
+ /**
14
+ * Pending photo request stored at AppServer level
15
+ * This allows O(1) lookup when photo uploads arrive via HTTP,
16
+ * and survives session reconnections.
17
+ * See: cloud/issues/019-sdk-photo-request-architecture
18
+ */
19
+ export interface PendingPhotoRequest {
20
+ userId: string;
21
+ sessionId: string;
22
+ session: AppSession;
23
+ resolve: (photo: PhotoData) => void;
24
+ reject: (error: Error) => void;
25
+ timestamp: number;
26
+ timeoutId?: NodeJS.Timeout;
27
+ }
12
28
  /**
13
29
  * ๐Ÿ”ง Configuration options for App Server
14
30
  *
@@ -90,6 +106,16 @@ export declare class AppServer {
90
106
  private cleanupHandlers;
91
107
  /** App instructions string shown to the user */
92
108
  private appInstructions;
109
+ /**
110
+ * Pending photo requests by requestId - owned by AppServer for HTTP endpoint access.
111
+ * This is the single source of truth for pending photo requests.
112
+ * Stored here (not on CameraModule) because:
113
+ * 1. Photo uploads arrive via HTTP to AppServer, not via WebSocket to session
114
+ * 2. Allows O(1) lookup by requestId instead of iterating all sessions
115
+ * 3. Survives session reconnections (session may be removed from activeSessions temporarily)
116
+ * See: cloud/issues/019-sdk-photo-request-architecture
117
+ */
118
+ private pendingPhotoRequests;
93
119
  readonly logger: Logger;
94
120
  constructor(config: AppServerConfig);
95
121
  getExpressApp(): Express;
@@ -192,7 +218,18 @@ export declare class AppServer {
192
218
  /**
193
219
  * ๐Ÿงน Cleanup
194
220
  * Closes all active sessions and runs cleanup handlers.
195
- * Releases ownership before disconnecting to enable clean handoffs (no resurrection).
221
+ * Does NOT release ownership - we want the cloud to resurrect when we come back up.
222
+ *
223
+ * OWNERSHIP_RELEASE should only be sent for:
224
+ * - switching_clouds: User moved to another cloud, don't compete
225
+ * - user_logout: User explicitly logged out
226
+ *
227
+ * NOT for clean_shutdown, because:
228
+ * - Server is restarting/redeploying
229
+ * - Cloud should resurrect the app (trigger webhook)
230
+ * - User expects their app to keep running
231
+ *
232
+ * See: cloud/issues/023-disposed-appsession-resurrection-bug
196
233
  */
197
234
  private cleanup;
198
235
  /**
@@ -200,15 +237,42 @@ export declare class AppServer {
200
237
  * Creates a /photo-upload endpoint for receiving photos directly from ASG glasses
201
238
  */
202
239
  private setupPhotoUploadEndpoint;
240
+ /**
241
+ * Register a pending photo request.
242
+ * Called by CameraModule when a photo is requested.
243
+ * Stores the request at AppServer level for O(1) lookup when HTTP response arrives.
244
+ *
245
+ * @param requestId - Unique identifier for this photo request
246
+ * @param request - Request details including session, resolve/reject callbacks
247
+ */
248
+ registerPhotoRequest(requestId: string, request: Omit<PendingPhotoRequest, "timeoutId">): void;
249
+ /**
250
+ * Get a pending photo request by ID.
251
+ *
252
+ * @param requestId - The request ID to look up
253
+ * @returns The pending request, or undefined if not found
254
+ */
255
+ getPhotoRequest(requestId: string): PendingPhotoRequest | undefined;
256
+ /**
257
+ * Complete a photo request (success or error).
258
+ * Clears the timeout and removes from the pending map.
259
+ *
260
+ * @param requestId - The request ID to complete
261
+ * @returns The pending request that was completed, or undefined if not found
262
+ */
263
+ completePhotoRequest(requestId: string): PendingPhotoRequest | undefined;
264
+ /**
265
+ * Clean up all pending photo requests for a session.
266
+ * Called when a session permanently disconnects.
267
+ *
268
+ * @param sessionId - The session ID to clean up requests for
269
+ */
270
+ cleanupPhotoRequestsForSession(sessionId: string): void;
203
271
  /**
204
272
  * ๐Ÿ” Setup Mentra Auth Redirect Endpoint
205
273
  * Creates a /mentra-auth endpoint that redirects to the MentraOS OAuth flow.
206
274
  */
207
275
  private setupMentraAuthRedirect;
208
- /**
209
- * Find session that has a pending photo request for the given requestId
210
- */
211
- private findSessionByPhotoRequestId;
212
276
  }
213
277
  /**
214
278
  * @deprecated Use `AppServerConfig` instead. `TpaServerConfig` is deprecated and will be removed in a future version.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/app/server/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/app/server/index.ts"],"names":[],"mappings":"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"}
@@ -74,8 +74,6 @@ export declare class CameraModule {
74
74
  private packageName;
75
75
  private sessionId;
76
76
  private logger;
77
- /** Map to store pending photo request promises */
78
- private pendingPhotoRequests;
79
77
  private isStreaming;
80
78
  private currentStreamUrl?;
81
79
  private currentStreamState?;
@@ -108,63 +106,27 @@ export declare class CameraModule {
108
106
  * ```
109
107
  */
110
108
  requestPhoto(options?: PhotoRequestOptions): Promise<PhotoData>;
111
- /**
112
- * ๐Ÿ“ฅ Handle photo received from /photo-upload endpoint
113
- *
114
- * This method is called internally when a photo response is received.
115
- * It resolves the corresponding pending promise with the photo data.
116
- *
117
- * @param photoData - The photo data received
118
- * @internal This method is used internally by AppSession
119
- */
120
- handlePhotoReceived(photoData: PhotoData): void;
121
- /**
122
- * โŒ Handle photo error from /photo-upload endpoint
123
- *
124
- * This method is called internally when a photo error response is received.
125
- * It rejects the corresponding pending promise with the error information.
126
- *
127
- * @param errorResponse - The error response received
128
- * @internal This method is used internally by AppSession
129
- */
130
- handlePhotoError(errorResponse: {
131
- requestId: string;
132
- success: false;
133
- error: {
134
- code: string;
135
- message: string;
136
- };
137
- }): void;
138
109
  /**
139
110
  * ๐Ÿ” Check if there's a pending photo request for the given request ID
111
+ * @deprecated Photo requests are now managed at AppServer level. This method delegates to AppServer.
140
112
  *
141
113
  * @param requestId - The request ID to check
142
114
  * @returns true if there's a pending request
143
115
  */
144
116
  hasPhotoPendingRequest(requestId: string): boolean;
145
- /**
146
- * ๐Ÿ“Š Get the number of pending photo requests
147
- *
148
- * @returns Number of pending photo requests
149
- */
150
- getPhotoPendingRequestCount(): number;
151
- /**
152
- * ๐Ÿ“‹ Get all pending photo request IDs
153
- *
154
- * @returns Array of pending request IDs
155
- */
156
- getPhotoPendingRequestIds(): string[];
157
117
  /**
158
118
  * โŒ Cancel a pending photo request
119
+ * @deprecated Photo requests are now managed at AppServer level. This method delegates to AppServer.
159
120
  *
160
121
  * @param requestId - The request ID to cancel
161
122
  * @returns true if the request was cancelled, false if it wasn't found
162
123
  */
163
124
  cancelPhotoRequest(requestId: string): boolean;
164
125
  /**
165
- * ๐Ÿงน Cancel all pending photo requests
126
+ * ๐Ÿงน Cancel all pending photo requests for this session
127
+ * @deprecated Photo requests are now managed at AppServer level. Use AppServer.cleanupPhotoRequestsForSession() instead.
166
128
  *
167
- * @returns Number of requests that were cancelled
129
+ * @returns Number of requests that were cancelled (always 0, cleanup happens at AppServer level)
168
130
  */
169
131
  cancelAllPhotoRequests(): number;
170
132
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"camera.d.ts","sourceRoot":"","sources":["../../../../src/app/session/modules/camera.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"camera.d.ts","sourceRoot":"","sources":["../../../../src/app/session/modules/camera.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -326,6 +326,8 @@ var init_message_types = __esm(() => {
326
326
  GlassesToCloudMessageType2["AUDIO_PLAY_RESPONSE"] = "audio_play_response";
327
327
  GlassesToCloudMessageType2["RGB_LED_CONTROL_RESPONSE"] = "rgb_led_control_response";
328
328
  GlassesToCloudMessageType2["LIVEKIT_INIT"] = "livekit_init";
329
+ GlassesToCloudMessageType2["UDP_REGISTER"] = "udp_register";
330
+ GlassesToCloudMessageType2["UDP_UNREGISTER"] = "udp_unregister";
329
331
  })(GlassesToCloudMessageType ||= {});
330
332
  ((CloudToGlassesMessageType2) => {
331
333
  CloudToGlassesMessageType2["CONNECTION_ACK"] = "connection_ack";
@@ -349,6 +351,7 @@ var init_message_types = __esm(() => {
349
351
  CloudToGlassesMessageType2["REQUEST_SINGLE_LOCATION"] = "request_single_location";
350
352
  CloudToGlassesMessageType2["WEBSOCKET_ERROR"] = "websocket_error";
351
353
  CloudToGlassesMessageType2["LIVEKIT_INFO"] = "livekit_info";
354
+ CloudToGlassesMessageType2["UDP_PING_ACK"] = "udp_ping_ack";
352
355
  })(CloudToGlassesMessageType ||= {});
353
356
  ((AppToCloudMessageType2) => {
354
357
  AppToCloudMessageType2["CONNECTION_INIT"] = "tpa_connection_init";
@@ -737,6 +740,12 @@ function isAudioPlayResponse(message) {
737
740
  function isLocalTranscription(message) {
738
741
  return message.type === "local_transcription" /* LOCAL_TRANSCRIPTION */;
739
742
  }
743
+ function isUdpRegister(message) {
744
+ return message.type === "udp_register" /* UDP_REGISTER */;
745
+ }
746
+ function isUdpUnregister(message) {
747
+ return message.type === "udp_unregister" /* UDP_UNREGISTER */;
748
+ }
740
749
  // src/types/messages/cloud-to-glasses.ts
741
750
  init_message_types();
742
751
  function isResponse(message) {
@@ -2646,7 +2655,6 @@ class CameraModule {
2646
2655
  packageName;
2647
2656
  sessionId;
2648
2657
  logger;
2649
- pendingPhotoRequests = new Map;
2650
2658
  isStreaming = false;
2651
2659
  currentStreamUrl;
2652
2660
  currentStreamState;
@@ -2663,9 +2671,15 @@ class CameraModule {
2663
2671
  const baseUrl = this.session?.getHttpsServerUrl?.() || "";
2664
2672
  cameraWarnLog(baseUrl, this.packageName, "requestPhoto");
2665
2673
  try {
2666
- console.log("DEBUG: requestPhoto options:", options);
2667
2674
  const requestId = `photo_req_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
2668
- this.pendingPhotoRequests.set(requestId, { resolve, reject });
2675
+ this.session.appServer.registerPhotoRequest(requestId, {
2676
+ userId: this.session.userId,
2677
+ sessionId: this.sessionId,
2678
+ session: this.session,
2679
+ resolve,
2680
+ reject: (error) => reject(error.message),
2681
+ timestamp: Date.now()
2682
+ });
2669
2683
  const message = {
2670
2684
  type: "photo_request" /* PHOTO_REQUEST */,
2671
2685
  packageName: this.packageName,
@@ -2687,91 +2701,41 @@ class CameraModule {
2687
2701
  }, `\uD83D\uDCF8 Photo request sent`);
2688
2702
  if (options?.customWebhookUrl) {
2689
2703
  this.logger.info({ requestId, customWebhookUrl: options.customWebhookUrl }, `\uD83D\uDCF8 Using custom webhook URL - resolving promise immediately since photo will be uploaded directly to custom endpoint`);
2690
- const mockPhotoData = {
2691
- buffer: Buffer.from([]),
2692
- mimeType: "image/jpeg",
2693
- filename: "photo.jpg",
2694
- requestId,
2695
- size: 0,
2696
- timestamp: new Date
2697
- };
2698
- this.pendingPhotoRequests.delete(requestId);
2699
- resolve(mockPhotoData);
2704
+ const pending = this.session.appServer.completePhotoRequest(requestId);
2705
+ if (pending) {
2706
+ const mockPhotoData = {
2707
+ buffer: Buffer.from([]),
2708
+ mimeType: "image/jpeg",
2709
+ filename: "photo.jpg",
2710
+ requestId,
2711
+ size: 0,
2712
+ timestamp: new Date
2713
+ };
2714
+ pending.resolve(mockPhotoData);
2715
+ }
2700
2716
  return;
2701
2717
  }
2702
- const timeoutMs = 30000;
2703
- if (this.session && this.session.resources) {
2704
- this.session.resources.setTimeout(() => {
2705
- if (this.pendingPhotoRequests.has(requestId)) {
2706
- this.pendingPhotoRequests.get(requestId).reject("Photo request timed out");
2707
- this.pendingPhotoRequests.delete(requestId);
2708
- this.logger.warn({ requestId }, `\uD83D\uDCF8 Photo request timed out`);
2709
- }
2710
- }, timeoutMs);
2711
- } else {
2712
- setTimeout(() => {
2713
- if (this.pendingPhotoRequests.has(requestId)) {
2714
- this.pendingPhotoRequests.get(requestId).reject("Photo request timed out");
2715
- this.pendingPhotoRequests.delete(requestId);
2716
- this.logger.warn({ requestId }, `\uD83D\uDCF8 Photo request timed out`);
2717
- }
2718
- }, timeoutMs);
2719
- }
2720
2718
  } catch (error) {
2721
2719
  const errorMessage = error instanceof Error ? error.message : String(error);
2722
2720
  reject(`Failed to request photo: ${errorMessage}`);
2723
2721
  }
2724
2722
  });
2725
2723
  }
2726
- handlePhotoReceived(photoData) {
2727
- const { requestId } = photoData;
2728
- const pendingRequest = this.pendingPhotoRequests.get(requestId);
2729
- if (pendingRequest) {
2730
- this.logger.info({ requestId }, `\uD83D\uDCF8 Photo received for request ${requestId}`);
2731
- pendingRequest.resolve(photoData);
2732
- this.pendingPhotoRequests.delete(requestId);
2733
- } else {
2734
- this.logger.warn({ requestId }, `\uD83D\uDCF8 Received photo for unknown request ID: ${requestId}`);
2735
- }
2736
- }
2737
- handlePhotoError(errorResponse) {
2738
- const { requestId, error } = errorResponse;
2739
- const pendingRequest = this.pendingPhotoRequests.get(requestId);
2740
- if (pendingRequest) {
2741
- this.logger.error({ requestId, errorCode: error.code, errorMessage: error.message }, `\uD83D\uDCF8 Photo capture failed: ${error.code} - ${error.message}`);
2742
- pendingRequest.reject(`${error.code}: ${error.message}`);
2743
- this.pendingPhotoRequests.delete(requestId);
2744
- } else {
2745
- this.logger.warn({ requestId, errorCode: error.code, errorMessage: error.message }, `\uD83D\uDCF8 Received photo error for unknown request ID: ${requestId}`);
2746
- }
2747
- }
2748
2724
  hasPhotoPendingRequest(requestId) {
2749
- return this.pendingPhotoRequests.has(requestId);
2750
- }
2751
- getPhotoPendingRequestCount() {
2752
- return this.pendingPhotoRequests.size;
2753
- }
2754
- getPhotoPendingRequestIds() {
2755
- return Array.from(this.pendingPhotoRequests.keys());
2725
+ return this.session.appServer.getPhotoRequest(requestId) !== undefined;
2756
2726
  }
2757
2727
  cancelPhotoRequest(requestId) {
2758
- const pendingRequest = this.pendingPhotoRequests.get(requestId);
2759
- if (pendingRequest) {
2760
- pendingRequest.reject("Photo request cancelled");
2761
- this.pendingPhotoRequests.delete(requestId);
2728
+ const pending = this.session.appServer.completePhotoRequest(requestId);
2729
+ if (pending) {
2730
+ pending.reject(new Error("Photo request cancelled"));
2762
2731
  this.logger.info({ requestId }, `\uD83D\uDCF8 Photo request cancelled`);
2763
2732
  return true;
2764
2733
  }
2765
2734
  return false;
2766
2735
  }
2767
2736
  cancelAllPhotoRequests() {
2768
- const count = this.pendingPhotoRequests.size;
2769
- for (const [requestId, { reject }] of this.pendingPhotoRequests) {
2770
- reject("Photo request cancelled - session cleanup");
2771
- this.logger.info({ requestId }, `\uD83D\uDCF8 Photo request cancelled during cleanup`);
2772
- }
2773
- this.pendingPhotoRequests.clear();
2774
- return count;
2737
+ this.logger.debug(`\uD83D\uDCF8 cancelAllPhotoRequests called - cleanup now happens at AppServer level`);
2738
+ return 0;
2775
2739
  }
2776
2740
  async startStream(options) {
2777
2741
  this.logger.info({ rtmpUrl: options.rtmpUrl }, `\uD83D\uDCF9 RTMP stream request starting`);
@@ -5072,6 +5036,7 @@ class AppServer {
5072
5036
  activeSessionsByUserId = new Map;
5073
5037
  cleanupHandlers = [];
5074
5038
  appInstructions = null;
5039
+ pendingPhotoRequests = new Map;
5075
5040
  logger;
5076
5041
  constructor(config) {
5077
5042
  this.config = config;
@@ -5246,6 +5211,23 @@ class AppServer {
5246
5211
  this.logger.info({ userId }, `\uD83D\uDDE3๏ธ Received session request for user ${userId}, session ${sessionId}
5247
5212
 
5248
5213
  `);
5214
+ const existingSession = this.activeSessions.get(sessionId);
5215
+ if (existingSession) {
5216
+ this.logger.info({ sessionId, userId }, `\uD83D\uDD04 Existing session found for ${sessionId} - sending OWNERSHIP_RELEASE and disconnecting before new connection`);
5217
+ try {
5218
+ await existingSession.releaseOwnership("switching_clouds");
5219
+ } catch (error) {
5220
+ this.logger.warn({ error, sessionId }, `โš ๏ธ Failed to send OWNERSHIP_RELEASE to old session - continuing anyway`);
5221
+ }
5222
+ try {
5223
+ existingSession.disconnect();
5224
+ } catch (error) {
5225
+ this.logger.warn({ error, sessionId }, `โš ๏ธ Failed to disconnect old session - continuing anyway`);
5226
+ }
5227
+ this.activeSessions.delete(sessionId);
5228
+ this.activeSessionsByUserId.delete(userId);
5229
+ this.logger.info({ sessionId, userId }, `โœ… Old session cleaned up, proceeding with new connection`);
5230
+ }
5249
5231
  const session = new AppSession({
5250
5232
  packageName: this.config.packageName,
5251
5233
  apiKey: this.config.apiKey,
@@ -5254,25 +5236,48 @@ class AppServer {
5254
5236
  userId
5255
5237
  });
5256
5238
  const cleanupDisconnect = session.events.onDisconnected((info) => {
5239
+ let isPermanent = false;
5240
+ let reason = "unknown";
5257
5241
  if (typeof info === "string") {
5258
5242
  this.logger.info(`\uD83D\uDC4B Session ${sessionId} disconnected: ${info}`);
5243
+ reason = info;
5244
+ isPermanent = false;
5259
5245
  } else {
5260
5246
  this.logger.info(`\uD83D\uDC4B Session ${sessionId} disconnected: ${info.message} (code: ${info.code}, reason: ${info.reason})`);
5247
+ reason = info.reason || info.message;
5261
5248
  if (info.sessionEnded === true) {
5262
5249
  this.logger.info(`\uD83D\uDED1 User session ended for session ${sessionId}, calling onStop`);
5250
+ isPermanent = true;
5263
5251
  this.onStop(sessionId, userId, "User session ended").catch((error) => {
5264
5252
  this.logger.error(error, `โŒ Error in onStop handler for session end:`);
5265
5253
  });
5266
5254
  } else if (info.permanent === true) {
5267
5255
  this.logger.info(`\uD83D\uDED1 Permanent disconnection detected for session ${sessionId}, calling onStop`);
5268
- const _session = this.activeSessions.get(sessionId);
5256
+ isPermanent = true;
5269
5257
  this.onStop(sessionId, userId, `Connection permanently lost: ${info.reason}`).catch((error) => {
5270
5258
  this.logger.error(error, `โŒ Error in onStop handler for permanent disconnection:`);
5271
5259
  });
5260
+ } else if (info.wasClean === true || info.code === 1000 || info.code === 1001) {
5261
+ this.logger.info(`\uD83D\uDED1 Clean WebSocket closure for session ${sessionId} (code: ${info.code}), treating as permanent`);
5262
+ isPermanent = true;
5263
+ this.onStop(sessionId, userId, `Clean disconnect: ${reason}`).catch((error) => {
5264
+ this.logger.error(error, `โŒ Error in onStop handler for clean disconnect:`);
5265
+ });
5272
5266
  }
5273
5267
  }
5274
- this.activeSessions.delete(sessionId);
5275
- this.activeSessionsByUserId.delete(userId);
5268
+ if (isPermanent) {
5269
+ if (this.activeSessions.get(sessionId) === session) {
5270
+ this.activeSessions.delete(sessionId);
5271
+ } else {
5272
+ this.logger.debug({ sessionId }, `\uD83D\uDD04 Session ${sessionId} cleanup skipped - a newer session has taken over`);
5273
+ }
5274
+ if (this.activeSessionsByUserId.get(userId) === session) {
5275
+ this.activeSessionsByUserId.delete(userId);
5276
+ }
5277
+ this.cleanupPhotoRequestsForSession(sessionId);
5278
+ } else {
5279
+ this.logger.debug({ sessionId, reason }, `\uD83D\uDD04 Temporary disconnect for session ${sessionId}, keeping in maps for reconnection`);
5280
+ }
5276
5281
  });
5277
5282
  const cleanupError = session.events.onError((error) => {
5278
5283
  this.logger.error(error, `โŒ [Session ${sessionId}] Error:`);
@@ -5376,12 +5381,12 @@ class AppServer {
5376
5381
  process.on("SIGINT", () => this.stop());
5377
5382
  }
5378
5383
  async cleanup() {
5384
+ this.logger.info(`\uD83D\uDD27 [LOCAL SDK] cleanup() called - NOT sending OWNERSHIP_RELEASE`);
5379
5385
  for (const [sessionId, session] of this.activeSessions) {
5380
- this.logger.info(`\uD83D\uDC4B Closing session ${sessionId} with ownership release`);
5386
+ this.logger.info(`\uD83D\uDC4B Closing session ${sessionId} (no ownership release - cloud will resurrect)`);
5381
5387
  try {
5382
5388
  await session.disconnect({
5383
- releaseOwnership: true,
5384
- reason: "clean_shutdown"
5389
+ releaseOwnership: false
5385
5390
  });
5386
5391
  } catch (error) {
5387
5392
  this.logger.error(error, `Error during cleanup of session ${sessionId}`);
@@ -5413,7 +5418,6 @@ class AppServer {
5413
5418
  try {
5414
5419
  const { requestId, type, success, errorCode, errorMessage } = req.body;
5415
5420
  const photoFile = req.file;
5416
- console.log("Received photo response: ", req.body);
5417
5421
  this.logger.info({ requestId, type, success, errorCode }, `\uD83D\uDCF8 Received photo response: ${requestId} (type: ${type})`);
5418
5422
  if (!requestId) {
5419
5423
  this.logger.error("No requestId in photo response");
@@ -5422,24 +5426,18 @@ class AppServer {
5422
5426
  error: "No requestId provided"
5423
5427
  });
5424
5428
  }
5425
- const session = this.findSessionByPhotoRequestId(requestId);
5426
- if (!session) {
5427
- this.logger.warn({ requestId }, "No active session found for photo request");
5429
+ const pending = this.completePhotoRequest(requestId);
5430
+ if (!pending) {
5431
+ this.logger.warn({ requestId, pendingCount: this.pendingPhotoRequests.size }, "\uD83D\uDCF8 No pending request found for photo (may have timed out or session ended)");
5428
5432
  return res.status(404).json({
5429
5433
  success: false,
5430
- error: "No active session found for this photo request"
5434
+ error: "No pending request found for this photo (may have timed out or session ended)"
5431
5435
  });
5432
5436
  }
5433
5437
  if (type === "photo_error" || success === false) {
5434
- const errorResponse = {
5435
- requestId,
5436
- success: false,
5437
- error: {
5438
- code: errorCode || "UNKNOWN_ERROR",
5439
- message: errorMessage || "Unknown error occurred"
5440
- }
5441
- };
5442
- session.camera.handlePhotoError(errorResponse);
5438
+ const errorMsg = errorMessage || "Unknown error occurred";
5439
+ this.logger.info({ requestId, errorCode, errorMessage: errorMsg }, "\uD83D\uDCF8 Photo error received");
5440
+ pending.reject(new Error(`Photo capture failed: ${errorMsg} (code: ${errorCode || "UNKNOWN_ERROR"})`));
5443
5441
  return res.json({
5444
5442
  success: true,
5445
5443
  requestId,
@@ -5448,6 +5446,7 @@ class AppServer {
5448
5446
  }
5449
5447
  if (!photoFile) {
5450
5448
  this.logger.error({ requestId }, "No photo file in successful upload");
5449
+ pending.reject(new Error("No photo file provided for successful upload"));
5451
5450
  return res.status(400).json({
5452
5451
  success: false,
5453
5452
  error: "No photo file provided for successful upload"
@@ -5461,7 +5460,8 @@ class AppServer {
5461
5460
  size: photoFile.size,
5462
5461
  timestamp: new Date
5463
5462
  };
5464
- session.camera.handlePhotoReceived(photoData);
5463
+ this.logger.info({ requestId, size: photoFile.size, mimeType: photoFile.mimetype }, "\uD83D\uDCF8 Photo received successfully, resolving promise");
5464
+ pending.resolve(photoData);
5465
5465
  res.json({
5466
5466
  success: true,
5467
5467
  requestId,
@@ -5476,6 +5476,53 @@ class AppServer {
5476
5476
  }
5477
5477
  });
5478
5478
  }
5479
+ registerPhotoRequest(requestId, request) {
5480
+ const timeoutMs = 30000;
5481
+ const timeoutId = setTimeout(() => {
5482
+ const pending = this.pendingPhotoRequests.get(requestId);
5483
+ if (pending) {
5484
+ pending.reject(new Error("Photo request timed out"));
5485
+ this.pendingPhotoRequests.delete(requestId);
5486
+ this.logger.warn({ requestId }, "\uD83D\uDCF8 Photo request timed out");
5487
+ }
5488
+ }, timeoutMs);
5489
+ this.pendingPhotoRequests.set(requestId, {
5490
+ ...request,
5491
+ timeoutId
5492
+ });
5493
+ this.logger.debug({ requestId, userId: request.userId, sessionId: request.sessionId }, "\uD83D\uDCF8 Photo request registered at AppServer level");
5494
+ }
5495
+ getPhotoRequest(requestId) {
5496
+ return this.pendingPhotoRequests.get(requestId);
5497
+ }
5498
+ completePhotoRequest(requestId) {
5499
+ const pending = this.pendingPhotoRequests.get(requestId);
5500
+ if (pending) {
5501
+ if (pending.timeoutId) {
5502
+ clearTimeout(pending.timeoutId);
5503
+ }
5504
+ this.pendingPhotoRequests.delete(requestId);
5505
+ this.logger.debug({ requestId }, "\uD83D\uDCF8 Photo request completed");
5506
+ }
5507
+ return pending;
5508
+ }
5509
+ cleanupPhotoRequestsForSession(sessionId) {
5510
+ let cleanedCount = 0;
5511
+ for (const [requestId, pending] of this.pendingPhotoRequests) {
5512
+ if (pending.sessionId === sessionId) {
5513
+ if (pending.timeoutId) {
5514
+ clearTimeout(pending.timeoutId);
5515
+ }
5516
+ pending.reject(new Error("Session ended"));
5517
+ this.pendingPhotoRequests.delete(requestId);
5518
+ cleanedCount++;
5519
+ this.logger.debug({ requestId, sessionId }, "\uD83D\uDCF8 Photo request cleaned up (session ended)");
5520
+ }
5521
+ }
5522
+ if (cleanedCount > 0) {
5523
+ this.logger.info({ sessionId, cleanedCount }, "\uD83D\uDCF8 Cleaned up photo requests for ended session");
5524
+ }
5525
+ }
5479
5526
  setupMentraAuthRedirect() {
5480
5527
  this.app.get("/mentra-auth", (req, res) => {
5481
5528
  const authUrl = `https://account.mentra.glass/auth?packagename=${encodeURIComponent(this.config.packageName)}`;
@@ -5483,14 +5530,6 @@ class AppServer {
5483
5530
  res.redirect(302, authUrl);
5484
5531
  });
5485
5532
  }
5486
- findSessionByPhotoRequestId(requestId) {
5487
- for (const [_sessionId, session] of this.activeSessions) {
5488
- if (session.camera.hasPhotoPendingRequest(requestId)) {
5489
- return session;
5490
- }
5491
- }
5492
- return;
5493
- }
5494
5533
  }
5495
5534
 
5496
5535
  class TpaServer extends AppServer {
@@ -5511,6 +5550,8 @@ export {
5511
5550
  isValidLanguageCode,
5512
5551
  isVad,
5513
5552
  isUpdate,
5553
+ isUdpUnregister,
5554
+ isUdpRegister,
5514
5555
  isStreamStatusCheckResponse,
5515
5556
  isStreamCategory,
5516
5557
  isStopWebhookRequest,
@@ -5638,4 +5679,4 @@ export {
5638
5679
  AnimationUtils
5639
5680
  };
5640
5681
 
5641
- //# debugId=5178B6743C1469D964756E2164756E21
5682
+ //# debugId=82AABDD8D6AA68F464756E2164756E21