@mentra/sdk 3.0.0-hono.0 → 3.0.0-hono.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app/server/index.d.ts +31 -4
- package/dist/app/server/index.d.ts.map +1 -1
- package/dist/app/session/index.d.ts +12 -3
- package/dist/app/session/index.d.ts.map +1 -1
- package/dist/app/session/modules/camera.d.ts +8 -40
- package/dist/app/session/modules/camera.d.ts.map +1 -1
- package/dist/app/webview/index.d.ts +41 -0
- package/dist/app/webview/index.d.ts.map +1 -1
- package/dist/index.d.ts +14 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +174 -112
- package/dist/index.js.map +9 -9
- package/dist/types/index.d.ts +5 -3
- package/dist/types/index.d.ts.map +1 -1
- package/node_modules/@mentra/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1333,6 +1333,9 @@ class AnimationUtils {
|
|
|
1333
1333
|
return frames;
|
|
1334
1334
|
}
|
|
1335
1335
|
}
|
|
1336
|
+
// src/types/index.ts
|
|
1337
|
+
init_message_types();
|
|
1338
|
+
|
|
1336
1339
|
// src/types/messages/cloud-to-app.ts
|
|
1337
1340
|
init_message_types();
|
|
1338
1341
|
init_streams();
|
|
@@ -1385,7 +1388,7 @@ function isAudioPlayResponse2(message) {
|
|
|
1385
1388
|
return message.type === "audio_play_response" /* AUDIO_PLAY_RESPONSE */;
|
|
1386
1389
|
}
|
|
1387
1390
|
|
|
1388
|
-
// src/index.ts
|
|
1391
|
+
// src/types/index.ts
|
|
1389
1392
|
init_streams();
|
|
1390
1393
|
init_dashboard();
|
|
1391
1394
|
|
|
@@ -1513,11 +1516,15 @@ function isSessionWebhookRequest(request) {
|
|
|
1513
1516
|
function isStopWebhookRequest(request) {
|
|
1514
1517
|
return request.type === "stop_request" /* STOP_REQUEST */;
|
|
1515
1518
|
}
|
|
1519
|
+
// src/index.ts
|
|
1520
|
+
init_streams();
|
|
1521
|
+
init_dashboard();
|
|
1522
|
+
|
|
1516
1523
|
// src/app/server/index.ts
|
|
1517
1524
|
import fs2 from "fs";
|
|
1518
1525
|
import path2 from "path";
|
|
1519
1526
|
import axios3 from "axios";
|
|
1520
|
-
import { Hono } from "hono";
|
|
1527
|
+
import { Hono as Hono2 } from "hono";
|
|
1521
1528
|
import { serveStatic } from "hono/bun";
|
|
1522
1529
|
|
|
1523
1530
|
// src/constants/log-messages/updates.ts
|
|
@@ -1591,7 +1598,7 @@ var BETTERSTACK_ENDPOINT = process.env.BETTERSTACK_ENDPOINT || "https://s1311181
|
|
|
1591
1598
|
var NODE_ENV = "isaiah";
|
|
1592
1599
|
var PORTER_APP_NAME = process.env.PORTER_APP_NAME || "cloud-local";
|
|
1593
1600
|
var LOG_LEVEL = NODE_ENV === "production" ? "info" : "debug";
|
|
1594
|
-
var
|
|
1601
|
+
var streams2 = [];
|
|
1595
1602
|
var prettyTransport = pino.transport({
|
|
1596
1603
|
target: "pino-pretty",
|
|
1597
1604
|
options: {
|
|
@@ -1603,7 +1610,7 @@ var prettyTransport = pino.transport({
|
|
|
1603
1610
|
customPrettifiers: {}
|
|
1604
1611
|
}
|
|
1605
1612
|
});
|
|
1606
|
-
|
|
1613
|
+
streams2.push({
|
|
1607
1614
|
stream: prettyTransport,
|
|
1608
1615
|
level: LOG_LEVEL
|
|
1609
1616
|
});
|
|
@@ -1616,7 +1623,7 @@ if (BETTERSTACK_SOURCE_TOKEN) {
|
|
|
1616
1623
|
options: { endpoint: BETTERSTACK_ENDPOINT }
|
|
1617
1624
|
}
|
|
1618
1625
|
});
|
|
1619
|
-
|
|
1626
|
+
streams2.push({
|
|
1620
1627
|
stream: betterStackTransport,
|
|
1621
1628
|
level: LOG_LEVEL
|
|
1622
1629
|
});
|
|
@@ -1624,7 +1631,7 @@ if (BETTERSTACK_SOURCE_TOKEN) {
|
|
|
1624
1631
|
console.warn("BetterStack logging unavailable:", error instanceof Error ? error.message : error);
|
|
1625
1632
|
}
|
|
1626
1633
|
}
|
|
1627
|
-
var multistream = pino.multistream(
|
|
1634
|
+
var multistream = pino.multistream(streams2);
|
|
1628
1635
|
var baseLoggerOptions = {
|
|
1629
1636
|
level: LOG_LEVEL,
|
|
1630
1637
|
base: {
|
|
@@ -1635,11 +1642,6 @@ var baseLoggerOptions = {
|
|
|
1635
1642
|
};
|
|
1636
1643
|
var logger = pino(baseLoggerOptions, multistream);
|
|
1637
1644
|
|
|
1638
|
-
// src/types/index.ts
|
|
1639
|
-
init_message_types();
|
|
1640
|
-
init_streams();
|
|
1641
|
-
init_dashboard();
|
|
1642
|
-
|
|
1643
1645
|
// src/app/session/index.ts
|
|
1644
1646
|
import { WebSocket } from "ws";
|
|
1645
1647
|
|
|
@@ -2689,7 +2691,6 @@ class CameraModule {
|
|
|
2689
2691
|
packageName;
|
|
2690
2692
|
sessionId;
|
|
2691
2693
|
logger;
|
|
2692
|
-
pendingPhotoRequests = new Map;
|
|
2693
2694
|
isStreaming = false;
|
|
2694
2695
|
currentStreamUrl;
|
|
2695
2696
|
currentStreamState;
|
|
@@ -2706,9 +2707,13 @@ class CameraModule {
|
|
|
2706
2707
|
const baseUrl = this.session?.getHttpsServerUrl?.() || "";
|
|
2707
2708
|
cameraWarnLog(baseUrl, this.packageName, "requestPhoto");
|
|
2708
2709
|
try {
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2710
|
+
const requestId = this.session.appServer.registerPhotoRequest({
|
|
2711
|
+
userId: this.session.userId,
|
|
2712
|
+
sessionId: this.sessionId,
|
|
2713
|
+
session: this.session,
|
|
2714
|
+
resolve,
|
|
2715
|
+
reject
|
|
2716
|
+
});
|
|
2712
2717
|
const message = {
|
|
2713
2718
|
type: "photo_request" /* PHOTO_REQUEST */,
|
|
2714
2719
|
packageName: this.packageName,
|
|
@@ -2729,7 +2734,7 @@ class CameraModule {
|
|
|
2729
2734
|
hasAuthToken: !!options?.authToken
|
|
2730
2735
|
}, `\uD83D\uDCF8 Photo request sent`);
|
|
2731
2736
|
if (options?.customWebhookUrl) {
|
|
2732
|
-
this.logger.info({ requestId, customWebhookUrl: options.customWebhookUrl }, `\uD83D\uDCF8 Using custom webhook URL - resolving promise immediately
|
|
2737
|
+
this.logger.info({ requestId, customWebhookUrl: options.customWebhookUrl }, `\uD83D\uDCF8 Using custom webhook URL - resolving promise immediately`);
|
|
2733
2738
|
const mockPhotoData = {
|
|
2734
2739
|
buffer: Buffer.from([]),
|
|
2735
2740
|
mimeType: "image/jpeg",
|
|
@@ -2738,83 +2743,37 @@ class CameraModule {
|
|
|
2738
2743
|
size: 0,
|
|
2739
2744
|
timestamp: new Date
|
|
2740
2745
|
};
|
|
2741
|
-
this.
|
|
2746
|
+
this.session.appServer.completePhotoRequest(requestId);
|
|
2742
2747
|
resolve(mockPhotoData);
|
|
2743
2748
|
return;
|
|
2744
2749
|
}
|
|
2745
|
-
const timeoutMs = 30000;
|
|
2746
|
-
if (this.session && this.session.resources) {
|
|
2747
|
-
this.session.resources.setTimeout(() => {
|
|
2748
|
-
if (this.pendingPhotoRequests.has(requestId)) {
|
|
2749
|
-
this.pendingPhotoRequests.get(requestId).reject("Photo request timed out");
|
|
2750
|
-
this.pendingPhotoRequests.delete(requestId);
|
|
2751
|
-
this.logger.warn({ requestId }, `\uD83D\uDCF8 Photo request timed out`);
|
|
2752
|
-
}
|
|
2753
|
-
}, timeoutMs);
|
|
2754
|
-
} else {
|
|
2755
|
-
setTimeout(() => {
|
|
2756
|
-
if (this.pendingPhotoRequests.has(requestId)) {
|
|
2757
|
-
this.pendingPhotoRequests.get(requestId).reject("Photo request timed out");
|
|
2758
|
-
this.pendingPhotoRequests.delete(requestId);
|
|
2759
|
-
this.logger.warn({ requestId }, `\uD83D\uDCF8 Photo request timed out`);
|
|
2760
|
-
}
|
|
2761
|
-
}, timeoutMs);
|
|
2762
|
-
}
|
|
2763
2750
|
} catch (error) {
|
|
2764
2751
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2765
|
-
reject(`Failed to request photo: ${errorMessage}`);
|
|
2752
|
+
reject(new Error(`Failed to request photo: ${errorMessage}`));
|
|
2766
2753
|
}
|
|
2767
2754
|
});
|
|
2768
2755
|
}
|
|
2769
|
-
handlePhotoReceived(photoData) {
|
|
2770
|
-
const { requestId } = photoData;
|
|
2771
|
-
const pendingRequest = this.pendingPhotoRequests.get(requestId);
|
|
2772
|
-
if (pendingRequest) {
|
|
2773
|
-
this.logger.info({ requestId }, `\uD83D\uDCF8 Photo received for request ${requestId}`);
|
|
2774
|
-
pendingRequest.resolve(photoData);
|
|
2775
|
-
this.pendingPhotoRequests.delete(requestId);
|
|
2776
|
-
} else {
|
|
2777
|
-
this.logger.warn({ requestId }, `\uD83D\uDCF8 Received photo for unknown request ID: ${requestId}`);
|
|
2778
|
-
}
|
|
2779
|
-
}
|
|
2780
|
-
handlePhotoError(errorResponse) {
|
|
2781
|
-
const { requestId, error } = errorResponse;
|
|
2782
|
-
const pendingRequest = this.pendingPhotoRequests.get(requestId);
|
|
2783
|
-
if (pendingRequest) {
|
|
2784
|
-
this.logger.error({ requestId, errorCode: error.code, errorMessage: error.message }, `\uD83D\uDCF8 Photo capture failed: ${error.code} - ${error.message}`);
|
|
2785
|
-
pendingRequest.reject(`${error.code}: ${error.message}`);
|
|
2786
|
-
this.pendingPhotoRequests.delete(requestId);
|
|
2787
|
-
} else {
|
|
2788
|
-
this.logger.warn({ requestId, errorCode: error.code, errorMessage: error.message }, `\uD83D\uDCF8 Received photo error for unknown request ID: ${requestId}`);
|
|
2789
|
-
}
|
|
2790
|
-
}
|
|
2791
2756
|
hasPhotoPendingRequest(requestId) {
|
|
2792
|
-
return
|
|
2757
|
+
return false;
|
|
2793
2758
|
}
|
|
2794
2759
|
getPhotoPendingRequestCount() {
|
|
2795
|
-
return
|
|
2760
|
+
return 0;
|
|
2796
2761
|
}
|
|
2797
2762
|
getPhotoPendingRequestIds() {
|
|
2798
|
-
return
|
|
2763
|
+
return [];
|
|
2799
2764
|
}
|
|
2800
2765
|
cancelPhotoRequest(requestId) {
|
|
2801
|
-
const
|
|
2802
|
-
if (
|
|
2803
|
-
|
|
2804
|
-
this.pendingPhotoRequests.delete(requestId);
|
|
2766
|
+
const pending = this.session.appServer.completePhotoRequest(requestId);
|
|
2767
|
+
if (pending) {
|
|
2768
|
+
pending.reject(new Error("Photo request cancelled"));
|
|
2805
2769
|
this.logger.info({ requestId }, `\uD83D\uDCF8 Photo request cancelled`);
|
|
2806
2770
|
return true;
|
|
2807
2771
|
}
|
|
2808
2772
|
return false;
|
|
2809
2773
|
}
|
|
2810
2774
|
cancelAllPhotoRequests() {
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
reject("Photo request cancelled - session cleanup");
|
|
2814
|
-
this.logger.info({ requestId }, `\uD83D\uDCF8 Photo request cancelled during cleanup`);
|
|
2815
|
-
}
|
|
2816
|
-
this.pendingPhotoRequests.clear();
|
|
2817
|
-
return count;
|
|
2775
|
+
this.session.appServer.cleanupPhotoRequestsForSession(this.sessionId);
|
|
2776
|
+
return 0;
|
|
2818
2777
|
}
|
|
2819
2778
|
async startStream(options) {
|
|
2820
2779
|
this.logger.info({ rtmpUrl: options.rtmpUrl }, `\uD83D\uDCF9 RTMP stream request starting`);
|
|
@@ -4834,6 +4793,7 @@ class TpaSession extends AppSession {
|
|
|
4834
4793
|
// src/app/webview/index.ts
|
|
4835
4794
|
import * as crypto from "crypto";
|
|
4836
4795
|
import axios2 from "axios";
|
|
4796
|
+
import { Hono } from "hono";
|
|
4837
4797
|
import { getCookie, setCookie, deleteCookie } from "hono/cookie";
|
|
4838
4798
|
import { KEYUTIL, KJUR } from "jsrsasign";
|
|
4839
4799
|
var userTokenPublicKey = process.env.MENTRAOS_CLOUD_USER_TOKEN_PUBLIC_KEY || `-----BEGIN PUBLIC KEY-----
|
|
@@ -5055,14 +5015,80 @@ function createAuthMiddleware(options) {
|
|
|
5055
5015
|
await next();
|
|
5056
5016
|
};
|
|
5057
5017
|
}
|
|
5018
|
+
function generateFrontendToken(userId, apiKey) {
|
|
5019
|
+
const hashedApiKey = crypto.createHash("sha256").update(apiKey).digest("hex");
|
|
5020
|
+
const hash = crypto.createHash("sha256").update(userId).update(hashedApiKey).digest("hex");
|
|
5021
|
+
return `${userId}:${hash}`;
|
|
5022
|
+
}
|
|
5023
|
+
function createMentraAuthRoutes(options) {
|
|
5024
|
+
const {
|
|
5025
|
+
apiKey,
|
|
5026
|
+
packageName,
|
|
5027
|
+
cookieSecret,
|
|
5028
|
+
cookieOptions = {
|
|
5029
|
+
httpOnly: true,
|
|
5030
|
+
secure: false,
|
|
5031
|
+
maxAge: 30 * 24 * 60 * 60,
|
|
5032
|
+
sameSite: "Lax",
|
|
5033
|
+
path: "/"
|
|
5034
|
+
}
|
|
5035
|
+
} = options;
|
|
5036
|
+
if (!apiKey) {
|
|
5037
|
+
throw new Error("API Key is required for Mentra auth routes.");
|
|
5038
|
+
}
|
|
5039
|
+
if (!cookieSecret || typeof cookieSecret !== "string" || cookieSecret.length < 8) {
|
|
5040
|
+
throw new Error("A strong cookieSecret (at least 8 characters) is required.");
|
|
5041
|
+
}
|
|
5042
|
+
const authRouter = new Hono;
|
|
5043
|
+
const cookieName = `${packageName}-session`;
|
|
5044
|
+
authRouter.get("/init", async (c) => {
|
|
5045
|
+
const tempToken = c.req.query("aos_temp_token");
|
|
5046
|
+
const signedUserToken = c.req.query("aos_signed_user_token");
|
|
5047
|
+
if (signedUserToken) {
|
|
5048
|
+
const userId = await verifySignedUserToken(signedUserToken);
|
|
5049
|
+
if (userId) {
|
|
5050
|
+
const frontendToken = generateFrontendToken(userId, apiKey);
|
|
5051
|
+
const signedSession = signSession(userId, cookieSecret);
|
|
5052
|
+
setCookie(c, cookieName, signedSession, cookieOptions);
|
|
5053
|
+
console.log("[mentra/auth/init] User authenticated via signed user token:", userId);
|
|
5054
|
+
return c.json({ success: true, userId, frontendToken });
|
|
5055
|
+
}
|
|
5056
|
+
return c.json({ success: false, error: "Invalid signed user token" }, 401);
|
|
5057
|
+
}
|
|
5058
|
+
if (tempToken) {
|
|
5059
|
+
try {
|
|
5060
|
+
let cloudApiUrl = "https://api.mentra.glass";
|
|
5061
|
+
const cloudApiUrlFromQuery = c.req.query("cloudApiUrl");
|
|
5062
|
+
if (cloudApiUrlFromQuery) {
|
|
5063
|
+
const checksum = c.req.query("cloudApiUrlChecksum");
|
|
5064
|
+
if (checksum && validateCloudApiUrlChecksum(checksum, cloudApiUrlFromQuery, apiKey)) {
|
|
5065
|
+
cloudApiUrl = cloudApiUrlFromQuery;
|
|
5066
|
+
}
|
|
5067
|
+
}
|
|
5068
|
+
const { userId } = await exchangeToken(cloudApiUrl, tempToken, apiKey, packageName);
|
|
5069
|
+
const frontendToken = generateFrontendToken(userId, apiKey);
|
|
5070
|
+
const signedSession = signSession(userId, cookieSecret);
|
|
5071
|
+
setCookie(c, cookieName, signedSession, cookieOptions);
|
|
5072
|
+
console.log("[mentra/auth/init] User authenticated via temp token:", userId);
|
|
5073
|
+
return c.json({ success: true, userId, frontendToken });
|
|
5074
|
+
} catch (error) {
|
|
5075
|
+
console.error("[mentra/auth/init] Token exchange failed:", error);
|
|
5076
|
+
return c.json({ success: false, error: "Token exchange failed" }, 401);
|
|
5077
|
+
}
|
|
5078
|
+
}
|
|
5079
|
+
return c.json({ success: false, error: "No token provided" }, 400);
|
|
5080
|
+
});
|
|
5081
|
+
return authRouter;
|
|
5082
|
+
}
|
|
5058
5083
|
|
|
5059
5084
|
// src/app/server/index.ts
|
|
5060
5085
|
var GIVE_APP_CONTROL_OF_TOOL_RESPONSE = "GIVE_APP_CONTROL_OF_TOOL_RESPONSE";
|
|
5061
5086
|
|
|
5062
|
-
class AppServer extends
|
|
5087
|
+
class AppServer extends Hono2 {
|
|
5063
5088
|
config;
|
|
5064
5089
|
activeSessions = new Map;
|
|
5065
5090
|
activeSessionsByUserId = new Map;
|
|
5091
|
+
pendingPhotoRequests = new Map;
|
|
5066
5092
|
cleanupHandlers = [];
|
|
5067
5093
|
appInstructions = null;
|
|
5068
5094
|
logger;
|
|
@@ -5086,7 +5112,7 @@ class AppServer extends Hono {
|
|
|
5086
5112
|
getAppSessionForUser: (userId) => {
|
|
5087
5113
|
return this.activeSessionsByUserId.get(userId) || null;
|
|
5088
5114
|
},
|
|
5089
|
-
cookieSecret: this.config.cookieSecret ||
|
|
5115
|
+
cookieSecret: this.config.cookieSecret || this.config.apiKey
|
|
5090
5116
|
}));
|
|
5091
5117
|
this.appInstructions = config.appInstructions || null;
|
|
5092
5118
|
this.setupWebhook();
|
|
@@ -5176,6 +5202,44 @@ class AppServer extends Hono {
|
|
|
5176
5202
|
addCleanupHandler(handler) {
|
|
5177
5203
|
this.cleanupHandlers.push(handler);
|
|
5178
5204
|
}
|
|
5205
|
+
registerPhotoRequest(params) {
|
|
5206
|
+
const requestId = `photo_req_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
5207
|
+
const timeoutId = setTimeout(() => {
|
|
5208
|
+
const pending = this.pendingPhotoRequests.get(requestId);
|
|
5209
|
+
if (pending) {
|
|
5210
|
+
this.logger.warn({ requestId, userId: pending.userId }, "\uD83D\uDCF8 Photo request timed out at AppServer level");
|
|
5211
|
+
pending.reject(new Error("Photo request timed out"));
|
|
5212
|
+
this.pendingPhotoRequests.delete(requestId);
|
|
5213
|
+
}
|
|
5214
|
+
}, 30000);
|
|
5215
|
+
this.pendingPhotoRequests.set(requestId, {
|
|
5216
|
+
...params,
|
|
5217
|
+
timestamp: Date.now(),
|
|
5218
|
+
timeoutId
|
|
5219
|
+
});
|
|
5220
|
+
return requestId;
|
|
5221
|
+
}
|
|
5222
|
+
completePhotoRequest(requestId) {
|
|
5223
|
+
const pending = this.pendingPhotoRequests.get(requestId);
|
|
5224
|
+
if (pending) {
|
|
5225
|
+
if (pending.timeoutId) {
|
|
5226
|
+
clearTimeout(pending.timeoutId);
|
|
5227
|
+
}
|
|
5228
|
+
this.pendingPhotoRequests.delete(requestId);
|
|
5229
|
+
}
|
|
5230
|
+
return pending;
|
|
5231
|
+
}
|
|
5232
|
+
cleanupPhotoRequestsForSession(sessionId) {
|
|
5233
|
+
for (const [requestId, pending] of this.pendingPhotoRequests.entries()) {
|
|
5234
|
+
if (pending.sessionId === sessionId) {
|
|
5235
|
+
if (pending.timeoutId) {
|
|
5236
|
+
clearTimeout(pending.timeoutId);
|
|
5237
|
+
}
|
|
5238
|
+
pending.reject(new Error("Session ended while photo request was pending"));
|
|
5239
|
+
this.pendingPhotoRequests.delete(requestId);
|
|
5240
|
+
}
|
|
5241
|
+
}
|
|
5242
|
+
}
|
|
5179
5243
|
setupWebhook() {
|
|
5180
5244
|
const webhookPath = this.config.webhookPath || "/webhook";
|
|
5181
5245
|
this.post(webhookPath, async (c) => {
|
|
@@ -5242,6 +5306,7 @@ class AppServer extends Hono {
|
|
|
5242
5306
|
userId
|
|
5243
5307
|
});
|
|
5244
5308
|
const cleanupDisconnect = session.events.onDisconnected((info) => {
|
|
5309
|
+
const isPermanent = typeof info === "object" && (info.permanent === true || info.sessionEnded === true);
|
|
5245
5310
|
if (typeof info === "string") {
|
|
5246
5311
|
this.logger.info(`\uD83D\uDC4B Session ${sessionId} disconnected: ${info}`);
|
|
5247
5312
|
} else {
|
|
@@ -5258,8 +5323,17 @@ class AppServer extends Hono {
|
|
|
5258
5323
|
});
|
|
5259
5324
|
}
|
|
5260
5325
|
}
|
|
5261
|
-
|
|
5262
|
-
|
|
5326
|
+
if (isPermanent) {
|
|
5327
|
+
if (this.activeSessions.get(sessionId) === session) {
|
|
5328
|
+
this.activeSessions.delete(sessionId);
|
|
5329
|
+
}
|
|
5330
|
+
if (this.activeSessionsByUserId.get(userId) === session) {
|
|
5331
|
+
this.activeSessionsByUserId.delete(userId);
|
|
5332
|
+
}
|
|
5333
|
+
this.cleanupPhotoRequestsForSession(sessionId);
|
|
5334
|
+
} else {
|
|
5335
|
+
this.logger.debug({ sessionId }, "\uD83D\uDD04 Temporary disconnect, session stays in maps for reconnection");
|
|
5336
|
+
}
|
|
5263
5337
|
});
|
|
5264
5338
|
const cleanupError = session.events.onError((error) => {
|
|
5265
5339
|
this.logger.error(error, `❌ [Session ${sessionId}] Error:`);
|
|
@@ -5387,32 +5461,26 @@ class AppServer extends Hono {
|
|
|
5387
5461
|
const body = await c.req.parseBody();
|
|
5388
5462
|
const requestId = body.requestId;
|
|
5389
5463
|
const type = body.type;
|
|
5390
|
-
const successStr = String(body.success);
|
|
5391
|
-
const success = successStr === "true";
|
|
5392
5464
|
const errorCode = body.errorCode;
|
|
5393
5465
|
const errorMessage = body.errorMessage;
|
|
5394
5466
|
const photoFile = body.photo;
|
|
5395
|
-
|
|
5396
|
-
|
|
5467
|
+
const hasPhotoFile = !!photoFile;
|
|
5468
|
+
const successValue = typeof body.success === "string" ? body.success : undefined;
|
|
5469
|
+
const isExplicitError = type === "photo_error" || successValue === "false";
|
|
5470
|
+
const isSuccess = hasPhotoFile || !isExplicitError && successValue !== "false";
|
|
5471
|
+
this.logger.info({ requestId, type, hasPhotoFile, isExplicitError, rawSuccess: body.success }, `\uD83D\uDCF8 Received photo response: ${requestId} (type: ${type})`);
|
|
5397
5472
|
if (!requestId) {
|
|
5398
5473
|
this.logger.error("No requestId in photo response");
|
|
5399
5474
|
return c.json({ success: false, error: "No requestId provided" }, 400);
|
|
5400
5475
|
}
|
|
5401
|
-
const
|
|
5402
|
-
if (!
|
|
5403
|
-
this.logger.warn({ requestId }, "No
|
|
5404
|
-
return c.json({ success: false, error: "No
|
|
5476
|
+
const pending = this.completePhotoRequest(requestId);
|
|
5477
|
+
if (!pending) {
|
|
5478
|
+
this.logger.warn({ requestId }, "\uD83D\uDCF8 No pending request found for photo (possibly timed out or already handled)");
|
|
5479
|
+
return c.json({ success: false, error: "No pending request found" }, 404);
|
|
5405
5480
|
}
|
|
5406
|
-
if (
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
success: false,
|
|
5410
|
-
error: {
|
|
5411
|
-
code: errorCode || "UNKNOWN_ERROR",
|
|
5412
|
-
message: errorMessage || "Unknown error occurred"
|
|
5413
|
-
}
|
|
5414
|
-
};
|
|
5415
|
-
session.camera.handlePhotoError(errorResponse);
|
|
5481
|
+
if (isExplicitError && !hasPhotoFile) {
|
|
5482
|
+
this.logger.error({ requestId, errorCode, errorMessage }, `\uD83D\uDCF8 Photo capture failed: ${errorCode} - ${errorMessage}`);
|
|
5483
|
+
pending.reject(new Error(`${errorCode || "UNKNOWN_ERROR"}: ${errorMessage || "Unknown error"}`));
|
|
5416
5484
|
return c.json({
|
|
5417
5485
|
success: true,
|
|
5418
5486
|
requestId,
|
|
@@ -5420,19 +5488,20 @@ class AppServer extends Hono {
|
|
|
5420
5488
|
});
|
|
5421
5489
|
}
|
|
5422
5490
|
if (!photoFile) {
|
|
5423
|
-
|
|
5424
|
-
|
|
5491
|
+
const errorMsg = "No photo file in upload (and no explicit error reported)";
|
|
5492
|
+
this.logger.error({ requestId, bodyKeys: Object.keys(body) }, errorMsg);
|
|
5493
|
+
pending.reject(new Error(errorMsg));
|
|
5494
|
+
return c.json({ success: false, error: errorMsg }, 400);
|
|
5425
5495
|
}
|
|
5426
5496
|
const buffer = Buffer.from(await photoFile.arrayBuffer());
|
|
5427
|
-
|
|
5497
|
+
pending.resolve({
|
|
5428
5498
|
buffer,
|
|
5429
5499
|
mimeType: photoFile.type,
|
|
5430
5500
|
filename: photoFile.name || "photo.jpg",
|
|
5431
5501
|
requestId,
|
|
5432
5502
|
size: photoFile.size,
|
|
5433
5503
|
timestamp: new Date
|
|
5434
|
-
};
|
|
5435
|
-
session.camera.handlePhotoReceived(photoData);
|
|
5504
|
+
});
|
|
5436
5505
|
return c.json({
|
|
5437
5506
|
success: true,
|
|
5438
5507
|
requestId,
|
|
@@ -5451,14 +5520,6 @@ class AppServer extends Hono {
|
|
|
5451
5520
|
return c.redirect(authUrl, 302);
|
|
5452
5521
|
});
|
|
5453
5522
|
}
|
|
5454
|
-
findSessionByPhotoRequestId(requestId) {
|
|
5455
|
-
for (const [_sessionId, session] of this.activeSessions) {
|
|
5456
|
-
if (session.camera.hasPhotoPendingRequest(requestId)) {
|
|
5457
|
-
return session;
|
|
5458
|
-
}
|
|
5459
|
-
}
|
|
5460
|
-
return;
|
|
5461
|
-
}
|
|
5462
5523
|
}
|
|
5463
5524
|
|
|
5464
5525
|
class TpaServer extends AppServer {
|
|
@@ -5493,11 +5554,11 @@ export {
|
|
|
5493
5554
|
isSessionWebhookRequest,
|
|
5494
5555
|
isRtmpStreamStopRequest,
|
|
5495
5556
|
isRtmpStreamStatus as isRtmpStreamStatusFromGlasses,
|
|
5496
|
-
|
|
5557
|
+
isRtmpStreamStatus as isRtmpStreamStatusFromCloud,
|
|
5497
5558
|
isRtmpStreamStatus,
|
|
5498
5559
|
isRtmpStreamRequest,
|
|
5499
5560
|
isRgbLedControlResponse as isRgbLedControlResponseFromGlasses,
|
|
5500
|
-
|
|
5561
|
+
isRgbLedControlResponse as isRgbLedControlResponseFromCloud,
|
|
5501
5562
|
isRgbLedControlResponse,
|
|
5502
5563
|
isRgbLedControlRequest,
|
|
5503
5564
|
isRgbLedControl,
|
|
@@ -5505,7 +5566,7 @@ export {
|
|
|
5505
5566
|
isRequestSettings,
|
|
5506
5567
|
isPhotoTaken,
|
|
5507
5568
|
isPhotoResponse as isPhotoResponseFromGlasses,
|
|
5508
|
-
|
|
5569
|
+
isPhotoResponse as isPhotoResponseFromCloud,
|
|
5509
5570
|
isPhotoResponse,
|
|
5510
5571
|
isPhotoRequest2 as isPhotoRequestFromApp,
|
|
5511
5572
|
isPhotoRequest,
|
|
@@ -5558,6 +5619,7 @@ export {
|
|
|
5558
5619
|
getLanguageInfo,
|
|
5559
5620
|
getBaseStreamType,
|
|
5560
5621
|
generateWebviewUrl,
|
|
5622
|
+
generateFrontendToken,
|
|
5561
5623
|
extractTokenFromUrl,
|
|
5562
5624
|
extractTempToken,
|
|
5563
5625
|
exchangeToken,
|
|
@@ -5566,6 +5628,7 @@ export {
|
|
|
5566
5628
|
createTranscriptionStream,
|
|
5567
5629
|
createTouchEventStream,
|
|
5568
5630
|
createToken,
|
|
5631
|
+
createMentraAuthRoutes,
|
|
5569
5632
|
createAuthMiddleware,
|
|
5570
5633
|
WebhookRequestType,
|
|
5571
5634
|
ViewType,
|
|
@@ -5590,7 +5653,6 @@ export {
|
|
|
5590
5653
|
GlassesToCloudMessageType,
|
|
5591
5654
|
GIVE_APP_CONTROL_OF_TOOL_RESPONSE,
|
|
5592
5655
|
EventTypes,
|
|
5593
|
-
DeviceState,
|
|
5594
5656
|
DashboardMode,
|
|
5595
5657
|
DashboardMessageTypes,
|
|
5596
5658
|
ControlActionTypes,
|
|
@@ -5608,4 +5670,4 @@ export {
|
|
|
5608
5670
|
AnimationUtils
|
|
5609
5671
|
};
|
|
5610
5672
|
|
|
5611
|
-
//# debugId=
|
|
5673
|
+
//# debugId=BD78F7F1ED1E3E0F64756E2164756E21
|