npm package: @horuzhenko/guac-adapter
Description: Адаптер для взаимодействия с Apache Guacamole
Latest version: 6.0.19

 b# @horuzhenko/guac-adapter

Адаптер для взаимодействия с Apache Guacamole в Node.js среде.

## Возможности

- ✅ Подключение к Guacamole серверу через REST API
- ✅ Установка WebSocket соединения с guacd
- ✅ Рендеринг удаленного рабочего стола в виртуальный canvas
- ✅ Создание скриншотов в форматах PNG и WebP
- ✅ Управление сессиями подключения
- ✅ Обработка Guacamole протокола
- ✅ **Интерактивный ввод:**
  - Движение мыши
  - Клики левой, правой и средней кнопкой
  - Прокрутка колесом мыши
  - Комбинации клавиш (Ctrl+C, Alt+Tab и т.д.)
  - Ввод текста с настраиваемыми задержками

## Установка

```bash
pnpm add @horuzhenko/guac-adapter
```

## Использование

### Базовое подключение

```typescript
import { GuacAdapter } from '@horuzhenko/guac-adapter';
import { ConnectParams } from '@horuzhenko/protocol';

const adapter = new GuacAdapter();

const connectParams: ConnectParams = {
  auth: {
    baseUrl: 'http://192.168.2.109:8080/guacamole',
    token: 'your-auth-token'
  },
  display: {
    width: 1024,
    height: 768,
    dpi: 96
  }
};

// Подключение
const sessionInfo = await adapter.connect(connectParams);
console.log('Сессия создана:', sessionInfo.id);

// Ожидание готовности соединения
await new Promise(resolve => setTimeout(resolve, 5000));

// Создание скриншота
const screenshot = await adapter.screenshot(sessionInfo.id, {
  format: 'png',
  quality: 80
});

// Сохранение скриншота
const fs = require('fs');
const imageBuffer = Buffer.from(screenshot.imageBase64, 'base64');
fs.writeFileSync('screenshot.png', imageBuffer);

// Интерактивный ввод
console.log('Демонстрация интерактивного ввода...');

// Движение мыши
await adapter.move(sessionInfo.id, { x: 100, y: 100 });

// Клик левой кнопкой
await adapter.click(sessionInfo.id, { x: 100, y: 100, button: 'left' });

// Двойной клик
await adapter.click(sessionInfo.id, { x: 100, y: 100, button: 'left', count: 2 });

// Комбинация клавиш (Ctrl+Alt+T для открытия терминала)
await adapter.keyCombo(sessionInfo.id, {
  keys: ['Ctrl', 'Alt', 't'],
  pressDelayMs: 100
});

// Ввод текста
await adapter.typeText(sessionInfo.id, {
  text: 'echo "Hello World!"',
  perCharDelayMs: 50
});

// Нажатие Enter
await adapter.keyCombo(sessionInfo.id, { keys: ['Return'] });

// Прокрутка
await adapter.scroll(sessionInfo.id, { x: 400, y: 300, deltaY: -240 });

// Отключение
await adapter.disconnect(sessionInfo.id);
```

### Создание скриншотов

```typescript
// PNG скриншот
const pngScreenshot = await adapter.screenshot(sessionId, {
  format: 'png'
});

// WebP скриншот с настройкой качества
const webpScreenshot = await adapter.screenshot(sessionId, {
  format: 'webp',
  quality: 70
});
```

## API

### GuacAdapter

#### `connect(params: ConnectParams): Promise<SessionInfo>`

Создает новое подключение к удаленному рабочему столу.

**Параметры:**
- `params.auth.baseUrl` - URL Guacamole сервера
- `params.auth.token` - Токен аутентификации
- `params.display` - Настройки дисплея (опционально)

**Возвращает:** Информацию о созданной сессии

#### `screenshot(sessionId: string, options?): Promise<Screenshot>`

Создает скриншот удаленного рабочего стола.

**Параметры:**
- `sessionId` - Идентификатор сессии
- `options.format` - Формат изображения ('png' | 'webp')
- `options.quality` - Качество для WebP (1-100)

**Возвращает:** Объект скриншота с base64 данными

#### `move(sessionId: string, move: MouseMove): Promise<void>`

Перемещает курсор мыши в указанные координаты.

**Параметры:**
- `sessionId` - Идентификатор сессии
- `move.x` - Координата X
- `move.y` - Координата Y

#### `click(sessionId: string, click: MouseClick): Promise<void>`

Выполняет клик мыши в указанных координатах.

**Параметры:**
- `sessionId` - Идентификатор сессии
- `click.x` - Координата X
- `click.y` - Координата Y
- `click.button` - Кнопка мыши ('left' | 'right' | 'middle')
- `click.count` - Количество кликов (опционально, по умолчанию 1)

#### `scroll(sessionId: string, scroll: Scroll): Promise<void>`

Выполняет прокрутку в указанных координатах.

**Параметры:**
- `sessionId` - Идентификатор сессии
- `scroll.x` - Координата X
- `scroll.y` - Координата Y
- `scroll.deltaY` - Величина прокрутки (положительная - вниз, отрицательная - вверх)

#### `keyCombo(sessionId: string, combo: KeyCombo): Promise<void>`

Выполняет комбинацию клавиш.

**Параметры:**
- `sessionId` - Идентификатор сессии
- `combo.keys` - Массив клавиш (['Ctrl', 'c'], ['Alt', 'Tab'])
- `combo.pressDelayMs` - Задержка между нажатиями (опционально)

#### `typeText(sessionId: string, input: TypeText): Promise<void>`

Вводит текст символ за символом.

**Параметры:**
- `sessionId` - Идентификатор сессии
- `input.text` - Текст для ввода
- `input.perCharDelayMs` - Задержка между символами (опционально)

#### `clipboardSet(sessionId: string, data: ClipboardSet): Promise<void>`

Записывает текстовые данные в буфер обмена удаленной машины.

**Параметры:**
- `sessionId` - Идентификатор сессии
- `data.text` - Текст для записи в буфер обмена
- `data.mime` - MIME-тип (по умолчанию 'text/plain')

**Важно:** Поддерживается передача только текстовых данных. Перенос файлов через буфер обмена не реализован. Стабильность синхронизации буфера обмена сильно зависит от используемого протокола (RDP работает надежнее, VNC имеет ограничения) и настроек сервера Guacamole. Возможны проблемы с кодировками при использовании VNC, так как стандарт не определяет передачу UTF-8.

#### `clipboardGet(sessionId: string, query?: ClipboardGet): Promise<ClipboardGet>`

Читает текстовые данные из буфера обмена удаленной машины.

**Параметры:**
- `sessionId` - Идентификатор сессии
- `query.selection` - Тип буфера ('CLIPBOARD' или 'PRIMARY', по умолчанию 'CLIPBOARD')

**Возвращает:** Объект с полями `text`, `mime`, `selection`

#### `disconnect(sessionId: string): Promise<void>`

Отключает сессию и освобождает ресурсы.

## Работа с клавиатурой и модификаторами

### Клавиши-модификаторы

Строковые псевдонимы `Meta`, `Cmd`, `Win`, `Windows`, `Command` преобразуются в один и тот же keysym `Super_L`. Guacamole-сервер и удаленная ОС сами решают, как его интерпретировать:
- На macOS: интерпретируется как ⌘ (Command)
- На Windows: интерпретируется как Win (клавиша Windows)
- На Linux: интерпретируется как Super

### Важное предупреждение

⚠️ **Операционная система или браузер, на которых запущен клиентский код, могут перехватывать системные комбинации клавиш** (например, `Alt+Tab`, `Cmd+Q`, `Ctrl+Alt+Delete`). Такие комбинации могут не дойти до удаленной машины. Это является ограничением окружения, а не адаптера.

Примеры проблемных комбинаций:
- `Alt+Tab` - переключение окон в локальной ОС
- `Cmd+Q` / `Alt+F4` - закрытие приложений
- `Ctrl+Alt+Delete` - системные функции
- `Win+L` - блокировка экрана Windows

## Архитектура

Адаптер работает по следующей схеме:

1. **Аутентификация** - подключение к Guacamole REST API
2. **WebSocket соединение** - установка связи с guacd
3. **Протокол Guacamole** - обмен инструкциями с сервером
4. **Canvas рендеринг** - отрисовка графических команд в памяти
5. **Скриншоты** - извлечение изображений из canvas

## Требования

- Node.js >= 18.0.0
- Запущенный Apache Guacamole сервер
- Доступ к guacd (обычно порт 4822)

## Тестирование

### Базовые тесты

```bash
pnpm test
```

### Интеграционные тесты

Для запуска интеграционных тестов нужен запущенный Guacamole сервер:

```bash
INTEGRATION_TESTS=true pnpm test
```

Интеграционные тесты:
- Подключаются к реальному Guacamole серверу
- Создают скриншоты и сохраняют их в файлы
- Проверяют полный цикл работы адаптера

## Отладка

Адаптер выводит отладочную информацию в консоль с префиксом `[DEBUG]`. Для продакшена эти логи можно отключить.

## Ограничения

- Поддерживается только VNC протокол (в текущей версии)
- Требуется предварительная настройка Guacamole сервера
- Canvas рендеринг работает только в Node.js среде

## Разработка

```bash
# Сборка
pnpm build

# Тесты
pnpm test

# Разработка с автоперезагрузкой
pnpm dev
```