Web Client Server
Цель
Нужно построить серверный слой для веб-клиентов, который умеет:
- авторизовать пользователя;
- отдавать только доступные этому пользователю приложения;
- запускать или подключать веб-клиент к выбранному приложению платформы;
- отдавать метаданные приложения и модель UI;
- держать живой двунаправленный канал для событий, команд, изменений данных и состояния интерфейса.
Новый сервер не должен быть продолжением старого HTML-интерфейса из plugin_webinterface.cpp. Старый слой рендерит окна в HTML и принимает POST-формы. Новая схема должна дать стабильный REST/WSS-контракт, поверх которого React UI сможет работать с метаданными платформы и runtime-событиями.
Общая Схема
Browser / React UI
|
| HTTPS REST + WSS
v
Web Client Server
- HTTP API
- WebSocket gateway
- auth/session middleware
- application catalog
- runtime session manager
|
| internal RPC / in-process adapter / process bridge
v
EasyControl platform runtime
- MetaProgramm
- формы и контролы
- главное меню
- права пользователя
- DB connection и бизнес-методы
Внешним протоколом, токенами, клиентскими сессиями и совместимостью API владеет web-сервер. Бизнес-логикой, интерпретацией метаданных, доступом к БД и проверкой прав владеет runtime платформы.
Основные Понятия
Principal
Авторизованный пользователь. Содержит стабильный userId, логин, отображаемое имя, роли, права, tenant/database context и опциональные claims внешнего провайдера.
Application
Запись доступного приложения платформы. Минимально содержит appId, название, иконку, версию/ревизию и runtime target. Может указывать на конкретную БД, MetaProgramm, экземпляр платформы или пул runtime-процессов.
Application catalog
Серверный список приложений, отфильтрованный по пользователю. Клиент не должен получать приложения, которые пользователь не может открыть.
Client session
Сессия логина браузера или устройства. Одна client session может создать несколько runtime session, например при открытии двух приложений в разных вкладках.
Runtime session
Живая серверная сессия выбранного приложения. Содержит runtimeSessionId, appId, principal, привязку к runtime платформы, открытые формы, UI state и WSS-канал.
Архитектурные Решения
- REST используется для логина, получения каталога приложений, bootstrap выбранного приложения и одноразовых запросов.
- WSS используется для live runtime-трафика после создания runtime session.
- Авторизация остается на сервере. Список приложений, меню, формы, поля и действия отдаются уже с учетом прав.
- Браузер не получает DB credentials и внутренние указатели платформы.
- Метаданные приложения версионируются. Bootstrap возвращает
appRevision, WSS-сообщения содержат protocol version. - Загрузка приложения создает или присоединяет runtime session. Это не просто скачивание JSON.
- Для браузера предпочтительны
HttpOnly Securecookies. Bearer token допустим для технических клиентов, но его нельзя хранить в небезопасном JS-хранилище.
Поток Запросов
1. Server Info
Запрос до логина, чтобы web shell понял возможности сервера.
GET /api/v1/server-info
Ответ:
{
"serverVersion": "1.0.0",
"protocolVersion": 1,
"auth": {
"password": true,
"sso": false
}
}
2. Login
POST /api/v1/auth/login
Content-Type: application/json
Запрос:
{
"login": "ivan",
"password": "secret",
"tenant": "main"
}
Ответ:
{
"accessToken": "opaque-or-jwt",
"expiresInSec": 900,
"refreshToken": "opaque-refresh-token",
"principal": {
"userId": "u-123",
"login": "ivan",
"displayName": "Ivan Petrov",
"roles": ["manager"]
}
}
Для браузерного сценария сервер может не возвращать токены в JSON, а выставлять access и refresh cookies с флагами HttpOnly, Secure, SameSite=Lax или строже.
3. Список Доступных Приложений
GET /api/v1/apps
Authorization: Bearer <accessToken>
Ответ:
{
"items": [
{
"appId": "sales",
"title": "Sales",
"description": "Orders and customers",
"iconUrl": "/api/v1/apps/sales/icon",
"revision": "2026.06.02.1",
"defaultRoute": "main-menu"
}
]
}
Правила:
- список фильтруется по пользователю, tenant/database, лицензии и доступности runtime;
- клиент не может выбрать приложение, которого нет в этом ответе;
- ответ кэшируется только в рамках пользователя/сессии, не глобально.
4. Создание Runtime Session
POST /api/v1/apps/{appId}/sessions
Authorization: Bearer <accessToken>
Content-Type: application/json
Запрос:
{
"client": {
"clientId": "browser-tab-uuid",
"timezone": "Asia/Tomsk",
"locale": "ru-RU"
}
}
Ответ:
{
"runtimeSessionId": "rs-8e9f",
"appId": "sales",
"appRevision": "2026.06.02.1",
"wsUrl": "wss://host/ws/v1/runtime/rs-8e9f",
"bootstrap": {
"mainMenuUrl": "/api/v1/runtime/rs-8e9f/main-menu",
"assetsBaseUrl": "/api/v1/apps/sales/assets/"
}
}
На этом шаге сервер повторно проверяет доступ к приложению. runtimeSessionId привязан к principal и не может использоваться другим пользователем.
5. Загрузка Bootstrap-Данных
GET /api/v1/runtime/{runtimeSessionId}/main-menu
Authorization: Bearer <accessToken>
Ответ:
{
"items": [
{
"id": "orders",
"title": "Orders",
"icon": "orders.png",
"enabled": true,
"visible": true,
"command": "OpenOrders",
"children": []
}
]
}
Меню уже должно быть отфильтровано по правам и локализовано. По смыслу это web-кодировка того же набора, который desktop использует через mainWindow_menuItems, но без C++ указателей и process-local id.
6. Подключение WSS
wss://host/ws/v1/runtime/{runtimeSessionId}
WebSocket авторизуется через ту же cookie-сессию или через короткоживущий одноразовый wsTicket, выданный при создании runtime session. Долгоживущий bearer token в query string использовать нельзя.
Первое сообщение клиента:
{
"type": "hello",
"protocolVersion": 1,
"runtimeSessionId": "rs-8e9f",
"clientId": "browser-tab-uuid"
}
Ответ сервера:
{
"type": "ready",
"serverTime": "2026-06-02T11:00:00Z",
"appRevision": "2026.06.02.1"
}
WSS-События
Каждое сообщение имеет общий envelope:
{
"type": "message-type",
"id": "optional-request-id",
"payload": {}
}
Базовый набор сообщений:
| Direction | Type | Назначение |
|---|---|---|
| client -> server | command.execute |
Выполнить команду меню или формы. |
| client -> server | form.open |
Открыть форму/модуль по id. |
| client -> server | form.event |
Отправить UI event: click/change/submit. |
| client -> server | data.query |
Запросить страницу данных для grid/list. |
| server -> client | form.patch |
Изменить дерево формы/контролов. |
| server -> client | data.patch |
Отправить изменения данных. |
| server -> client | notification |
Сообщение, toast, ошибка. |
| server -> client | session.expiring |
Предупредить об истечении сессии. |
| both | ping / pong |
Keepalive и latency. |
Клиент может скрывать недоступные действия, но сервер всегда остается источником истины и повторно проверяет права на каждую команду.
Модель Авторизации
Авторизация делится на три уровня:
- Server access: пользователь может войти и пользоваться web-сервером.
- Application access: пользователь может видеть и запускать конкретный
appId. - Runtime permissions: пользователь может видеть пункт меню, открыть форму, читать поле, редактировать поле, выполнить метод, запросить таблицу или запустить бизнес-действие.
Каталог приложений отвечает за уровни 1 и 2. Runtime платформы отвечает за уровень 3 через существующую модель метаданных и прав. Если web-сервер кэширует вычисленные права, ключ кэша должен включать ревизию приложения и пользователя/роль.
Источник Каталога Приложений
Каталог хранится на сервере, не в браузере. На первом этапе это может быть XML/YAML/DB config:
applications:
- appId: sales
title: Sales
tenant: main
runtime:
type: platform-process
executable: TmaPlatform.exe
database: sales_db
access:
roles: [manager, admin]
REST API не должен зависеть от конкретного формата хранения каталога.
Runtime Loading Contract
Загрузка приложения должна вернуть нормализованный web manifest:
{
"appId": "sales",
"revision": "2026.06.02.1",
"ui": {
"navigation": "side-menu",
"theme": "system"
},
"capabilities": {
"forms": true,
"reports": true,
"files": true,
"realtime": true
}
}
Правила manifest:
- содержит стабильные id, а не C++ указатели или process-local handles;
- ссылается на assets через URL или asset id;
- может кэшироваться по
appId + revision; - user-specific visibility остается в runtime-ответах, не в публичном manifest.
Ошибки
REST и WSS command replies используют один envelope:
{
"error": {
"code": "APP_ACCESS_DENIED",
"message": "Application is not available for this user.",
"details": {
"appId": "sales"
}
}
}
Начальный набор кодов:
| Code | Значение |
|---|---|
AUTH_REQUIRED |
Нет токена или он истек. |
AUTH_INVALID |
Неверные credentials или token. |
APP_ACCESS_DENIED |
Приложение недоступно пользователю. |
APP_NOT_FOUND |
Неизвестный appId. |
RUNTIME_NOT_FOUND |
Runtime session не существует или принадлежит другому пользователю. |
RUNTIME_BUSY |
Runtime платформы временно не может принять команду. |
COMMAND_DENIED |
Команда существует, но запрещена. |
VALIDATION_ERROR |
Невалидный payload запроса. |
INTERNAL_ERROR |
Неожиданная ошибка сервера или runtime. |
Security Requirements
- HTTPS и WSS обязательны вне local development.
- Password login имеет rate limit и audit log.
- Access token живет 5-15 минут.
- Refresh token revocable и привязан к client session.
- Runtime session привязана к principal, app, tenant и browser client id.
- WSS проверяет origin.
- Сервер логирует login, app session creation, denied command, runtime errors.
- DB credentials и внутренние handles платформы не уходят в браузер.
- File/image endpoints требуют app/runtime authorization.
MVP
Первый этап:
- password login через текущую базу пользователей платформы;
GET /api/v1/apps;POST /api/v1/apps/{appId}/sessions;GET /api/v1/runtime/{sessionId}/main-menu;- WSS
hello,ready,command.execute,notification,error; - audit log для login, app start, command execute, close session.
Не включаем в первый этап:
- offline mode;
- SSO;
- runtime migration между серверами;
- push updates между несколькими web-серверами;
- полный протокол form diff.
Открытые Решения
- Где хранить application catalog: XML config, DB table или admin UI.
- Делать browser auth только на cookies или сохранить bearer-token режим для части клиентов.
- Может ли один process платформы безопасно держать несколько runtime session.
- Как версионировать сериализованные формы и контролы.
- Как сопоставить существующие
MetaProgrammи menu command ids со стабильными web id. - Как инвалидировать активные сессии при изменении ролей или метаданных приложения.