@mentra/sdk 1.1.19

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 (101) hide show
  1. package/README.md +102 -0
  2. package/dist/constants/index.d.ts +14 -0
  3. package/dist/constants/index.d.ts.map +1 -0
  4. package/dist/constants/index.js +16 -0
  5. package/dist/examples/rtmp-streaming-example.d.ts +2 -0
  6. package/dist/examples/rtmp-streaming-example.d.ts.map +1 -0
  7. package/dist/examples/rtmp-streaming-example.js +102 -0
  8. package/dist/index.d.ts +4 -0
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/index.js +33 -0
  11. package/dist/logging/logger.d.ts +3 -0
  12. package/dist/logging/logger.d.ts.map +1 -0
  13. package/dist/logging/logger.js +79 -0
  14. package/dist/tpa/index.d.ts +6 -0
  15. package/dist/tpa/index.d.ts.map +1 -0
  16. package/dist/tpa/index.js +24 -0
  17. package/dist/tpa/server/index.d.ts +193 -0
  18. package/dist/tpa/server/index.d.ts.map +1 -0
  19. package/dist/tpa/server/index.js +436 -0
  20. package/dist/tpa/session/api-client.d.ts +49 -0
  21. package/dist/tpa/session/api-client.d.ts.map +1 -0
  22. package/dist/tpa/session/api-client.js +101 -0
  23. package/dist/tpa/session/dashboard.d.ts +52 -0
  24. package/dist/tpa/session/dashboard.d.ts.map +1 -0
  25. package/dist/tpa/session/dashboard.js +149 -0
  26. package/dist/tpa/session/events.d.ts +178 -0
  27. package/dist/tpa/session/events.d.ts.map +1 -0
  28. package/dist/tpa/session/events.js +294 -0
  29. package/dist/tpa/session/index.d.ts +391 -0
  30. package/dist/tpa/session/index.d.ts.map +1 -0
  31. package/dist/tpa/session/index.js +1452 -0
  32. package/dist/tpa/session/layouts.d.ts +150 -0
  33. package/dist/tpa/session/layouts.d.ts.map +1 -0
  34. package/dist/tpa/session/layouts.js +282 -0
  35. package/dist/tpa/session/modules/streaming.d.ts +100 -0
  36. package/dist/tpa/session/modules/streaming.d.ts.map +1 -0
  37. package/dist/tpa/session/modules/streaming.js +270 -0
  38. package/dist/tpa/session/settings.d.ts +202 -0
  39. package/dist/tpa/session/settings.d.ts.map +1 -0
  40. package/dist/tpa/session/settings.js +361 -0
  41. package/dist/tpa/token/index.d.ts +7 -0
  42. package/dist/tpa/token/index.d.ts.map +1 -0
  43. package/dist/tpa/token/index.js +22 -0
  44. package/dist/tpa/token/utils.d.ts +69 -0
  45. package/dist/tpa/token/utils.d.ts.map +1 -0
  46. package/dist/tpa/token/utils.js +144 -0
  47. package/dist/tpa/webview/index.d.ts +47 -0
  48. package/dist/tpa/webview/index.d.ts.map +1 -0
  49. package/dist/tpa/webview/index.js +344 -0
  50. package/dist/types/dashboard/index.d.ts +128 -0
  51. package/dist/types/dashboard/index.d.ts.map +1 -0
  52. package/dist/types/dashboard/index.js +12 -0
  53. package/dist/types/enums.d.ts +57 -0
  54. package/dist/types/enums.d.ts.map +1 -0
  55. package/dist/types/enums.js +72 -0
  56. package/dist/types/index.d.ts +38 -0
  57. package/dist/types/index.d.ts.map +1 -0
  58. package/dist/types/index.js +87 -0
  59. package/dist/types/layouts.d.ts +51 -0
  60. package/dist/types/layouts.d.ts.map +1 -0
  61. package/dist/types/layouts.js +3 -0
  62. package/dist/types/message-types.d.ts +109 -0
  63. package/dist/types/message-types.d.ts.map +1 -0
  64. package/dist/types/message-types.js +189 -0
  65. package/dist/types/messages/base.d.ts +12 -0
  66. package/dist/types/messages/base.d.ts.map +1 -0
  67. package/dist/types/messages/base.js +3 -0
  68. package/dist/types/messages/cloud-to-glasses.d.ts +126 -0
  69. package/dist/types/messages/cloud-to-glasses.d.ts.map +1 -0
  70. package/dist/types/messages/cloud-to-glasses.js +60 -0
  71. package/dist/types/messages/cloud-to-tpa.d.ts +228 -0
  72. package/dist/types/messages/cloud-to-tpa.d.ts.map +1 -0
  73. package/dist/types/messages/cloud-to-tpa.js +61 -0
  74. package/dist/types/messages/glasses-to-cloud.d.ts +219 -0
  75. package/dist/types/messages/glasses-to-cloud.d.ts.map +1 -0
  76. package/dist/types/messages/glasses-to-cloud.js +88 -0
  77. package/dist/types/messages/tpa-to-cloud.d.ts +146 -0
  78. package/dist/types/messages/tpa-to-cloud.d.ts.map +1 -0
  79. package/dist/types/messages/tpa-to-cloud.js +67 -0
  80. package/dist/types/models.d.ts +165 -0
  81. package/dist/types/models.d.ts.map +1 -0
  82. package/dist/types/models.js +84 -0
  83. package/dist/types/rtmp-stream.d.ts +68 -0
  84. package/dist/types/rtmp-stream.d.ts.map +1 -0
  85. package/dist/types/rtmp-stream.js +3 -0
  86. package/dist/types/streams.d.ts +138 -0
  87. package/dist/types/streams.d.ts.map +1 -0
  88. package/dist/types/streams.js +251 -0
  89. package/dist/types/token.d.ts +41 -0
  90. package/dist/types/token.d.ts.map +1 -0
  91. package/dist/types/token.js +7 -0
  92. package/dist/types/user-session.d.ts +73 -0
  93. package/dist/types/user-session.d.ts.map +1 -0
  94. package/dist/types/user-session.js +17 -0
  95. package/dist/types/webhooks.d.ts +107 -0
  96. package/dist/types/webhooks.d.ts.map +1 -0
  97. package/dist/types/webhooks.js +55 -0
  98. package/dist/utils/resource-tracker.d.ts +94 -0
  99. package/dist/utils/resource-tracker.d.ts.map +1 -0
  100. package/dist/utils/resource-tracker.js +153 -0
  101. package/package.json +50 -0
@@ -0,0 +1,344 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.extractTempToken = extractTempToken;
40
+ exports.exchangeToken = exchangeToken;
41
+ exports.createAuthMiddleware = createAuthMiddleware;
42
+ // src/tpa/webview/index.ts
43
+ const axios_1 = __importDefault(require("axios"));
44
+ // Note: Your Express app needs to use cookie-parser middleware for this to work
45
+ // Example: app.use(require('cookie-parser')());
46
+ const crypto = __importStar(require("crypto"));
47
+ const jsrsasign_1 = require("jsrsasign");
48
+ const userTokenPublicKey = process.env.AUGMENTOS_CLOUD_USER_TOKEN_PUBLIC_KEY || "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0Yt2RtNOdeKQxWMY0c84\nADpY1Jy58YWZhaEgP2A5tBwFUKgy/TH9gQLWZjQ3dQ/6XXO8qq0kluoYFqM7ZDRF\nzJ0E4Yi0WQncioLRcCx4q8pDmqY9vPKgv6PruJdFWca0l0s3gZ3BqSeWum/C23xK\nFPHPwi8gvRdc6ALrkcHeciM+7NykU8c0EY8PSitNL+Tchti95kGu+j6APr5vNewi\nzRpQGOdqaLWe+ahHmtj6KtUZjm8o6lan4f/o08C6litizguZXuw2Nn/Kd9fFI1xF\nIVNJYMy9jgGaOi71+LpGw+vIpwAawp/7IvULDppvY3DdX5nt05P1+jvVJXPxMKzD\nTQIDAQAB\n-----END PUBLIC KEY-----";
49
+ /**
50
+ * Extracts the temporary token from a URL string.
51
+ * @param url The URL string, typically window.location.href.
52
+ * @returns The token string or null if not found.
53
+ */
54
+ function extractTempToken(url) {
55
+ try {
56
+ const parsedUrl = new URL(url);
57
+ return parsedUrl.searchParams.get('aos_temp_token');
58
+ }
59
+ catch (e) {
60
+ console.error("Error parsing URL for temp token:", e);
61
+ return null;
62
+ }
63
+ }
64
+ /**
65
+ * Exchanges a temporary token for a user ID with the AugmentOS Cloud.
66
+ * This should be called from the TPA's backend server.
67
+ * @param cloudApiUrl The base URL of the AugmentOS Cloud API.
68
+ * @param tempToken The temporary token obtained from the webview URL.
69
+ * @param apiKey Your TPA's secret API key.
70
+ * @returns A Promise that resolves with an object containing the userId.
71
+ * @throws Throws an error if the exchange fails (e.g., invalid token, expired, network error).
72
+ */
73
+ async function exchangeToken(cloudApiUrl, tempToken, apiKey, packageName) {
74
+ const endpoint = `${cloudApiUrl}/api/auth/exchange-user-token`;
75
+ console.log(`Exchanging token for user at ${endpoint}`);
76
+ try {
77
+ const response = await axios_1.default.post(endpoint, { aos_temp_token: tempToken, packageName: packageName }, {
78
+ headers: {
79
+ 'Content-Type': 'application/json',
80
+ 'Authorization': `Bearer ${apiKey}`,
81
+ },
82
+ timeout: 10000, // 10 second timeout
83
+ });
84
+ if (response.status === 200 && response.data.success && response.data.userId) {
85
+ return { userId: response.data.userId };
86
+ }
87
+ else {
88
+ // Handle specific error messages from the server if available
89
+ const errorMessage = response.data?.error || `Failed with status ${response.status}`;
90
+ throw new Error(errorMessage);
91
+ }
92
+ }
93
+ catch (error) {
94
+ if (axios_1.default.isAxiosError(error)) {
95
+ const status = error.response?.status;
96
+ const data = error.response?.data;
97
+ const message = data?.error || error.message || 'Unknown error during token exchange';
98
+ console.error(`Token exchange failed with status ${status}: ${message}`);
99
+ throw new Error(`Token exchange failed: ${message}`);
100
+ }
101
+ else {
102
+ console.error('Unexpected error during token exchange:', error);
103
+ throw new Error('An unexpected error occurred during token exchange.');
104
+ }
105
+ }
106
+ }
107
+ /**
108
+ * Signs a user ID to create a secure session token.
109
+ * @param userId The user ID to sign
110
+ * @param secret The secret key used for signing
111
+ * @returns A signed session token string
112
+ */
113
+ function signSession(userId, secret) {
114
+ // Format: userId.timestamp.signature
115
+ const timestamp = Date.now();
116
+ const data = `${userId}|${timestamp}`;
117
+ const signature = crypto
118
+ .createHmac('sha256', secret)
119
+ .update(data)
120
+ .digest('hex');
121
+ return `${data}|${signature}`;
122
+ }
123
+ /**
124
+ * Verifies and extracts the user ID from a signed session token.
125
+ * @param token The signed session token
126
+ * @param secret The secret key used for verification
127
+ * @param maxAge The maximum age of the token in milliseconds
128
+ * @returns The extracted user ID if valid, or null if invalid
129
+ */
130
+ function verifySession(token, secret, maxAge) {
131
+ try {
132
+ const parts = token.split('|');
133
+ if (parts.length !== 3)
134
+ return null;
135
+ const [userId, timestampStr, signature] = parts;
136
+ const timestamp = parseInt(timestampStr, 10);
137
+ // Check if token has expired
138
+ if (maxAge && Date.now() - timestamp > maxAge) {
139
+ console.log(`Session token expired: ${token}. Parsed date is ${timestamp}, meaning age is ${Date.now() - timestamp}, but maxAge is ${maxAge}`);
140
+ return null;
141
+ }
142
+ // Verify signature
143
+ const data = `${userId}|${timestamp}`;
144
+ const expectedSignature = crypto
145
+ .createHmac('sha256', secret)
146
+ .update(data)
147
+ .digest('hex');
148
+ if (signature !== expectedSignature) {
149
+ console.log(`Session token signature mismatch: ${signature} !== ${expectedSignature}`);
150
+ return null;
151
+ }
152
+ return userId;
153
+ }
154
+ catch (error) {
155
+ console.error("Session verification failed:", error);
156
+ return null;
157
+ }
158
+ }
159
+ /**
160
+ * Verifies a signed user token and extracts the user ID from it
161
+ * @param signedUserToken The JWT token to verify
162
+ * @returns The user ID (subject) from the token, or null if invalid
163
+ */
164
+ async function verifySignedUserToken(signedUserToken) {
165
+ try {
166
+ // 1. Parse the PEM public key into a jsrsasign key object
167
+ const publicKeyObj = jsrsasign_1.KEYUTIL.getKey(userTokenPublicKey);
168
+ // 2. Verify JWT signature + claims (issuer, exp, iat) with 2-min tolerance
169
+ const isValid = jsrsasign_1.KJUR.jws.JWS.verifyJWT(signedUserToken, publicKeyObj, {
170
+ alg: ["RS256"],
171
+ iss: ["https://prod.augmentos.cloud"],
172
+ verifyAt: jsrsasign_1.KJUR.jws.IntDate.get("now"),
173
+ gracePeriod: 120,
174
+ });
175
+ if (!isValid)
176
+ return null;
177
+ // 3. Decode payload and return the subject (user ID)
178
+ const parsed = jsrsasign_1.KJUR.jws.JWS.parse(signedUserToken);
179
+ return parsed.payloadObj.sub || null;
180
+ }
181
+ catch (e) {
182
+ console.error("[verifySignedUserToken] Error verifying token:", e);
183
+ return null;
184
+ }
185
+ }
186
+ /**
187
+ * Verifies a frontend token by comparing it to a secure hash of the API key
188
+ * @param frontendToken The token to verify (should be a hash of the API key)
189
+ * @param apiKey The API key to hash and compare against
190
+ * @param userId Optional user ID that may be embedded in the token format
191
+ * @returns The user ID if the token is valid, or null if invalid
192
+ */
193
+ function verifyFrontendToken(frontendToken, apiKey) {
194
+ try {
195
+ // Check if the token contains a user ID and hash separated by a colon
196
+ const tokenParts = frontendToken.split(':');
197
+ if (tokenParts.length === 2) {
198
+ // Format: userId:hash
199
+ const [tokenUserId, tokenHash] = tokenParts;
200
+ // Align the hashing algorithm with server-side `hashWithApiKey`
201
+ // 1. Hash the API key first (server only stores the hashed version)
202
+ const hashedApiKey = crypto.createHash('sha256').update(apiKey).digest('hex');
203
+ // 2. Create the expected hash using userId + hashedApiKey (same order & update calls)
204
+ const expectedHash = crypto.createHash('sha256')
205
+ .update(tokenUserId)
206
+ .update(hashedApiKey)
207
+ .digest('hex');
208
+ if (tokenHash === expectedHash) {
209
+ return tokenUserId;
210
+ }
211
+ }
212
+ else {
213
+ throw new Error("Invalid frontend token format");
214
+ }
215
+ return null;
216
+ }
217
+ catch (error) {
218
+ console.error("Frontend token verification failed:", error);
219
+ return null;
220
+ }
221
+ }
222
+ function validateCloudApiUrlChecksum(checksum, cloudApiUrl, apiKey) {
223
+ const hashedApiKey = crypto.createHash('sha256').update(apiKey).digest('hex');
224
+ const expectedChecksum = crypto.createHash('sha256').update(cloudApiUrl)
225
+ .update(hashedApiKey)
226
+ .digest('hex');
227
+ return expectedChecksum === checksum;
228
+ }
229
+ /**
230
+ * Express middleware for automatically handling the token exchange.
231
+ * Assumes API key and Cloud URL are available (e.g., via environment variables).
232
+ * Adds `req.authUserId` if successful.
233
+ *
234
+ * @param options Configuration options.
235
+ * @param options.cloudApiUrl The base URL of the AugmentOS Cloud API.
236
+ * @param options.apiKey Your TPA's secret API key.
237
+ * @param options.tokenQueryParam The name of the query parameter containing the token (default: 'aos_temp_token').
238
+ * @param options.cookieName The name of the cookie to store the session token (default: 'aos_session').
239
+ * @param options.cookieSecret Secret key used to sign the session cookie. MUST be provided and kept secure.
240
+ * @param options.cookieOptions Options for the session cookie (default: { httpOnly: true, secure: process.env.NODE_ENV === 'production' }).
241
+ */
242
+ function createAuthMiddleware(options) {
243
+ const { apiKey, packageName, cookieName = 'aos_session', cookieSecret, cookieOptions = {
244
+ httpOnly: true,
245
+ secure: process.env.NODE_ENV === 'production',
246
+ maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days by default
247
+ sameSite: process.env.NODE_ENV === 'production' ? 'none' : 'lax',
248
+ path: '/'
249
+ } } = options;
250
+ if (!apiKey) {
251
+ throw new Error("API Key are required for the auth middleware.");
252
+ }
253
+ if (!cookieSecret || typeof cookieSecret !== 'string' || cookieSecret.length < 8) {
254
+ throw new Error("A strong cookieSecret (at least 8 characters) is required for secure session management.");
255
+ }
256
+ return async (req, res, next) => {
257
+ // First check for temporary token in the query string
258
+ const tempToken = req.query['aos_temp_token'];
259
+ const frontendToken = req.headers.authorization?.replace('Bearer ', '') || req.query['aos_frontend_token'];
260
+ const signedUserToken = req.query['aos_signed_user_token'];
261
+ // first check for signed user token
262
+ if (signedUserToken) {
263
+ const userId = await verifySignedUserToken(signedUserToken);
264
+ if (userId) {
265
+ // Set the user ID on the request
266
+ req.authUserId = userId;
267
+ // Create a signed session token and store it in a cookie
268
+ const signedSession = signSession(userId, cookieSecret);
269
+ res.cookie(cookieName, signedSession, cookieOptions);
270
+ console.log('[auth.middleware] User ID verified from signed user token: ', userId);
271
+ return next();
272
+ }
273
+ else {
274
+ console.log('[auth.middleware] Signed user token invalid');
275
+ }
276
+ }
277
+ // If temporary token exists, authenticate with it
278
+ if (tempToken) {
279
+ try {
280
+ let cloudApiUrl = `https://prod.augmentos.cloud`;
281
+ const cloudApiUrlFromQuery = req.query['cloudApiUrl'];
282
+ if (cloudApiUrlFromQuery) {
283
+ const cloudApiUrlChecksum = req.query['cloudApiUrlChecksum'];
284
+ if (validateCloudApiUrlChecksum(cloudApiUrlChecksum, cloudApiUrlFromQuery, apiKey)) {
285
+ console.log(`Cloud API is being routed to alternate url at request of the server: ${cloudApiUrlFromQuery}`);
286
+ cloudApiUrl = cloudApiUrlFromQuery;
287
+ }
288
+ else {
289
+ console.error(`Server requested alternate cloud url of ${cloudApiUrlFromQuery} but the checksum is invalid (checksum: ${cloudApiUrlChecksum}). Using default cloud url of ${cloudApiUrl} instead.`);
290
+ }
291
+ }
292
+ const { userId } = await exchangeToken(cloudApiUrl, tempToken, apiKey, packageName);
293
+ // Set the user ID on the request
294
+ req.authUserId = userId;
295
+ // Create a signed session token and store it in a cookie
296
+ const signedSession = signSession(userId, cookieSecret);
297
+ res.cookie(cookieName, signedSession, cookieOptions);
298
+ console.log('[auth.middleware] User ID verified from temporary token: ', userId);
299
+ return next();
300
+ }
301
+ catch (error) {
302
+ console.error("Webview token exchange failed:", error);
303
+ // Temporary token is invalid
304
+ }
305
+ }
306
+ if (frontendToken) {
307
+ // Check for user ID in headers if not embedded in token
308
+ const userId = verifyFrontendToken(frontendToken, apiKey);
309
+ if (userId) {
310
+ req.authUserId = userId;
311
+ // Create a signed session token and store it in a cookie
312
+ const signedSession = signSession(userId, cookieSecret);
313
+ res.cookie(cookieName, signedSession, cookieOptions);
314
+ console.log('[auth.middleware] User ID verified from frontend user token: ', userId);
315
+ return next();
316
+ }
317
+ else {
318
+ console.log('[auth.middleware] Frontend token invalid');
319
+ }
320
+ }
321
+ // No valid temporary token, check for existing session cookie
322
+ const sessionCookie = req.cookies?.[cookieName];
323
+ if (sessionCookie) {
324
+ try {
325
+ // Verify the signed session cookie and extract the user ID
326
+ const userId = verifySession(sessionCookie, cookieSecret, cookieOptions.maxAge);
327
+ console.log(`User ID verified from session cookie: ${userId}`);
328
+ if (userId) {
329
+ req.authUserId = userId;
330
+ return next();
331
+ }
332
+ // Invalid or expired session, clear the cookie
333
+ res.clearCookie(cookieName, { path: cookieOptions.path });
334
+ }
335
+ catch (error) {
336
+ console.error("Invalid session cookie:", error);
337
+ // Clear the invalid cookie
338
+ res.clearCookie(cookieName, { path: cookieOptions.path });
339
+ }
340
+ }
341
+ // No valid authentication method found, proceed without setting req.authUserId
342
+ next();
343
+ };
344
+ }
@@ -0,0 +1,128 @@
1
+ import { TpaToCloudMessageType } from '../message-types';
2
+ /**
3
+ * Dashboard modes supported by the system
4
+ */
5
+ export declare enum DashboardMode {
6
+ MAIN = "main",// Full dashboard experience
7
+ EXPANDED = "expanded"
8
+ }
9
+ /**
10
+ * Dashboard API for the system dashboard TPA
11
+ */
12
+ export interface DashboardSystemAPI {
13
+ /**
14
+ * Set content for the top left section of the dashboard
15
+ * @param content Content to display
16
+ */
17
+ setTopLeft(content: string): void;
18
+ /**
19
+ * Set content for the top right section of the dashboard
20
+ * @param content Content to display
21
+ */
22
+ setTopRight(content: string): void;
23
+ /**
24
+ * Set content for the bottom left section of the dashboard
25
+ * @param content Content to display
26
+ */
27
+ setBottomLeft(content: string): void;
28
+ /**
29
+ * Set content for the bottom right section of the dashboard
30
+ * @param content Content to display
31
+ */
32
+ setBottomRight(content: string): void;
33
+ /**
34
+ * Set the current dashboard mode
35
+ * @param mode Dashboard mode to set
36
+ */
37
+ setViewMode(mode: DashboardMode): void;
38
+ }
39
+ /**
40
+ * Dashboard API for all TPAs
41
+ */
42
+ export interface DashboardContentAPI {
43
+ /**
44
+ * Write content to dashboard
45
+ * @param content Content to display
46
+ * @param targets Optional list of dashboard modes to target
47
+ */
48
+ write(content: string, targets?: DashboardMode[]): void;
49
+ /**
50
+ * Write content to main dashboard mode
51
+ * @param content Content to display
52
+ */
53
+ writeToMain(content: string): void;
54
+ /**
55
+ * Write content to expanded dashboard mode
56
+ * @param content Text content to display
57
+ */
58
+ writeToExpanded(content: string): void;
59
+ /**
60
+ * Write content to always-on dashboard mode
61
+ * @param content Content to display
62
+ */
63
+ /**
64
+ * Get current active dashboard mode
65
+ * @returns Promise resolving to current mode or 'none'
66
+ */
67
+ getCurrentMode(): Promise<DashboardMode | 'none'>;
68
+ /**
69
+ * Check if always-on dashboard is enabled
70
+ * @returns Promise resolving to boolean
71
+ */
72
+ /**
73
+ * Register for mode change notifications
74
+ * @param callback Function to call when mode changes
75
+ * @returns Cleanup function to unregister callback
76
+ */
77
+ onModeChange(callback: (mode: DashboardMode | 'none') => void): () => void;
78
+ }
79
+ /**
80
+ * Dashboard API exposed on TpaSession
81
+ */
82
+ export interface DashboardAPI {
83
+ /**
84
+ * System dashboard API (only available for system dashboard TPA)
85
+ */
86
+ system?: DashboardSystemAPI;
87
+ /**
88
+ * Content API (available to all TPAs)
89
+ */
90
+ content: DashboardContentAPI;
91
+ }
92
+ /**
93
+ * Message to update dashboard content
94
+ */
95
+ export interface DashboardContentUpdate {
96
+ type: TpaToCloudMessageType.DASHBOARD_CONTENT_UPDATE;
97
+ packageName: string;
98
+ sessionId: string;
99
+ content: string;
100
+ modes: DashboardMode[];
101
+ timestamp: Date;
102
+ }
103
+ /**
104
+ * Message for dashboard mode change
105
+ */
106
+ export interface DashboardModeChange {
107
+ type: TpaToCloudMessageType.DASHBOARD_MODE_CHANGE;
108
+ packageName: string;
109
+ sessionId: string;
110
+ mode: DashboardMode;
111
+ timestamp: Date;
112
+ }
113
+ /**
114
+ * Message to update system dashboard content
115
+ */
116
+ export interface DashboardSystemUpdate {
117
+ type: TpaToCloudMessageType.DASHBOARD_SYSTEM_UPDATE;
118
+ packageName: string;
119
+ sessionId: string;
120
+ section: 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight';
121
+ content: string;
122
+ timestamp: Date;
123
+ }
124
+ /**
125
+ * Union type of all dashboard message types
126
+ */
127
+ export type DashboardMessage = DashboardContentUpdate | DashboardModeChange | DashboardSystemUpdate;
128
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/types/dashboard/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAEzD;;GAEG;AACH,oBAAY,aAAa;IACvB,IAAI,SAAS,CAAY,4BAA4B;IACrD,QAAQ,aAAa;CAEtB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAElC;;;OAGG;IACH,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAEnC;;;OAGG;IACH,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAErC;;;OAGG;IACH,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAEtC;;;OAGG;IACH,WAAW,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI,CAAC;CACxC;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,EAAE,GAAG,IAAI,CAAC;IAExD;;;OAGG;IACH,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAEnC;;;OAGG;IACH,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAEvC;;;OAGG;IAGH;;;OAGG;IACH,cAAc,IAAI,OAAO,CAAC,aAAa,GAAG,MAAM,CAAC,CAAC;IAElD;;;OAGG;IAGH;;;;OAIG;IACH,YAAY,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;CAQ5E;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B;;OAEG;IACH,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAE5B;;OAEG;IACH,OAAO,EAAE,mBAAmB,CAAC;CAC9B;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,qBAAqB,CAAC,wBAAwB,CAAC;IACrD,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,qBAAqB,CAAC,qBAAqB,CAAC;IAClD,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,aAAa,CAAC;IACpB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,qBAAqB,CAAC,uBAAuB,CAAC;IACpD,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,SAAS,GAAG,UAAU,GAAG,YAAY,GAAG,aAAa,CAAC;IAC/D,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,MAAM,gBAAgB,GACxB,sBAAsB,GACtB,mBAAmB,GACnB,qBAAqB,CAAC"}
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DashboardMode = void 0;
4
+ /**
5
+ * Dashboard modes supported by the system
6
+ */
7
+ var DashboardMode;
8
+ (function (DashboardMode) {
9
+ DashboardMode["MAIN"] = "main";
10
+ DashboardMode["EXPANDED"] = "expanded";
11
+ // ALWAYS_ON = 'always_on' // Persistent minimal dashboard
12
+ })(DashboardMode || (exports.DashboardMode = DashboardMode = {}));
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Types of Third-Party Applications (TPAs)
3
+ */
4
+ export declare enum TpaType {
5
+ SYSTEM_DASHBOARD = "system_dashboard",// Special UI placement, system functionality
6
+ BACKGROUND = "background",// Can temporarily take control of display
7
+ STANDARD = "standard"
8
+ }
9
+ /**
10
+ * Application states in the system
11
+ */
12
+ export declare enum AppState {
13
+ NOT_INSTALLED = "not_installed",// Initial state
14
+ INSTALLED = "installed",// Installed but never run
15
+ BOOTING = "booting",// Starting up
16
+ RUNNING = "running",// Active and running
17
+ STOPPED = "stopped",// Manually stopped
18
+ ERROR = "error"
19
+ }
20
+ /**
21
+ * Supported languages
22
+ */
23
+ export declare enum Language {
24
+ EN = "en",
25
+ ES = "es",
26
+ FR = "fr"
27
+ }
28
+ /**
29
+ * Types of layouts for displaying content
30
+ */
31
+ export declare enum LayoutType {
32
+ TEXT_WALL = "text_wall",
33
+ DOUBLE_TEXT_WALL = "double_text_wall",
34
+ DASHBOARD_CARD = "dashboard_card",
35
+ REFERENCE_CARD = "reference_card",
36
+ BITMAP_VIEW = "bitmap_view"
37
+ }
38
+ /**
39
+ * Types of views for displaying content
40
+ */
41
+ export declare enum ViewType {
42
+ DASHBOARD = "dashboard",// Regular dashboard (main/expanded)
43
+ ALWAYS_ON = "always_on",// Persistent overlay dashboard
44
+ MAIN = "main"
45
+ }
46
+ export declare enum AppSettingType {
47
+ TOGGLE = "toggle",
48
+ TEXT = "text",
49
+ SELECT = "select",
50
+ SLIDER = "slider",
51
+ GROUP = "group",
52
+ TEXT_NO_SAVE_BUTTON = "text_no_save_button",
53
+ SELECT_WITH_SEARCH = "select_with_search",
54
+ MULTISELECT = "multiselect",
55
+ TITLE_VALUE = "titleValue"
56
+ }
57
+ //# sourceMappingURL=enums.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enums.d.ts","sourceRoot":"","sources":["../../src/types/enums.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,oBAAY,OAAO;IACf,gBAAgB,qBAAqB,CAAG,6CAA6C;IACrF,UAAU,eAAe,CAAe,0CAA0C;IAClF,QAAQ,aAAa;CACxB;AAGD;;GAEG;AACH,oBAAY,QAAQ;IAChB,aAAa,kBAAkB,CAAG,gBAAgB;IAClD,SAAS,cAAc,CAAW,0BAA0B;IAC5D,OAAO,YAAY,CAAe,cAAc;IAChD,OAAO,YAAY,CAAe,qBAAqB;IACvD,OAAO,YAAY,CAAe,mBAAmB;IACrD,KAAK,UAAU;CAClB;AAED;;GAEG;AACH,oBAAY,QAAQ;IAChB,EAAE,OAAO;IACT,EAAE,OAAO;IACT,EAAE,OAAO;CAEZ;AAED;;GAEG;AACH,oBAAY,UAAU;IAClB,SAAS,cAAc;IACvB,gBAAgB,qBAAqB;IACrC,cAAc,mBAAmB;IACjC,cAAc,mBAAmB;IACjC,WAAW,gBAAgB;CAC9B;AAED;;GAEG;AACH,oBAAY,QAAQ;IAChB,SAAS,cAAc,CAAI,oCAAoC;IAC/D,SAAS,cAAc,CAAI,+BAA+B;IAC1D,IAAI,SAAS;CAChB;AAGD,oBAAY,cAAc;IACtB,MAAM,WAAW;IACjB,IAAI,SAAS;IACb,MAAM,WAAW;IACjB,MAAM,WAAW;IACjB,KAAK,UAAU;IACf,mBAAmB,wBAAwB;IAC3C,kBAAkB,uBAAuB;IACzC,WAAW,gBAAgB;IAC3B,WAAW,eAAe;CAC7B"}
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ // src/enums.ts
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.AppSettingType = exports.ViewType = exports.LayoutType = exports.Language = exports.AppState = exports.TpaType = void 0;
5
+ /**
6
+ * Types of Third-Party Applications (TPAs)
7
+ */
8
+ var TpaType;
9
+ (function (TpaType) {
10
+ TpaType["SYSTEM_DASHBOARD"] = "system_dashboard";
11
+ TpaType["BACKGROUND"] = "background";
12
+ TpaType["STANDARD"] = "standard"; // Regular TPA (default) only one standard app can run at a time. starting a standard TPA will close any other standard TPA that is running.
13
+ })(TpaType || (exports.TpaType = TpaType = {}));
14
+ // TODO(isaiah): doesn't seem like this is actually used anywhere, remove?
15
+ /**
16
+ * Application states in the system
17
+ */
18
+ var AppState;
19
+ (function (AppState) {
20
+ AppState["NOT_INSTALLED"] = "not_installed";
21
+ AppState["INSTALLED"] = "installed";
22
+ AppState["BOOTING"] = "booting";
23
+ AppState["RUNNING"] = "running";
24
+ AppState["STOPPED"] = "stopped";
25
+ AppState["ERROR"] = "error"; // Error state
26
+ })(AppState || (exports.AppState = AppState = {}));
27
+ /**
28
+ * Supported languages
29
+ */
30
+ var Language;
31
+ (function (Language) {
32
+ Language["EN"] = "en";
33
+ Language["ES"] = "es";
34
+ Language["FR"] = "fr";
35
+ // TODO: Add more languages
36
+ })(Language || (exports.Language = Language = {}));
37
+ /**
38
+ * Types of layouts for displaying content
39
+ */
40
+ var LayoutType;
41
+ (function (LayoutType) {
42
+ LayoutType["TEXT_WALL"] = "text_wall";
43
+ LayoutType["DOUBLE_TEXT_WALL"] = "double_text_wall";
44
+ LayoutType["DASHBOARD_CARD"] = "dashboard_card";
45
+ LayoutType["REFERENCE_CARD"] = "reference_card";
46
+ LayoutType["BITMAP_VIEW"] = "bitmap_view";
47
+ })(LayoutType || (exports.LayoutType = LayoutType = {}));
48
+ /**
49
+ * Types of views for displaying content
50
+ */
51
+ var ViewType;
52
+ (function (ViewType) {
53
+ ViewType["DASHBOARD"] = "dashboard";
54
+ ViewType["ALWAYS_ON"] = "always_on";
55
+ ViewType["MAIN"] = "main"; // Regular app content
56
+ })(ViewType || (exports.ViewType = ViewType = {}));
57
+ // Types for AppSettings
58
+ var AppSettingType;
59
+ (function (AppSettingType) {
60
+ AppSettingType["TOGGLE"] = "toggle";
61
+ AppSettingType["TEXT"] = "text";
62
+ AppSettingType["SELECT"] = "select";
63
+ AppSettingType["SLIDER"] = "slider";
64
+ AppSettingType["GROUP"] = "group";
65
+ AppSettingType["TEXT_NO_SAVE_BUTTON"] = "text_no_save_button";
66
+ AppSettingType["SELECT_WITH_SEARCH"] = "select_with_search";
67
+ AppSettingType["MULTISELECT"] = "multiselect";
68
+ AppSettingType["TITLE_VALUE"] = "titleValue";
69
+ })(AppSettingType || (exports.AppSettingType = AppSettingType = {}));
70
+ // | { type: "toggle"; key: string; label: string; defaultValue: boolean }
71
+ // | { type: "text"; key: string; label: string; defaultValue?: string }
72
+ // | { type: "select"; key: string; label: string; options: { label: string; value: string }[]; defaultValue?: string };
@@ -0,0 +1,38 @@
1
+ export * from './token';
2
+ export * from './message-types';
3
+ export * from './messages/base';
4
+ export * from './messages/glasses-to-cloud';
5
+ export * from './messages/cloud-to-glasses';
6
+ export * from './messages/tpa-to-cloud';
7
+ export { TpaConnectionAck, TpaConnectionError, AppStopped, SettingsUpdate as TpaSettingsUpdate, // Alias to avoid conflict with cloud-to-glasses SettingsUpdate
8
+ DataStream, CloudToTpaMessage, TranslationData, ToolCall, StandardConnectionError, CustomMessage, AugmentosSettingsUpdate, TranscriptionData, AudioChunk, PermissionError, PermissionErrorDetail, isTpaConnectionAck, isTpaConnectionError, isAppStopped, isSettingsUpdate, isDataStream, isAudioChunk, isDashboardModeChanged, isDashboardAlwaysOnChanged, isPhotoResponse as isPhotoResponseFromCloud, isRtmpStreamStatus as isRtmpStreamStatusFromCloud } from './messages/cloud-to-tpa';
9
+ export * from './streams';
10
+ export * from './layouts';
11
+ export * from './dashboard';
12
+ export * from './rtmp-stream';
13
+ export * from './enums';
14
+ export * from './models';
15
+ export * from './user-session';
16
+ export * from './webhooks';
17
+ export { ButtonPress, HeadPosition, GlassesBatteryUpdate, PhoneBatteryUpdate, GlassesConnectionState, LocationUpdate, CalendarEvent, Vad, PhoneNotification, NotificationDismissed, StartApp, StopApp, ConnectionInit, DashboardState, OpenDashboard, GlassesToCloudMessage, PhotoResponse, RtmpStreamStatus, KeepAliveAck } from './messages/glasses-to-cloud';
18
+ export { ConnectionAck, ConnectionError, AuthError, DisplayEvent, AppStateChange, MicrophoneStateChange, CloudToGlassesMessage, PhotoRequestToGlasses, SettingsUpdate, StartRtmpStream, StopRtmpStream, KeepRtmpStreamAlive } from './messages/cloud-to-glasses';
19
+ export { TpaConnectionInit, TpaSubscriptionUpdate, RtmpStreamRequest, RtmpStreamStopRequest, TpaToCloudMessage, PhotoRequest } from './messages/tpa-to-cloud';
20
+ export { TextWall, DoubleTextWall, DashboardCard, ReferenceCard, Layout, DisplayRequest } from './layouts';
21
+ export { isButtonPress, isHeadPosition, isConnectionInit, isStartApp, isStopApp, isPhotoResponse as isPhotoResponseFromGlasses, isRtmpStreamStatus as isRtmpStreamStatusFromGlasses, isKeepAliveAck } from './messages/glasses-to-cloud';
22
+ export { isConnectionAck, isDisplayEvent, isAppStateChange, isPhotoRequest, isSettingsUpdate as isSettingsUpdateToGlasses, isStartRtmpStream, isStopRtmpStream, isKeepRtmpStreamAlive } from './messages/cloud-to-glasses';
23
+ export { isTpaConnectionInit, isTpaSubscriptionUpdate, isDisplayRequest, isRtmpStreamRequest, isRtmpStreamStopRequest, isPhotoRequest as isPhotoRequestFromTpa } from './messages/tpa-to-cloud';
24
+ export { BaseAppSetting, AppSetting, AppSettings, TpaConfig, validateTpaConfig, ToolSchema, ToolParameterSchema } from './models';
25
+ export { VideoConfig, AudioConfig, StreamConfig, StreamStatusHandler } from './rtmp-stream';
26
+ /**
27
+ * WebSocket error information
28
+ */
29
+ export interface WebSocketError {
30
+ code: string;
31
+ message: string;
32
+ details?: unknown;
33
+ }
34
+ import { Request } from 'express';
35
+ export interface AuthenticatedRequest extends Request {
36
+ authUserId?: string;
37
+ }
38
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}