Complete lifecycle of user sessions from creation to disposal
/glasses-ws
// In WebSocketService if (url.pathname === '/glasses-ws') { // Extract and verify JWT token const coreToken = request.headers.authorization?.split(' ')[1]; const userData = jwt.verify(coreToken, AUGMENTOS_AUTH_JWT_SECRET); const userId = userData.email; // Attach userId to request (request as any).userId = userId; // Proceed with WebSocket upgrade this.glassesWss.handleUpgrade(request, socket, head, ws => { this.glassesWss.emit('connection', ws, request); }); }
email
sub
AUGMENTOS_AUTH_JWT_SECRET
// In SessionService const userSession = new UserSession(userId, ws); // Initialize with installed apps const installedApps = await appService.getAllApps(userId); for (const app of installedApps) { userSession.installedApps.set(app.packageName, app); } return { userSession, reconnection: false };
const existingSession = UserSession.getById(userId); if (existingSession) { // Update WebSocket and cancel cleanup existingSession.updateWebSocket(ws); existingSession.disconnectedAt = null; if (existingSession.cleanupTimerId) { clearTimeout(existingSession.cleanupTimerId); existingSession.cleanupTimerId = undefined; } return { userSession: existingSession, reconnection: true }; }
constructor(userId: string, websocket: WebSocket) { // Core identification this.userId = userId; this.websocket = websocket; // Initialize managers this.appManager = new AppManager(this); this.audioManager = new AudioManager(this); this.displayManager = new DisplayManager(this); this.transcriptionManager = new TranscriptionManager(this); // ... other managers // Start heartbeat this.setupGlassesHeartbeat(); // Register in storage SessionStorage.getInstance().set(userId, this); }
private setupGlassesHeartbeat(): void { const HEARTBEAT_INTERVAL = 10000; // 10 seconds this.glassesHeartbeatInterval = setInterval(() => { if (this.websocket.readyState === WebSocket.OPEN) { this.websocket.ping(); } else { this.clearGlassesHeartbeat(); } }, HEARTBEAT_INTERVAL); }
// In GlassesWebSocketService (websocket-glasses.service.ts:558-566) const connectionAck = { type: CloudToGlassesMessageType.CONNECTION_ACK, sessionId: userSession.sessionId, userSession: await sessionService.transformUserSessionForClient(userSession), timestamp: new Date() }; ws.send(JSON.stringify(connectionAck));
// In GlassesWebSocketService handleDisconnect userSession.disconnectedAt = new Date(); // Set cleanup timer (5 minutes default) const CLEANUP_TIMEOUT = 5 * 60 * 1000; userSession.cleanupTimerId = setTimeout(async () => { logger.info(`Cleaning up session for user ${userId} after grace period`); // Stop all running apps for (const packageName of userSession.runningApps) { await userSession.appManager.stopApp(packageName); } // Dispose session await userSession.dispose(); }, CLEANUP_TIMEOUT);
async dispose(): Promise<void> { // Track session metrics const duration = Date.now() - this.startTime.getTime(); await PosthogService.trackEvent("disconnected", this.userId, { duration, disconnectedAt: new Date().toISOString() }); // Dispose all managers if (this.appManager) this.appManager.dispose(); if (this.audioManager) this.audioManager.dispose(); if (this.microphoneManager) this.microphoneManager.dispose(); // ... dispose all managers // Clear heartbeat this.clearGlassesHeartbeat(); // Clear timers if (this.cleanupTimerId) { clearTimeout(this.cleanupTimerId); } // Remove from storage SessionStorage.getInstance().delete(this.userId); // Clean up subscriptions subscriptionService.removeAllSubscriptionsForSession(this.userId); }
// Example: AudioManager disposal dispose(): void { // Clear intervals if (this.bufferCleanupInterval) { clearInterval(this.bufferCleanupInterval); } // Clear buffers this.bufferedAudio = []; this.recentAudioBuffer = []; // Remove event listeners this.removeAllListeners(); }
// In SubscriptionService removeAllSubscriptionsForSession(sessionId: string): void { for (const key of this.subscriptions.keys()) { if (key.startsWith(`${sessionId}:`)) { this.subscriptions.delete(key); } } // Clear caches this.calendarEventsCache.delete(sessionId); this.lastLocationCache.delete(sessionId); }
enum SessionState { CONNECTING, // Initial connection ACTIVE, // Normal operation DISCONNECTED, // Connection lost, grace period DISPOSING, // Being cleaned up DISPOSED // Fully removed }
// Send error before closing userSession.sendError( "Authentication failed", GlassesErrorCode.AUTHENTICATION_FAILED ); // Clean close ws.close(1008, "Authentication failed");
try { await manager.dispose(); } catch (error) { logger.error(`Failed to dispose manager: ${error}`); // Continue with other disposals }