Wexus Looker · amoCRM widget

Предпросмотр файлов
прямо в карточке сделки

Глазик у каждого вложения в ленте amoCRM: клик — и PDF, картинка, таблица или документ открываются в модалке без скачивания. Браузер делает максимум сам; сервер подключается только там, где без него нельзя.

Виджет · JS в странице кабинета Конвертер · Docker за nginx 13 форматов · приватность по типам
↓ листай — данные придут в движение
00 / Простыми словами

Что это и из чего состоит

Wexus Looker — виджет для amoCRM. Он добавляет кнопку-«глазик» рядом с каждым вложением в ленте сделки или контакта. Клик по глазику открывает модалку предпросмотра: файл можно посмотреть сразу, не скачивая на диск и не открывая сторонние программы.

Система состоит из двух частей и одного принципа:

Часть 1 · Виджет (в браузере пользователя)
  • Живёт в том же окне, что и amoCRM (не iframe): следит за лентой, врезает глазики, рисует модалку. Один файл script.js (~270 КБ), все зависимости встроены.
  • Файл вложения браузер качает сам, из хранилища amo — под сессией пользователя. Виджету не нужны пароли и токены: их в коде просто нет.
  • PDF, картинки, текст, markdown рендерятся прямо в браузере — ни один байт не покидает компьютер пользователя.
Часть 2 · Конвертер (наш сервер)
  • Office-файлы браузер показать не умеет — их обрабатывает наш сервис wexus-oko.naithon.one (Node + LibreOffice в Docker, за nginx).
  • Старые форматы (.doc .xls .ppt …) конвертируются в PDF прогретым пулом LibreOffice — за полсекунды, и PDF возвращается в браузер.
  • Современные (.docx .xlsx .pptx .csv) показывает Microsoft Office viewer: сервер на 5 минут публикует файл по одноразовому адресу, откуда его забирает Microsoft.

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

01 / Архитектура

Четыре участника, три потока

Браузер пользователя — центр системы: он забирает файл из хранилища amo и сам решает, рендерить локально или звать конвертер. Зелёный поток — локальные данные, жёлтый — наш сервер, красный — Microsoft viewer.

system-map browser · amo storage · converter · microsoft
fetch · same-origin · кука amo локальный рендер POST байты · office/legacy PDF назад uuid-url · TTL 5 мин viewer в iframe Браузер пользователя · страница amoCRM inject.js глазики в ленте modal.js окно + рендереры loader + cache fetch · LRU · abort pdf · image/svg · text · markdown — рендер на месте Хранилище amoCRM drive → S3 · файлы вложений Конвертер · наш сервер nginx → Node → LibreOffice wexus-oko.naithon.one Microsoft Office Online viewer docx · xlsx · pptx · csv
Виджет живёт в том же окне, что и amoCRM, поэтому файл качает браузер пользователя — под его сессией, с кукой amo. Наш сервер никогда не ходит в amoCRM сам и не видит учётных данных. Дальше — три маршрута: локальный рендер (большинство форматов), конвертация в PDF на нашем сервере (старый Office) и публикация для Microsoft viewer (современный Office).
02 / Поток клика

Что происходит за полсекунды

От клика по глазику до картинки в модалке. Маршрут выбирается по расширению файла — единственная таблица соответствия в fileUtils.js.

click-pipeline inject → modal → loader → renderer
клик · capture-фаза kind по расширению кэш? иначе fetch байты готовый предпросмотр глазик listener на document модалка + preconnect к серверам рендерер 1 файл = 1 формат loader + LRU-кэш abort · revoke · лимиты модалка показывает файл повторное открытие — из кэша, мгновенно
Клик ловится нативным обработчиком в capture-фазе — amoCRM глушит обычные клики, capture проходит всегда. Модалка заранее прогревает TLS-соединение к конвертеру (если формат серверный), пока loader качает файл. Закрытие модалки отменяет все незавершённые загрузки и освобождает память. Неизвестный формат не уходит на сервер «на всякий случай» — пользователь видит честное «скачать файл».
03 / Приватность

Куда уходит файл — по формату

Главное продуктовое решение системы. Цвет слева: зелёный — не покидает браузер, жёлтый — наш сервер транзитом, красный — обрабатывает Microsoft.

privacy-matrix формат → маршрут → что остаётся
ФорматыМаршрутЧто остаётся после показа
pdf · jpg png gif webp svg · txt json log · md рендер в браузере: blob-URL → iframe / img / pre; markdown — без HTML; скрипты в svg не исполняются ничего — память вкладки, очищается при закрытии
doc · xls · ppt · rtf · odt ods odp байты → наш конвертер → LibreOffice → PDF → назад в браузер ничего — файл живёт только в рамках запроса, имена не логируются
docx · pptx · xlsx · csv байты → наш сервер → одноразовый uuid-адрес → Microsoft Office viewer файл удаляется через 5 минут; у Microsoft — по их политике
Маппинг «расширение → маршрут» живёт в одном файле (fileUtils.js) — поведение формата меняется в одной точке. Выбор Microsoft viewer для современного Office — осознанный компромисс (идеальный рендер сложных документов), раскрытый в политике конфиденциальности; всё остальное обрабатывается своими средствами.
04 / Конвертер

Эшелоны защиты и прогретый пул

Сервис публичный (авторизация — по Origin кабинета amoCRM/Kommo), поэтому вход охраняет эшелон лимитов, а конвертирует — тёплый пул LibreOffice: полсекунды на типичный документ вместо запуска офиса с нуля.

converter-internals auth → limits → queue → warm pool | cold
тело ещё не читается окно 60 с очередь файл ≤ 2 МБ файл > 2 МБ nginx TLS · h2 · limit_req auth-гейт Origin-паттерн / токен ДО буферизации тела лимиты rate: origin + ip inflight-кап · 50 МБ p-limit (2) 2 конвертации разом кап очереди → 503 warm-пул 2 тёплых LibreOffice ~0.5 с на документ самолечение воркеров cold-путь свежий soffice на запрос не занимает пул = режим отката
Auth стоит до чтения тела: чужой запрос отбрасывается раньше, чем сервер потратит память на его файл. Origin подделывается из консоли — это принятый риск: реальную защиту даёт эшелон (rate-limit по кабинету и IP, глобальный кап запросов, очередь, лимиты nginx). Конвертация: тёплые воркеры LibreOffice для типичных файлов (~0.5 с) и холодный путь для тяжёлых (>2 МБ) — он же мгновенный откат всего режима одной переменной. Зависший воркер убивается по таймауту и перезапускается; деградировавший — лечится сам (recycle после 2 ошибок подряд и профилактически после 100 задач). Каждый запрос оставляет тайминг-запись без имён файлов и IP — только тип, размер, длительности, страна.
05 / Кэш клиента

Повторное открытие — мгновенно

Сессионный LRU-кэш в памяти вкладки (100 МБ · 16 записей): три слоя перехватывают маршрут на разной глубине. Кэшируются байты, а не ссылки — память освобождается честно.

client-cache 3 слоя · src / pdf / office · page lifetime
мимо кэша — в сеть мимо кэша — на сервер повторное открытие: все три слоя отвечают из памяти src:‹href› байты из amo S3 все форматы pdf:‹href› готовый PDF от конвертера legacy-форматы — без повторной конвертации office:‹href› preview-url Microsoft viewer TTL 5 мин − зазор · без повторного аплоада
Первый показ файла наполняет слои; повторный клик по тому же вложению не делает ни одного сетевого запроса: локальные форматы и legacy-PDF рендерятся из байтов в памяти, office переиспользует действующий preview-адрес в пределах его жизни. Кэш живёт ровно столько, сколько открыта вкладка amoCRM, — ничего не пишется на диск, приватность не меняется. Вытеснение — по объёму и числу записей (LRU); неудачные загрузки не кэшируются.
06 / Карта сервера

Эндпоинты и хранилища

Один домен за nginx, контейнер опубликован только на loopback — снаружи всё ходит через TLS-фронт.

endpoint-map wexus-oko.naithon.one · nginx → docker
wexus-oko.naithon.one · nginx 1.24 · TLS (Let's Encrypt) · http/2 ├─ POST /convert — старый Office → PDF ├─ POST /preview-host — публикация для Microsoft viewerTTL 5 минут ├─ GET /preview/‹uuid› — одноразовые файлы для viewerno-store · noindex ├─ GET /health — проверка живости ├─ /widget/ — статика виджета для ручного теста ├─ /widget-dev/ — dev-канал для проверки новых версий ├─ /legal/ — политика конфиденциальности и поддержка (ru/en) └─ контейнер wexus-converter · Docker ├─ Node (Express) · auth → лимиты → очередь · тайминг-логи ├─ LibreOffice · warm-пул ×2 + cold-путь · таймаут 40 с ├─ non-root (uid 10001) · mem 2 ГБ · pids 256 · tmpfs /tmp └─ секреты (.env) — только на серверев git не попадают
Прод-статика /widget/ всегда байт-в-байт равна релизному архиву (проверяется хэшем) — в кабинетах исполняется ровно тот код, что прошёл проверку. Новые версии живут на /widget-dev/ и не касаются боевого канала.
07 / Легенда

Как читать схемы

Потоки данных

локальные данные / живой поток
транзит через наш сервер
внешний сервис (Microsoft)

Узлы

зелёная рамка — данные не утекают
серая — обычный компонент
красная — внешняя сторона

Части системы

виджет (браузер пользователя)
конвертер (наш сервер)
пунктир — ответный/возвратный поток

Wexus Looker · системная страница. Первоисточники: CLAUDE.md, спека work_directory/01_specs/04_geo_performance_spec.md, политика конфиденциальности /legal/privacy_ru.html.

Страница автономна: один HTML + локальные шрифты, без внешних запросов и скриптов.

← Все кейсы