UNPKG

homebridge-config-ui-x

Version:

A web based management, configuration and control platform for Homebridge.

250 lines • 11.9 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; var TerminalService_1; Object.defineProperty(exports, "__esModule", { value: true }); exports.TerminalService = void 0; const node_os_1 = __importDefault(require("node:os")); const node_process_1 = __importDefault(require("node:process")); const common_1 = require("@nestjs/common"); const fs_extra_1 = require("fs-extra"); const config_service_1 = require("../../../core/config/config.service"); const logger_service_1 = require("../../../core/logger/logger.service"); const node_pty_service_1 = require("../../../core/node-pty/node-pty.service"); let TerminalService = TerminalService_1 = class TerminalService { constructor(configService, logger, nodePtyService) { this.configService = configService; this.logger = logger; this.nodePtyService = nodePtyService; this.ending = false; this.instanceId = Math.random().toString(36).substring(2, 11); this.logger.debug(`TerminalService instance created: ${this.instanceId}`); } async getPreferredShell() { if (node_os_1.default.platform() === 'darwin' && await (0, fs_extra_1.pathExists)('/bin/zsh')) { return '/bin/zsh'; } return await (0, fs_extra_1.pathExists)('/bin/bash') ? '/bin/bash' : '/bin/sh'; } async startSession(client, size) { this.ending = false; if (!this.configService.enableTerminalAccess) { this.logger.warn('Terminal is not enabled, disconnecting client...'); client.disconnect(); return; } const terminalPersistence = Boolean(this.configService.ui.terminal?.persistence); if (terminalPersistence) { return this.attachToPersistentTerminal(client, size); } else { return this.createNewTerminal(client, size); } } async createNewTerminal(client, size) { this.logger.log('Starting new terminal session.'); const shell = await this.getPreferredShell(); const term = this.nodePtyService.spawn(shell, [], { name: 'xterm-color', cols: size.cols, rows: size.rows, cwd: this.configService.storagePath, env: node_process_1.default.env, }); term.onData((data) => { client.emit('stdout', data); }); term.onExit((exitInfo) => { try { if (!this.ending) { client.emit('process-exit', exitInfo.exitCode); } } catch (e) { } }); client.on('stdin', (data) => { term.write(data); }); client.on('resize', (resize) => { try { term.resize(resize.cols, resize.rows); } catch (e) { } }); const onEnd = () => { this.ending = true; client.removeAllListeners('stdin'); client.removeAllListeners('resize'); client.removeAllListeners('end'); client.removeAllListeners('disconnect'); try { this.logger.log('Terminal session ended.'); term.kill(); } catch (e) { } }; client.on('end', onEnd.bind(this)); client.on('disconnect', onEnd.bind(this)); } async attachToPersistentTerminal(client, size) { this.logger.debug(`[${this.instanceId}] attachToPersistentTerminal called`); if (!TerminalService_1.persistentTerminal) { this.logger.debug(`[${this.instanceId}] Creating new persistent terminal session.`); const shell = await this.getPreferredShell(); TerminalService_1.persistentTerminal = this.nodePtyService.spawn(shell, [], { name: 'xterm-color', cols: size.cols, rows: size.rows, cwd: this.configService.storagePath, env: node_process_1.default.env, }); if (!TerminalService_1.dataListenerAttached) { this.logger.debug(`[${this.instanceId}] Attaching data listener`); TerminalService_1.persistentTerminal.onData((data) => { try { this.logger.debug(`[${this.instanceId}] Terminal output: ${data.length} characters`); TerminalService_1.terminalBuffer += data; const maxBufferSize = this.configService.ui.terminal?.bufferSize; if (TerminalService_1.terminalBuffer.length > maxBufferSize) { TerminalService_1.terminalBuffer = TerminalService_1.terminalBuffer.slice(-maxBufferSize); } if (TerminalService_1.connectedClients.size > 0) { this.logger.debug(`[${this.instanceId}] Sending output to ${TerminalService_1.connectedClients.size} connected clients`); TerminalService_1.connectedClients.forEach((client) => { try { client.emit('stdout', data); } catch (e) { this.logger.error(`[${this.instanceId}] Error sending output to a client: ${e}`); TerminalService_1.connectedClients.delete(client); } }); } } catch (e) { this.logger.error(`[${this.instanceId}] Error sending output to client: ${e}`); } }); TerminalService_1.dataListenerAttached = true; } TerminalService_1.persistentTerminal.onExit((exitInfo) => { this.logger.debug(`[${this.instanceId}] Persistent terminal exited.`); TerminalService_1.connectedClients.forEach((client) => { try { client.emit('process-exit', exitInfo.exitCode); } catch (e) { TerminalService_1.connectedClients.delete(client); } }); TerminalService_1.persistentTerminal = null; TerminalService_1.connectedClients.clear(); TerminalService_1.dataListenerAttached = false; TerminalService_1.terminalBuffer = ''; }); } else { this.logger.debug(`[${this.instanceId}] Attaching to existing persistent terminal.`); try { TerminalService_1.persistentTerminal.resize(size.cols, size.rows); } catch (e) { } } this.logger.debug(`[${this.instanceId}] Cleaning up existing client listeners`); client.removeAllListeners('stdin'); client.removeAllListeners('resize'); this.logger.debug(`[${this.instanceId}] Adding client to connected clients`); TerminalService_1.connectedClients.add(client); if (TerminalService_1.terminalBuffer && TerminalService_1.terminalBuffer.length > 0) { this.logger.debug(`[${this.instanceId}] Sending ${TerminalService_1.terminalBuffer.length} chars of buffer to new client`); try { client.emit('stdout', TerminalService_1.terminalBuffer); } catch (e) { this.logger.error(`[${this.instanceId}] Error sending buffer to client: ${e}`); } } else { this.logger.debug(`[${this.instanceId}] No buffer to send to new client`); } this.logger.debug(`[${this.instanceId}] Adding stdin and resize listeners`); client.on('stdin', (data) => { this.logger.debug(`[${this.instanceId}] Received stdin from client: ${data.length} characters`); if (TerminalService_1.persistentTerminal) { this.logger.debug(`[${this.instanceId}] Writing to persistent terminal: ${data.length} characters`); TerminalService_1.persistentTerminal.write(data); } else { this.logger.warn(`[${this.instanceId}] No persistent terminal to write to!`); } }); client.on('resize', (resize) => { this.logger.debug(`[${this.instanceId}] Received resize from client`); try { if (TerminalService_1.persistentTerminal) { TerminalService_1.persistentTerminal.resize(resize.cols, resize.rows); } } catch (e) { } }); const onEnd = () => { this.logger.debug(`[${this.instanceId}] Client disconnecting`); client.removeAllListeners('stdin'); client.removeAllListeners('resize'); client.removeAllListeners('end'); client.removeAllListeners('disconnect'); if (TerminalService_1.connectedClients.has(client)) { TerminalService_1.connectedClients.delete(client); this.logger.debug(`[${this.instanceId}] Removed client from connected clients`); } this.logger.debug(`[${this.instanceId}] Client cleanup complete`); }; client.on('end', onEnd); client.on('disconnect', onEnd); } hasPersistentSession() { const hasPersistent = TerminalService_1.persistentTerminal !== null; this.logger.debug(`[${this.instanceId}] hasPersistentSession: ${hasPersistent}`); return hasPersistent; } destroyPersistentSession() { this.logger.debug(`[${this.instanceId}] Destroying persistent terminal session`); if (TerminalService_1.persistentTerminal) { try { this.logger.debug(`[${this.instanceId}] Killing persistent terminal process`); TerminalService_1.persistentTerminal.kill(); } catch (e) { this.logger.error(`[${this.instanceId}] Error killing persistent terminal: ${e}`); } TerminalService_1.persistentTerminal = null; } TerminalService_1.terminalBuffer = ''; TerminalService_1.dataListenerAttached = false; TerminalService_1.connectedClients.clear(); this.logger.debug(`[${this.instanceId}] Persistent terminal session destroyed`); } }; exports.TerminalService = TerminalService; TerminalService.persistentTerminal = null; TerminalService.connectedClients = new Set(); TerminalService.dataListenerAttached = false; TerminalService.terminalBuffer = ''; exports.TerminalService = TerminalService = TerminalService_1 = __decorate([ (0, common_1.Injectable)(), __metadata("design:paramtypes", [config_service_1.ConfigService, logger_service_1.Logger, node_pty_service_1.NodePtyService]) ], TerminalService); //# sourceMappingURL=terminal.service.js.map