Для улучшения работы сайта мы используем файлы cookies. Оставаясь на сайте, вы соглашаетесь с политикой обработки персональных данных.

Подключение к приложениям Termit из стороннего клиента

Termit позволяет запускать сессии удалённого доступа в обход основного окна приложения. Благодаря этому возможно инициировать подключения из внешних систем и встраивать Termit в сторонние интерфейсы. Для интеграции необходимо выполнить следующие действия, описанные ниже.

Что такое IPC

Inter‑Process Communication (IPC) — механизм обмена данными между процессами. В Termit, десктоп-клиенте, IPC‑сервер предоставляет внешний интерфейс, через который другие приложения могут отправлять команды и получать ответы от десктоп‑клиента.

Транспорт и формат обмена

Канал связи

IPC‑сервер создаёт именованный канал (named pipe / Unix socket), работающий как потоковый TCP‑подобный транспорт.

Таблица 1. Адрес канала
ОС Путь

Windows

\\.\pipe\termit-desktop

Linux / macOS

/tmp/termit-desktop.sock

Формат сообщений

  • Формат: текстовый JSON.

  • Завершение сообщения: символ \n

  • Сервер читает входящий поток построчно.

  • В одном чанке (фрагменте данных) может содержаться несколько сообщений.

Формат запроса (Client → Server)

Каждый запрос клиента должен иметь следующую структуру:

{
  "type": "request",
  "operation": "<OPERATION_NAME>",
  "data": { ... },
  "requestId": "<string>"
}

Где:

  • type — строка, всегда должна иметь значение "request";

  • operation — имя вызываемой операции;

  • data — объект с параметрами операции (структура зависит от операции);

  • requestId — строковый идентификатор запроса, используется для сопоставления запроса и ответа.

    Ошибки формата

    При получении некорректного JSON или неверной структуре запроса сервер возвращает ответ:

BAD_REQUEST

Формат запроса (Server → Client)

Успешный ответ

{
  "type": "response",
  "requestId": "<same as in request>",
  "success": true,
  "data": <результат>
}

Ошибка

{
  "type": "error",
  "requestId": "<same as in request>",
  "success": false,
  "error": {
    "code": "<ERROR_CODE>",
    "message": "<message>"
  }
}
Коды ошибок
  • BAD_REQUEST — неверный формат запроса или структура JSON;

  • OP_FAILED — ошибка выполнения операции;

  • DUPLICATE_OPERATION — операция уже выполняется;

  • DEADLOCK — ошибка очереди или зависание.

Очередь операций

Некоторые операции не могут выполняться параллельно.

Если в запросе указано enqueue: true, сервер помещает операцию в очередь ожидания.

Если операция с тем же ключом уже выполняется, а новая помечена как enqueue: false, сервер возвращает ошибку:

{
  "type": "error",
  "requestId": "<id>",
  "success": false,
  "error": {
    "code": "DUPLICATE_OPERATION",
    "message": "<operation> already running"
  }
}

Поддерживаемые операции

  • CONNECT_BROKER

    Запрос:

    {
     "type":"request",
     "operation":"CONNECT_BROKER",
     "data":{"broker":"host"},
     "requestId":"1"
    }

    Поведение:

    • Проверяет текущее состояние подключения.

    • Если брокер не подключён — устанавливает соединение.

    • Если уже подключён к тому же адресу — возвращает ошибку.

      Успех:

      {
       "type":"response",
       "requestId":"1",
       "success":true,
       "data":true
      }
  • DISCONNECT_BROKER

    Вызывает disconnectFromBroker()

  • AUTH_USER

    Передаёт данные в authService.authByCredentials()

    Ошибка:

    OP_FAILED, "Auth failed"

  • FETCH_RESOURCES

    Вызывает resourcesService.fetchFullResources(). Ответ: Возвращает список доступных ресурсов (структура ответа зависит от реализации).

  • START_SESSION

    • Загружает ресурс по идентификатору resourceId;

    • Запускает клиентскую сессию;

    • Открывает окно клиента.

  • LOGOUT

    Вызывает logoutUser()

  • QUIT

    Поведение:

    • Если graceful = true — вызывает app.quit() (корректное завершение).

    • Если graceful = false— вызывает app.exit(0) (немедленное завершение).

для данной операции коды ошибок не предусмотрены.

Логика обработки входящих данных

  1. Клиент отправляет одну или несколько строк JSON. Каждая строка должна завершаться символом \n.

  2. Сервер буферизует полученные данные.

  3. Сервер извлекает из буфера отдельные строки.

  4. Каждая строка парсится как JSON:

    • Если парсинг завершился ошибкой, сервер возвращает ответ: BAD_REQUEST.

  5. Проверяет структуру запроса. Если операция существует:

    • при enqueue = false — операция выполняется синхронно (немедленно).

    • при enqueue = true — операция ставится в очередь и выполняется асинхронно.

  6. После завершении выполнения операции сервер отправляет ответ клиенту.

Операции, использующие очередь

Следующие операции выполняются последовательно через очередь (не параллельно):

  • CONNECT_BROKER

  • DISCONNECT_BROKER

  • AUTH_USER

  • START_SESSION

  • LOGOUT

  • QUIT

Операция`FETCH_RESOURCES` — выполняется немедленно, минуя очередь.

Пример успешного диалога

Запрос клиента (Client →):

{"type":"request","operation":"CONNECT_BROKER","data":{"broker":"b.local"},"requestId":"1"}

Ответ сервера (Server →):

{"type":"response","requestId":"1","success":true,"data":true}

Запрос клиента (Client →):

{"type":"request","operation":"FETCH_RESOURCES","data":{},"requestId":"2"}

Ответ сервера (Server →):

{"type":"response","requestId":"2","success":true,"data":[ ... ]}

Пример диалога с ошибкой

Запрос клиента (Client →):

{"type":"request","operation":"CONNECT_BROKER","data":{"broker":"b.local"},"requestId":"1"}

Ответ сервера (Server →):

{"type":"response","requestId":"1","success":true,"data":true}

Запрос клиента (Client →):

{"type":"request","operation":"CONNECT_BROKER","data":{"broker":"b.local"},"requestId":"2"}

Ответ сервера (Server →):

{
  "type":"error",
  "requestId":"2",
  "success":false,
  "error":{"code":"OP_FAILED","message":"broker already connected"}
}

Пример клиентского скрипта и предварительные действия

Предварительные действия

  1. Установите десктоп-клиент на машину, где будет выполняться IPC-взаимодействие.

  2. Запустите клиент в headless-режиме с помощью команды:

    termit-desktop.exe --headless=true

    В этом режиме клиент работает без UI, но не поднимается IPC-сервер:

    • Windows: \\.\pipe\termit-desktop;

    • Linux/macOS: /tmp/termit-desktop.sock.

  3. В брокере BROKER_FQDN скрипт поднимает сессию с ресурсом RESOURCE_NAME под пользователем USERNAME с паролем PASSWORD. Для этого в файле ipc.py укажите следующие параметры:

    BROKER_FQDN = "<адрес брокера>"
    USERNAME = "<имя пользователя>"
    PASSWORD = "<пароль>"
    RESOURCE_NAME = "<имя ресурса>"
    Пример скрипта
    import os
    import json
    import time
    import socket
    
    BROKER_FQDN = "<broker_fqdn"
    USERNAME = "<username>"
    PASSWORD = "<password>"
    RESOURCE_NAME = "<app_name>"
    
    
    def wait(ms):
        time.sleep(ms / 1000)
    
    
    def send(write_fn, data):
        line = json.dumps(data) + "\n"
        write_fn(line.encode("utf-8"))
    
    
    print("[CLIENT] connecting...")
    
    # -------------------------
    #  WINDOWS (Named Pipe)
    # -------------------------
    if os.name == "nt":
        PIPE = r"\\.\pipe\termit-desktop"
        flags = os.O_RDWR
        if hasattr(os, "O_BINARY"):
            flags |= os.O_BINARY
    
        pipe = os.open(PIPE, flags)
    
        def write_fn(data):
            os.write(pipe, data)
    
        def read_fn():
            return os.read(pipe, 4096)
    
        print("[CLIENT] connected via Windows named pipe")
    
    # -------------------------
    #  LINUX / UNIX (UNIX socket)
    # -------------------------
    else:
        PIPE = "/tmp/termit-desktop.sock"
    
        sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        sock.connect(PIPE)
    
        def write_fn(data):
            sock.sendall(data)
    
        def read_fn():
            return sock.recv(4096)
    
        print("[CLIENT] connected via UNIX socket")
    
    
    # -------------------------
    #  SEND INITIAL REQUESTS
    # -------------------------
    
    send(write_fn, {
        "type": "request",
        "operation": "CONNECT_BROKER",
        "data": {"broker": BROKER_FQDN},
        "requestId": "1"
    })
    
    wait(2000)
    
    send(write_fn, {
        "type": "request",
        "operation": "AUTH_USER",
        "data": {"username": USERNAME, "password": PASSWORD},
        "requestId": "3"
    })
    
    wait(3000)
    
    send(write_fn, {
        "type": "request",
        "operation": "FETCH_RESOURCES",
        "data": {},
        "requestId": "8"
    })
    
    wait(3000)
    
    
    # -------------------------
    #  MAIN LOOP
    # -------------------------
    
    buffer = b""
    
    while True:
        chunk = read_fn()
        if not chunk:
            break
    
        buffer += chunk
    
        while b"\n" in buffer:
            raw, buffer = buffer.split(b"\n", 1)
            if not raw.strip():
                continue
    
            try:
                json_msg = json.loads(raw.decode("utf-8"))
            except Exception:
                print("[CLIENT] parse error:", raw)
                continue
    
            print("[CLIENT] server replied:", json_msg)
    
            # -------------------------
            #  HANDLE "broker already connected"
            # -------------------------
            if (
                not json_msg.get("success")
                and json_msg.get("error", {}).get("message") == "broker already connected"
            ):
                send(write_fn, {
                    "type": "request",
                    "operation": "DISCONNECT_BROKER",
                    "data": {},
                    "requestId": "5"
                })
                wait(500)
    
                send(write_fn, {
                    "type": "request",
                    "operation": "CONNECT_BROKER",
                    "data": {"broker": BROKER_FQDN},
                    "requestId": "6"
                })
                wait(2000)
    
                send(write_fn, {
                    "type": "request",
                    "operation": "AUTH_USER",
                    "data": {"username": USERNAME, "password": PASSWORD},
                    "requestId": "3"
                })
                wait(3000)
    
            # -------------------------
            #  HANDLE FETCH_RESOURCES
            # -------------------------
            if json_msg.get("success") and json_msg.get("requestId") == "8":
                resources = json_msg.get("response", {}).get("resources", [])
                resource_id = None
    
                for resource in resources:
                    if resource.get("name") == RESOURCE_NAME:
                        resource_id = resource.get("id")
                        break
    
                if resource_id:
                    print(f"[CLIENT] using resource '{RESOURCE_NAME}' with ID: {resource_id}")
                    send(write_fn, {
                        "type": "request",
                        "operation": "START_SESSION",
                        "data": {"resourceId": resource_id},
                        "requestId": "4"
                    })
                else:
                    print(f"[CLIENT] resource '{RESOURCE_NAME}' not found")