Exchange Calendar
Фоновый сервис + виджет: недельный календарь Exchange/OWA через EWS (только чтение). Логин: шаблон {{user.login}}, пароль: личная настройка пользователя.
Фоновый сервис
Пакет запускается платформой как долгоживущий процесс. Отдельные навыки в каталоге чата он не регистрирует, зато отдаёт виджеты и HTTP-интерфейс.
Виджеты · 1
- Календарь Exchange
/Добавляется на доску виджетов и в панель виджетов EVA Desktop.
Настройки пакета · 8
Подключение
- URL EWS
ews_urlобязательнообщееТочка подключения Exchange Web Services, напр. https://mail.example.com/EWS/Exchange.asmx
- Тип аутентификации
auth_typeобщееntlm: доменная авторизация (нужен «Домен»); basic: обычная Basic по HTTPS.
- Домен (для NTLM)
domainобщееОставьте пустым, если NTLM не используется. Можно указывать как DOMAIN или DOMAIN\.
Учётные данные
- Логин Exchange
exchange_loginобязательнообщееШаблон логина. По умолчанию {{user.login}}: логин берётся у текущего пользователя. Можно вписать фиксированный сервисный логин.
- Пароль Exchange
exchange_passwordобязательносекретна пользователяЛичный пароль каждого пользователя (персональная настройка). В фоне не виден: попадает только в запросы этого пользователя.
Отображение
- Часовой пояс
timezoneобщееПояс, в котором рассчитывается неделя Пн-Вс. IANA, напр. Europe/Moscow.
- Максимум встреч за запрос
max_itemsобщееОграничение CalendarView (1-2000). По умолчанию 500.
- Проверять TLS-сертификат
verify_tlsобщееОтключите только для OWA с самоподписанным сертификатом.
Зависит от SDK
README
# Exchange Calendar
Фоновый сервис + виджет: **персональный** недельный календарь Exchange/OWA через EWS. **Только чтение.**
## Как берутся учётные данные
Фоновый сервис: одиночный демон, у него нет пользовательского контекста. Поэтому в `main(ctx)`
доступны только global/daemon-safe настройки, а персональные значения приходят **в HTTP-обработчики
виджета**, после авторизации запроса:
| значение | ключ и scope | как читается |
|---|---|---|
| логин | `exchange_login`, `global` + `x-eva-template: true`, значение по умолчанию `{{user.login}}` | `req.ctx.settings.exchange_login` (шаблон раскрыт в логин текущего пользователя) |
| пароль | `exchange_password`, `x-eva-scope: user`, `format: password` | `req.ctx.settings.exchange_password` |
| подключение | `ews_url`, `auth_type`, `domain`, `timezone`, `verify_tls`, `max_items`: все `global` | корневой `ctx.settings` в `main(ctx)` |
В `main(ctx)` ключей `exchange_login` (как шаблон с `{{user.*}}`) и `exchange_password` **нет**: чтение такого ключа из корневого контекста намеренно бросает Error, поэтому `src/config.js`
читает их только из контекста запроса (`req.ctx` / 3-й аргумент handler / `getContext()`).
Пароль пользователя нигде не логируется и в ответах API не возвращается.
Он хранится только в реестре настроек платформы (личная настройка пользователя).
## HTTP API
Корень пакета: `/internal/bg-service/exchange-calendar/` (проксирует платформа, свои порты слушать не нужно).
| метод | путь | описание |
|---|---|---|
| GET | `/api/health` | конфиг без секретов, пользователь, полнота личных настроек |
| GET | `/api/health?deep=1` | то же + реальный запрос в EWS (`Folder.Bind`) |
| GET | `/api/me` | кто пользователь и хватает ли личных настроек (+`hint`) |
| GET | `/api/diagnose` | доступность EWS и его схемы авторизации: **без учётных данных** |
| GET | `/api/week?start=YYYY-MM-DD&tz=Europe/Moscow` | неделя Пн-Вс + встречи текущего пользователя |
Ответ `/api/week`:
```json
{
"ok": true,
"timezone": "Europe/Moscow",
"user": { "id": "u_1", "login": "user@example.com", "name": "Пользователь" },
"credentials": "personal",
"week": { "start_date": "2026-10-12", "end_date": "2026-10-18", "today": "2026-10-16", "days": [] },
"events": [{ "id": "…", "subject": "…", "start": "…", "end": "…", "all_day": false, "location": "…", "organizer": "…", "free_busy": "Busy" }],
"count": 1,
"fetched_at": "…",
"notes": ["ntlm as DOMAIN\\us***"]
}
```
Ошибки:
- `503 not_configured`: не задан `ews_url`;
- `401 need_settings`: у пользователя не задан личный пароль (`missing_personal`) или не определён логин;
- `502` + `error.code` (`unauthorized` | `timeout` | `dns` | `tls` | `ews_error`).
Блок `week` возвращается и в ошибочных ответах, поэтому сетка остаётся на экране.
### Диагностика
`GET /api/diagnose` отправляет SOAP-запрос в EWS **без логина и пароля**: сервер обязан ответить
401 и заголовком `WWW-Authenticate`. Из него видно, что требует ваш OWA:
- `Negotiate`/`NTLM` → в настройках нужен `auth_type = ntlm` + «Домен»;
- `Basic` → `auth_type = basic` (только HTTPS);
- ошибка `dns`/`timeout`/`tls` → сервер не достижим из контейнера платформы (VPN, фаервол, DNS)
или не проходит проверка сертификата.
Та же проверка доступна кнопкой **«Диагностика»** в шапке виджета.
## Виджет
`eva.widgets[].path = "/"`, поэтому фронт берёт base от `location.pathname`.
Сетка: Пн-Вс, часы 07:00-21:00, события позиционируются по минутам в целевом поясе,
красная линия: текущее время, автообновление раз в 5 минут, стрелки: навигация по неделям.
## Настройки
| ключ | scope | назначение |
|---|---|---|
| `ews_url` | global | точка EWS, напр. `https://mail.example.com/EWS/Exchange.asmx` |
| `auth_type` | global | `ntlm` (по умолчанию) или `basic` |
| `domain` | global | домен для NTLM (`DOMAIN` или `DOMAIN\`) |
| `exchange_login` | global, template | шаблон логина, по умолчанию `{{user.login}}` |
| `exchange_password` | **user**, secret | личный пароль Exchange: задаёт каждый пользователь у себя |
| `timezone` | global | пояс расчёта недели, по умолчанию `Europe/Moscow` |
| `verify_tls` | global | проверять TLS-сертификат |
| `max_items` | global | максимум встреч за запрос (1-2000) |
## Проверка
```bash
URL=http://platform:3000/internal/bg-service/exchange-calendar
curl -s "$URL/api/health" | jq
curl -s "$URL/api/diagnose" | jq '{hint, http}' # какие схемы авторизации предлагает OWA
curl -s "$URL/api/me" | jq '{user, credentials, missing_personal}'
curl -s "$URL/api/week" | jq '.count, .events[0]' # в контексте пользователя: логин из {{user.login}}
```
Из Studio-чата эндпоинты без пользовательского контекста покажут `credentials: "incomplete"`: это ожидаемо: личный пароль виден только в запросах самого пользователя.
## Технические детали
- **basic**: `service.Credentials = new WebCredentials(login, password)`; только по HTTPS.
- **ntlm**: `new XhrApi({ gzip: true }).useNtlmAuthentication('DOMAIN\\user', password)` +
`ConfigurationApi.ConfigureXHR(xhr)`. `gzip: true` обязателен: иначе ломается разбор сжатых
ответов (issue #334 у `ews-javascript-api`). NTLM-провайдер настраивается глобально, поэтому
NTLM-запросы сериализуются (`withAuthLock`), а basic-запросы идут параллельно.
- Кэша недели нет: каждый `/api/week`: это `FindAppointments` (и `Folder.Bind` в deep-проверке).
При частых обновлениях логично добавить кэш на 1-2 минуты с ключом «пользователь + неделя».
- Окно виджета 07:00-21:00 пока захардкожено.Как подключить
Администратор загружает пакет в разделе Админ → Пакеты или публикует его из Студии. После публикации пакет появляется у пользователей: навыки в каталоге Навыки, а виджеты фоновых сервисов на доске виджетов. Настройки с меткой «общее» задаёт администратор, с меткой «на пользователя» каждый пользователь заполняет у себя.
Подробнее о жизненном цикле пакета: Студия: публикация и версии. Весь каталог: Пакеты EVA.