@mentra/sdk 2.1.26 → 2.1.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/dist/app/session/api-client.d.ts.map +1 -1
  2. package/dist/app/session/dashboard.d.ts +5 -8
  3. package/dist/app/session/dashboard.d.ts.map +1 -1
  4. package/dist/app/session/events.d.ts +5 -2
  5. package/dist/app/session/events.d.ts.map +1 -1
  6. package/dist/app/session/index.d.ts +62 -3
  7. package/dist/app/session/index.d.ts.map +1 -1
  8. package/dist/app/session/modules/audio.d.ts +33 -4
  9. package/dist/app/session/modules/audio.d.ts.map +1 -1
  10. package/dist/app/session/modules/camera-managed-extension.d.ts +2 -3
  11. package/dist/app/session/modules/camera-managed-extension.d.ts.map +1 -1
  12. package/dist/app/session/modules/camera.d.ts +5 -5
  13. package/dist/app/session/modules/camera.d.ts.map +1 -1
  14. package/dist/app/session/modules/led.d.ts +141 -0
  15. package/dist/app/session/modules/led.d.ts.map +1 -0
  16. package/dist/app/session/modules/location.d.ts +1 -2
  17. package/dist/app/session/modules/location.d.ts.map +1 -1
  18. package/dist/app/session/modules/simple-storage.d.ts.map +1 -1
  19. package/dist/constants/log-messages/color.d.ts +5 -0
  20. package/dist/constants/log-messages/color.d.ts.map +1 -0
  21. package/dist/constants/log-messages/logos.d.ts +4 -0
  22. package/dist/constants/log-messages/logos.d.ts.map +1 -0
  23. package/dist/constants/{messages.d.ts → log-messages/updates.d.ts} +2 -3
  24. package/dist/constants/log-messages/updates.d.ts.map +1 -0
  25. package/dist/constants/log-messages/warning.d.ts +8 -0
  26. package/dist/constants/log-messages/warning.d.ts.map +1 -0
  27. package/dist/index.d.ts +7 -7
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +5347 -112
  30. package/dist/index.js.map +45 -0
  31. package/dist/logging/logger.d.ts +2 -1
  32. package/dist/logging/logger.d.ts.map +1 -1
  33. package/dist/types/capabilities.d.ts +3 -0
  34. package/dist/types/capabilities.d.ts.map +1 -1
  35. package/dist/types/index.d.ts +4 -14
  36. package/dist/types/index.d.ts.map +1 -1
  37. package/dist/types/message-types.d.ts +12 -3
  38. package/dist/types/message-types.d.ts.map +1 -1
  39. package/dist/types/messages/app-to-cloud.d.ts +48 -2
  40. package/dist/types/messages/app-to-cloud.d.ts.map +1 -1
  41. package/dist/types/messages/cloud-to-app.d.ts +25 -6
  42. package/dist/types/messages/cloud-to-app.d.ts.map +1 -1
  43. package/dist/types/messages/cloud-to-glasses.d.ts +43 -1
  44. package/dist/types/messages/cloud-to-glasses.d.ts.map +1 -1
  45. package/dist/types/messages/glasses-to-cloud.d.ts +32 -1
  46. package/dist/types/messages/glasses-to-cloud.d.ts.map +1 -1
  47. package/dist/types/rtmp-stream.d.ts +1 -1
  48. package/dist/types/rtmp-stream.d.ts.map +1 -1
  49. package/dist/types/streams.d.ts +28 -1
  50. package/dist/types/streams.d.ts.map +1 -1
  51. package/dist/utils/permissions-utils.d.ts +8 -0
  52. package/dist/utils/permissions-utils.d.ts.map +1 -0
  53. package/package.json +12 -3
  54. package/dist/app/index.js +0 -24
  55. package/dist/app/server/index.js +0 -658
  56. package/dist/app/session/api-client.js +0 -101
  57. package/dist/app/session/dashboard.js +0 -149
  58. package/dist/app/session/events.js +0 -305
  59. package/dist/app/session/index.js +0 -1571
  60. package/dist/app/session/layouts.js +0 -372
  61. package/dist/app/session/modules/audio.js +0 -321
  62. package/dist/app/session/modules/camera-managed-extension.js +0 -310
  63. package/dist/app/session/modules/camera.js +0 -603
  64. package/dist/app/session/modules/index.js +0 -19
  65. package/dist/app/session/modules/location.js +0 -58
  66. package/dist/app/session/modules/simple-storage.js +0 -232
  67. package/dist/app/session/settings.js +0 -358
  68. package/dist/app/token/index.js +0 -22
  69. package/dist/app/token/utils.js +0 -144
  70. package/dist/app/webview/index.js +0 -382
  71. package/dist/constants/index.js +0 -16
  72. package/dist/constants/messages.d.ts.map +0 -1
  73. package/dist/constants/messages.js +0 -57
  74. package/dist/examples/managed-rtmp-streaming-example.js +0 -158
  75. package/dist/examples/managed-rtmp-streaming-with-restream-example.js +0 -124
  76. package/dist/examples/rtmp-streaming-example.js +0 -102
  77. package/dist/logging/logger.js +0 -79
  78. package/dist/types/capabilities.js +0 -9
  79. package/dist/types/dashboard/index.js +0 -12
  80. package/dist/types/enums.js +0 -75
  81. package/dist/types/index.js +0 -101
  82. package/dist/types/layouts.js +0 -3
  83. package/dist/types/message-types.js +0 -207
  84. package/dist/types/messages/app-to-cloud.js +0 -95
  85. package/dist/types/messages/base.js +0 -3
  86. package/dist/types/messages/cloud-to-app.js +0 -78
  87. package/dist/types/messages/cloud-to-glasses.js +0 -68
  88. package/dist/types/messages/glasses-to-cloud.js +0 -139
  89. package/dist/types/models.js +0 -101
  90. package/dist/types/photo-data.js +0 -2
  91. package/dist/types/rtmp-stream.js +0 -3
  92. package/dist/types/streams.js +0 -306
  93. package/dist/types/token.js +0 -7
  94. package/dist/types/webhooks.js +0 -28
  95. package/dist/utils/animation-utils.js +0 -340
  96. package/dist/utils/bitmap-utils.js +0 -475
  97. package/dist/utils/resource-tracker.js +0 -153
@@ -1,658 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.TpaServer = exports.AppServer = exports.GIVE_APP_CONTROL_OF_TOOL_RESPONSE = void 0;
7
- /**
8
- * 🚀 App Server Module
9
- *
10
- * Creates and manages a server for Apps in the MentraOS ecosystem.
11
- * Handles webhook endpoints, session management, and cleanup.
12
- */
13
- const express_1 = __importDefault(require("express"));
14
- const path_1 = __importDefault(require("path"));
15
- const fs_1 = __importDefault(require("fs"));
16
- const index_1 = require("../session/index");
17
- const webview_1 = require("../webview");
18
- const messages_1 = require("../../constants/messages");
19
- const types_1 = require("../../types");
20
- const logger_1 = require("../../logging/logger");
21
- const axios_1 = __importDefault(require("axios"));
22
- exports.GIVE_APP_CONTROL_OF_TOOL_RESPONSE = "GIVE_APP_CONTROL_OF_TOOL_RESPONSE";
23
- /**
24
- * 🎯 App Server Implementation
25
- *
26
- * Base class for creating App servers. Handles:
27
- * - 🔄 Session lifecycle management
28
- * - 📡 Webhook endpoints for MentraOS Cloud
29
- * - 📂 Static file serving
30
- * - ❤️ Health checks
31
- * - 🧹 Cleanup on shutdown
32
- *
33
- * @example
34
- * ```typescript
35
- * class MyAppServer extends AppServer {
36
- * protected async onSession(session: AppSession, sessionId: string, userId: string) {
37
- * // Handle new user sessions here
38
- * session.events.onTranscription((data) => {
39
- * session.layouts.showTextWall(data.text);
40
- * });
41
- * }
42
- * }
43
- *
44
- * const server = new MyAppServer({
45
- * packageName: 'org.example.myapp',
46
- * apiKey: 'your_api_key',
47
- * publicDir: "/public",
48
- * });
49
- *
50
- * await server.start();
51
- * ```
52
- */
53
- class AppServer {
54
- constructor(config) {
55
- this.config = config;
56
- /** Map of active user sessions by sessionId */
57
- this.activeSessions = new Map();
58
- /** Map of active user sessions by userId */
59
- this.activeSessionsByUserId = new Map();
60
- /** Array of cleanup handlers to run on shutdown */
61
- this.cleanupHandlers = [];
62
- /** App instructions string shown to the user */
63
- this.appInstructions = null;
64
- // Set defaults and merge with provided config
65
- this.config = {
66
- port: 7010,
67
- webhookPath: "/webhook",
68
- publicDir: false,
69
- healthCheck: true,
70
- ...config,
71
- };
72
- this.logger = logger_1.logger.child({
73
- app: this.config.packageName,
74
- packageName: this.config.packageName,
75
- service: "app-server",
76
- });
77
- // Initialize Express app
78
- this.app = (0, express_1.default)();
79
- this.app.use(express_1.default.json());
80
- const cookieParser = require("cookie-parser");
81
- this.app.use(cookieParser(this.config.cookieSecret ||
82
- `AOS_${this.config.packageName}_${this.config.apiKey.substring(0, 8)}`));
83
- // Apply authentication middleware
84
- this.app.use((0, webview_1.createAuthMiddleware)({
85
- apiKey: this.config.apiKey,
86
- packageName: this.config.packageName,
87
- getAppSessionForUser: (userId) => {
88
- return this.activeSessionsByUserId.get(userId) || null;
89
- },
90
- cookieSecret: this.config.cookieSecret ||
91
- `AOS_${this.config.packageName}_${this.config.apiKey.substring(0, 8)}`,
92
- }));
93
- this.appInstructions = config.appInstructions || null;
94
- // Setup server features
95
- this.setupWebhook();
96
- this.setupSettingsEndpoint();
97
- this.setupHealthCheck();
98
- this.setupToolCallEndpoint();
99
- this.setupPhotoUploadEndpoint();
100
- this.setupMentraAuthRedirect();
101
- this.setupPublicDir();
102
- this.setupShutdown();
103
- }
104
- // Expose Express app for custom routes.
105
- // This is useful for adding custom API routes or middleware.
106
- getExpressApp() {
107
- return this.app;
108
- }
109
- /**
110
- * 👥 Session Handler
111
- * Override this method to handle new App sessions.
112
- * This is where you implement your app's core functionality.
113
- *
114
- * @param session - App session instance for the user
115
- * @param sessionId - Unique identifier for this session
116
- * @param userId - User's identifier
117
- */
118
- async onSession(session, sessionId, userId) {
119
- this.logger.info(`🚀 Starting new session handling for session ${sessionId} and user ${userId}`);
120
- // Core session handling logic (onboarding removed)
121
- this.logger.info(`✅ Session handling completed for session ${sessionId} and user ${userId}`);
122
- }
123
- /**
124
- * 👥 Stop Handler
125
- * Override this method to handle stop requests.
126
- * This is where you can clean up resources when a session is stopped.
127
- *
128
- * @param sessionId - Unique identifier for this session
129
- * @param userId - User's identifier
130
- * @param reason - Reason for stopping
131
- */
132
- async onStop(sessionId, userId, reason) {
133
- this.logger.debug(`Session ${sessionId} stopped for user ${userId}. Reason: ${reason}`);
134
- // Default implementation: close the session if it exists
135
- const session = this.activeSessions.get(sessionId);
136
- if (session) {
137
- session.disconnect();
138
- this.activeSessions.delete(sessionId);
139
- this.activeSessionsByUserId.delete(userId);
140
- }
141
- }
142
- /**
143
- * 🛠️ Tool Call Handler
144
- * Override this method to handle tool calls from MentraOS Cloud.
145
- * This is where you implement your app's tool functionality.
146
- *
147
- * @param toolCall - The tool call request containing tool details and parameters
148
- * @returns Optional string response that will be sent back to MentraOS Cloud
149
- */
150
- async onToolCall(toolCall) {
151
- this.logger.debug(`Tool call received: ${toolCall.toolId}`);
152
- this.logger.debug(`Parameters: ${JSON.stringify(toolCall.toolParameters)}`);
153
- return undefined;
154
- }
155
- /**
156
- * 🚀 Start the Server
157
- * Starts listening for incoming connections and webhook calls.
158
- *
159
- * @returns Promise that resolves when server is ready
160
- */
161
- start() {
162
- return new Promise((resolve) => {
163
- this.app.listen(this.config.port, async () => {
164
- this.logger.info(`🎯 App server running at http://localhost:${this.config.port}`);
165
- if (this.config.publicDir) {
166
- this.logger.info(`📂 Serving static files from ${this.config.publicDir}`);
167
- }
168
- // 🔑 Grab SDK version
169
- try {
170
- // Look for the actual installed @mentra/sdk package.json in node_modules
171
- const sdkPkgPath = path_1.default.resolve(process.cwd(), "node_modules/@mentra/sdk/package.json");
172
- let currentVersion = "unknown";
173
- if (fs_1.default.existsSync(sdkPkgPath)) {
174
- const sdkPkg = JSON.parse(fs_1.default.readFileSync(sdkPkgPath, "utf-8"));
175
- // Get the actual installed version
176
- currentVersion = sdkPkg.version || "not-found"; // located in the node module
177
- }
178
- else {
179
- this.logger.debug({ sdkPkgPath }, "No @mentra/sdk package.json found at path");
180
- }
181
- // this.logger.debug(`Developer is using SDK version: ${currentVersion}`);
182
- // Fetch latest SDK version from the API endpoint
183
- let latest = "2.1.25"; // fallback version
184
- try {
185
- const cloudHost = "api.mentra.glass";
186
- const response = await axios_1.default.get(`https://${cloudHost}/api/sdk/version`);
187
- if (response.data && response.data.success && response.data.data) {
188
- latest = response.data.data.latest; // Changed from "recommended" to "latest"
189
- }
190
- }
191
- catch (fetchError) {
192
- this.logger.debug({ fetchError }, "Failed to fetch latest SDK version from API, using fallback");
193
- }
194
- if (currentVersion === "not-found") {
195
- this.logger.warn(`⚠️ @mentra/sdk not found in your project dependencies. Please install it with: npm install @mentra/sdk`);
196
- }
197
- else if (latest && latest !== currentVersion) {
198
- this.logger.warn((0, messages_1.newSDKUpdate)(latest));
199
- }
200
- }
201
- catch (err) {
202
- this.logger.error(err, "Version check failed");
203
- }
204
- resolve();
205
- });
206
- });
207
- }
208
- /**
209
- * 🛑 Stop the Server
210
- * Gracefully shuts down the server and cleans up all sessions.
211
- */
212
- stop() {
213
- this.logger.info("\n🛑 Shutting down...");
214
- this.cleanup();
215
- process.exit(0);
216
- }
217
- /**
218
- * 🔐 Generate a App token for a user
219
- * This should be called when handling a session webhook request.
220
- *
221
- * @param userId - User identifier
222
- * @param sessionId - Session identifier
223
- * @param secretKey - Secret key for signing the token
224
- * @returns JWT token string
225
- */
226
- generateToken(userId, sessionId, secretKey) {
227
- const { createToken } = require("../token/utils");
228
- return createToken({
229
- userId,
230
- packageName: this.config.packageName,
231
- sessionId,
232
- }, { secretKey });
233
- }
234
- /**
235
- * 🧹 Add Cleanup Handler
236
- * Register a function to be called during server shutdown.
237
- *
238
- * @param handler - Function to call during cleanup
239
- */
240
- addCleanupHandler(handler) {
241
- this.cleanupHandlers.push(handler);
242
- }
243
- /**
244
- * 🎯 Setup Webhook Endpoint
245
- * Creates the webhook endpoint that MentraOS Cloud calls to start new sessions.
246
- */
247
- setupWebhook() {
248
- if (!this.config.webhookPath) {
249
- this.logger.error("❌ Webhook path not set");
250
- throw new Error("Webhook path not set");
251
- }
252
- this.app.post(this.config.webhookPath, async (req, res) => {
253
- try {
254
- const webhookRequest = req.body;
255
- // Handle session request
256
- if (webhookRequest.type === types_1.WebhookRequestType.SESSION_REQUEST) {
257
- await this.handleSessionRequest(webhookRequest, res);
258
- }
259
- // Handle stop request
260
- else if (webhookRequest.type === types_1.WebhookRequestType.STOP_REQUEST) {
261
- await this.handleStopRequest(webhookRequest, res);
262
- }
263
- // Unknown webhook type
264
- else {
265
- this.logger.error("❌ Unknown webhook request type");
266
- res.status(400).json({
267
- status: "error",
268
- message: "Unknown webhook request type",
269
- });
270
- }
271
- }
272
- catch (error) {
273
- this.logger.error(error, "❌ Error handling webhook: " + error.message);
274
- res.status(500).json({
275
- status: "error",
276
- message: "Error handling webhook: " + error.message,
277
- });
278
- }
279
- });
280
- }
281
- /**
282
- * 🛠️ Setup Tool Call Endpoint
283
- * Creates a /tool endpoint for handling tool calls from MentraOS Cloud.
284
- */
285
- setupToolCallEndpoint() {
286
- this.app.post("/tool", async (req, res) => {
287
- try {
288
- const toolCall = req.body;
289
- if (this.activeSessionsByUserId.has(toolCall.userId)) {
290
- toolCall.activeSession =
291
- this.activeSessionsByUserId.get(toolCall.userId) || null;
292
- }
293
- else {
294
- toolCall.activeSession = null;
295
- }
296
- this.logger.info({ body: req.body }, `🔧 Received tool call: ${toolCall.toolId}`);
297
- // Call the onToolCall handler and get the response
298
- const response = await this.onToolCall(toolCall);
299
- // Send back the response if one was provided
300
- if (response !== undefined) {
301
- res.json({ status: "success", reply: response });
302
- }
303
- else {
304
- res.json({ status: "success", reply: null });
305
- }
306
- }
307
- catch (error) {
308
- this.logger.error(error, "❌ Error handling tool call:");
309
- res.status(500).json({
310
- status: "error",
311
- message: error instanceof Error
312
- ? error.message
313
- : "Unknown error occurred calling tool",
314
- });
315
- }
316
- });
317
- this.app.get("/tool", async (req, res) => {
318
- res.json({ status: "success", reply: "Hello, world!" });
319
- });
320
- }
321
- /**
322
- * Handle a session request webhook
323
- */
324
- async handleSessionRequest(request, res) {
325
- const { sessionId, userId, mentraOSWebsocketUrl, augmentOSWebsocketUrl } = request;
326
- this.logger.info({ userId }, `🗣️ Received session request for user ${userId}, session ${sessionId}\n\n`);
327
- // Create new App session
328
- const session = new index_1.AppSession({
329
- packageName: this.config.packageName,
330
- apiKey: this.config.apiKey,
331
- mentraOSWebsocketUrl: mentraOSWebsocketUrl || augmentOSWebsocketUrl, // The websocket URL for the specific MentraOS server that this userSession is connecting to.
332
- appServer: this,
333
- userId,
334
- });
335
- // Setup session event handlers
336
- const cleanupDisconnect = session.events.onDisconnected((info) => {
337
- // Handle different disconnect info formats (string or object)
338
- if (typeof info === "string") {
339
- this.logger.info(`👋 Session ${sessionId} disconnected: ${info}`);
340
- }
341
- else {
342
- // It's an object with detailed disconnect information
343
- this.logger.info(`👋 Session ${sessionId} disconnected: ${info.message} (code: ${info.code}, reason: ${info.reason})`);
344
- // Check if this is a user session end event
345
- // This happens when the UserSession is disposed after 1 minute grace period
346
- if (info.sessionEnded === true) {
347
- this.logger.info(`🛑 User session ended for session ${sessionId}, calling onStop`);
348
- // Call onStop with session end reason
349
- // This allows apps to clean up resources when the user's session ends
350
- this.onStop(sessionId, userId, "User session ended").catch((error) => {
351
- this.logger.error(error, `❌ Error in onStop handler for session end:`);
352
- });
353
- }
354
- // Check if this is a permanent disconnection after exhausted reconnection attempts
355
- else if (info.permanent === true) {
356
- this.logger.info(`🛑 Permanent disconnection detected for session ${sessionId}, calling onStop`);
357
- // Keep track of the original session before removal
358
- // const session = this.activeSessions.get(sessionId);
359
- const _session = this.activeSessions.get(sessionId);
360
- // Call onStop with a reconnection failure reason
361
- this.onStop(sessionId, userId, `Connection permanently lost: ${info.reason}`).catch((error) => {
362
- this.logger.error(error, `❌ Error in onStop handler for permanent disconnection:`);
363
- });
364
- }
365
- }
366
- // Remove the session from active sessions in all cases
367
- this.activeSessions.delete(sessionId);
368
- this.activeSessionsByUserId.delete(userId);
369
- });
370
- const cleanupError = session.events.onError((error) => {
371
- this.logger.error(error, `❌ [Session ${sessionId}] Error:`);
372
- });
373
- // Start the session
374
- try {
375
- await session.connect(sessionId);
376
- this.activeSessions.set(sessionId, session);
377
- this.activeSessionsByUserId.set(userId, session);
378
- await this.onSession(session, sessionId, userId);
379
- res.status(200).json({ status: "success" });
380
- }
381
- catch (error) {
382
- this.logger.error(error, "❌ Failed to connect:");
383
- cleanupDisconnect();
384
- cleanupError();
385
- res.status(500).json({
386
- status: "error",
387
- message: "Failed to connect",
388
- });
389
- }
390
- }
391
- /**
392
- * Handle a stop request webhook
393
- */
394
- async handleStopRequest(request, res) {
395
- const { sessionId, userId, reason } = request;
396
- this.logger.info(`\n\n🛑 Received stop request for user ${userId}, session ${sessionId}, reason: ${reason}\n\n`);
397
- try {
398
- await this.onStop(sessionId, userId, reason);
399
- res.status(200).json({ status: "success" });
400
- }
401
- catch (error) {
402
- this.logger.error(error, "❌ Error handling stop request:");
403
- res.status(500).json({
404
- status: "error",
405
- message: "Failed to process stop request",
406
- });
407
- }
408
- }
409
- /**
410
- * ❤️ Setup Health Check Endpoint
411
- * Creates a /health endpoint for monitoring server status.
412
- */
413
- setupHealthCheck() {
414
- if (this.config.healthCheck) {
415
- this.app.get("/health", (req, res) => {
416
- res.json({
417
- status: "healthy",
418
- app: this.config.packageName,
419
- activeSessions: this.activeSessions.size,
420
- });
421
- });
422
- }
423
- }
424
- /**
425
- * ⚙️ Setup Settings Endpoint
426
- * Creates a /settings endpoint that the MentraOS Cloud can use to update settings.
427
- */
428
- setupSettingsEndpoint() {
429
- this.app.post("/settings", async (req, res) => {
430
- try {
431
- const { userIdForSettings, settings } = req.body;
432
- if (!userIdForSettings || !Array.isArray(settings)) {
433
- return res.status(400).json({
434
- status: "error",
435
- message: "Missing userId or settings array in request body",
436
- });
437
- }
438
- this.logger.info(`⚙️ Received settings update for user ${userIdForSettings}`);
439
- // Find all active sessions for this user
440
- const userSessions = [];
441
- // Look through all active sessions
442
- this.activeSessions.forEach((session, _sessionId) => {
443
- // Check if the session has this userId (not directly accessible)
444
- // We're relying on the webhook handler to have already verified this
445
- if (session.userId === userIdForSettings) {
446
- userSessions.push(session);
447
- }
448
- });
449
- if (userSessions.length === 0) {
450
- this.logger.warn(`⚠️ No active sessions found for user ${userIdForSettings}`);
451
- }
452
- else {
453
- this.logger.info(`🔄 Updating settings for ${userSessions.length} active sessions`);
454
- }
455
- // Update settings for all of the user's sessions
456
- for (const session of userSessions) {
457
- session.updateSettingsForTesting(settings);
458
- }
459
- // Allow subclasses to handle settings updates if they implement the method
460
- if (typeof this.onSettingsUpdate === "function") {
461
- await this.onSettingsUpdate(userIdForSettings, settings);
462
- }
463
- res.json({
464
- status: "success",
465
- message: "Settings updated successfully",
466
- sessionsUpdated: userSessions.length,
467
- });
468
- }
469
- catch (error) {
470
- this.logger.error(error, "❌ Error handling settings update:");
471
- res.status(500).json({
472
- status: "error",
473
- message: "Internal server error processing settings update",
474
- });
475
- }
476
- });
477
- }
478
- /**
479
- * 📂 Setup Static File Serving
480
- * Configures Express to serve static files from the specified directory.
481
- */
482
- setupPublicDir() {
483
- if (this.config.publicDir) {
484
- const publicPath = path_1.default.resolve(this.config.publicDir);
485
- this.app.use(express_1.default.static(publicPath));
486
- this.logger.info(`📂 Serving static files from ${publicPath}`);
487
- }
488
- }
489
- /**
490
- * 🛑 Setup Shutdown Handlers
491
- * Registers process signal handlers for graceful shutdown.
492
- */
493
- setupShutdown() {
494
- process.on("SIGTERM", () => this.stop());
495
- process.on("SIGINT", () => this.stop());
496
- }
497
- /**
498
- * 🧹 Cleanup
499
- * Closes all active sessions and runs cleanup handlers.
500
- */
501
- cleanup() {
502
- // Close all active sessions
503
- for (const [sessionId, session] of this.activeSessions) {
504
- this.logger.info(`👋 Closing session ${sessionId}`);
505
- session.disconnect();
506
- }
507
- this.activeSessions.clear();
508
- this.activeSessionsByUserId.clear();
509
- // Run cleanup handlers
510
- this.cleanupHandlers.forEach((handler) => handler());
511
- }
512
- /**
513
- * 🎯 Setup Photo Upload Endpoint
514
- * Creates a /photo-upload endpoint for receiving photos directly from ASG glasses
515
- */
516
- setupPhotoUploadEndpoint() {
517
- const multer = require("multer");
518
- // Configure multer for handling multipart form data
519
- const upload = multer({
520
- storage: multer.memoryStorage(),
521
- limits: {
522
- fileSize: 10 * 1024 * 1024, // 10MB limit
523
- },
524
- fileFilter: (req, file, cb) => {
525
- // Accept image files only
526
- if (file.mimetype && file.mimetype.startsWith("image/")) {
527
- cb(null, true);
528
- }
529
- else {
530
- cb(new Error("Only image files are allowed"), false);
531
- }
532
- },
533
- });
534
- this.app.post("/photo-upload", upload.single("photo"), async (req, res) => {
535
- try {
536
- const { requestId, type, success, errorCode, errorMessage } = req.body;
537
- const photoFile = req.file;
538
- console.log("Received photo response: ", req.body);
539
- this.logger.info({ requestId, type, success, errorCode }, `📸 Received photo response: ${requestId} (type: ${type})`);
540
- if (!requestId) {
541
- this.logger.error("No requestId in photo response");
542
- return res.status(400).json({
543
- success: false,
544
- error: "No requestId provided",
545
- });
546
- }
547
- // Find the corresponding session that made this photo request
548
- const session = this.findSessionByPhotoRequestId(requestId);
549
- if (!session) {
550
- this.logger.warn({ requestId }, "No active session found for photo request");
551
- return res.status(404).json({
552
- success: false,
553
- error: "No active session found for this photo request",
554
- });
555
- }
556
- // Handle error response (no photo file, but has error info)
557
- if (type === "photo_error" || !success) {
558
- // Create error response object
559
- const errorResponse = {
560
- requestId,
561
- success: false,
562
- error: {
563
- code: errorCode || "UNKNOWN_ERROR",
564
- message: errorMessage || "Unknown error occurred",
565
- },
566
- };
567
- // Deliver error to the session (logging happens in camera module)
568
- session.camera.handlePhotoError(errorResponse);
569
- // Respond to ASG client
570
- return res.json({
571
- success: true,
572
- requestId,
573
- message: "Photo error received successfully",
574
- });
575
- }
576
- // Handle successful photo upload
577
- if (!photoFile) {
578
- this.logger.error({ requestId }, "No photo file in successful upload");
579
- return res.status(400).json({
580
- success: false,
581
- error: "No photo file provided for successful upload",
582
- });
583
- }
584
- // Create photo data object
585
- const photoData = {
586
- buffer: photoFile.buffer,
587
- mimeType: photoFile.mimetype,
588
- filename: photoFile.originalname || "photo.jpg",
589
- requestId,
590
- size: photoFile.size,
591
- timestamp: new Date(),
592
- };
593
- // Deliver photo to the session
594
- session.camera.handlePhotoReceived(photoData);
595
- // Respond to ASG client
596
- res.json({
597
- success: true,
598
- requestId,
599
- message: "Photo received successfully",
600
- });
601
- }
602
- catch (error) {
603
- this.logger.error(error, "❌ Error handling photo response");
604
- res.status(500).json({
605
- success: false,
606
- error: "Internal server error processing photo response",
607
- });
608
- }
609
- });
610
- }
611
- /**
612
- * 🔐 Setup Mentra Auth Redirect Endpoint
613
- * Creates a /mentra-auth endpoint that redirects to the MentraOS OAuth flow.
614
- */
615
- setupMentraAuthRedirect() {
616
- this.app.get("/mentra-auth", (req, res) => {
617
- // Redirect to the account.mentra.glass OAuth flow with the app's package name
618
- const authUrl = `https://account.mentra.glass/auth?packagename=${encodeURIComponent(this.config.packageName)}`;
619
- this.logger.info(`🔐 Redirecting to MentraOS OAuth flow: ${authUrl}`);
620
- res.redirect(302, authUrl);
621
- });
622
- }
623
- /**
624
- * Find session that has a pending photo request for the given requestId
625
- */
626
- findSessionByPhotoRequestId(requestId) {
627
- for (const [_sessionId, session] of this.activeSessions) {
628
- if (session.camera.hasPhotoPendingRequest(requestId)) {
629
- return session;
630
- }
631
- }
632
- return undefined;
633
- }
634
- }
635
- exports.AppServer = AppServer;
636
- /**
637
- * @deprecated Use `AppServer` instead. `TpaServer` is deprecated and will be removed in a future version.
638
- * This is an alias for backward compatibility only.
639
- *
640
- * @example
641
- * ```typescript
642
- * // ❌ Deprecated - Don't use this
643
- * class MyServer extends TpaServer { ... }
644
- *
645
- * // ✅ Use this instead
646
- * class MyServer extends AppServer { ... }
647
- * ```
648
- */
649
- class TpaServer extends AppServer {
650
- constructor(config) {
651
- super(config);
652
- // Emit a deprecation warning to help developers migrate
653
- console.warn("⚠️ DEPRECATION WARNING: TpaServer is deprecated and will be removed in a future version. " +
654
- "Please use AppServer instead. " +
655
- 'Simply replace "TpaServer" with "AppServer" in your code.');
656
- }
657
- }
658
- exports.TpaServer = TpaServer;