Skip to content

Формат widget.zip ​

Релиз — это один ZIP-архив. Вы загружаете его в релиз, Bito проверяет архив до того, как распакует хотя бы один байт, и сохраняет только при успешной проверке. Отчёт возвращается вам сразу же — каждая ошибка называет нарушенное правило.

Структура каталогов ​

text
widget.zip
├── manifest.json        обязателен, в корне архива
├── script.js            обязателен, в корне архива
├── style.css            необязателен
├── i18n/
│   ├── uz.json          по файлу на каждый язык из манифеста
│   ├── ru.json
│   └── en.json
├── templates/           необязателен, имена любые
│   └── card.twig
└── images/              необязателен
    ├── logo.png
    └── menu.png         необязателен, иконка левого меню

manifest.json и script.js должны лежать в корне, а не внутри папки. Заархивировать каталог вместо его содержимого — самая частая причина отказа при загрузке.

manifest.json ​

json
{
    "widget": { "interface_version": 2 },
    "bito": {
        "schema_version": 1,
        "locations": {
            "surfaces": ["customer_profile", "card_phone", "list:customers"],
            "persistent": false
        },
        "network": {
            "connect_origins": ["https://api.example.com", "wss://events.example.com"]
        },
        "pipeline_endpoint": "https://api.example.com/bito/pipeline",
        "left_menu": {
            "widget_page": {
                "title_key": "app_title",
                "icon": "images/menu.png",
                "submenu": {
                    "deals": { "title_key": "deals" },
                    "reports": { "title_key": "reports" }
                }
            },
            "saleMain": {
                "submenu": { "order_sync": { "title_key": "order_sync" } }
            }
        }
    },
    "languages": ["uz", "ru", "en"],
    "default_language": "uz"
}
ПолеОбязательноПравила
widget.interface_versionДаЧисло. 2 — текущий внутристраничный рантайм.
bito.schema_versionДаЧисло. Текущее значение — 1.
languagesДаМассив кодов языков. Для каждого в архиве должен быть i18n/<lang>.json.
default_languageДаСтрока. Используется, если языка пользователя нет в languages.
bito.locations.surfacesНетМассив точек монтирования. Каждый элемент — именованная точка, list:<page_name> или global. Дубликаты отклоняются.
bito.locations.persistentНетБулево. true включает постоянный, всегда смонтированный слот. Указание global в surfaces включает его тоже — считаются обе формы.
bito.network.connect_originsНетМассив точных origin. Только раскрытие — см. ниже.
bito.pipeline_endpointНетТочный https:// URL. Туда воронка заказов отправляет действие widget_action.
bito.left_menuНетСтраницы в левом меню админ-панели: собственный раздел виджета и/или страницы подменю в существующих модулях. Требует код виджета у приложения; см. левое меню.

Всё, что не разбирается как JSON или не совпадает с этой формой, — ошибка валидации с сообщением, а не серверная ошибка.

connect_origins — раскрытие, а не ограничение ​

DANGER

bito.network.connect_origins показывается компании на экране согласия и никогда не применяется технически. Ничто не мешает вашему виджету — или его скомпрометированной версии — обратиться к адресу, которого нет в этом списке.

Применить его и невозможно: CSP уровня страницы действует на весь документ и не может быть ограничен одним скриптом, а ваш код работает в странице хоста. Считайте это поле декларацией, которую вы даёте компании, а не ограничением, которое Bito накладывает на вас. Не описывайте его своим пользователям как песочницу.

Форма при загрузке проверяется, чтобы на экране согласия был аккуратный список:

  • только точный origin https:// или wss://;
  • никаких подстановок — символ * в строке означает немедленный отказ;
  • никакой «голой» схемы (https:);
  • ни пути, ни query, ни фрагмента, ни завершающего слеша — строка должна совпадать сама с собой как origin.

pipeline_endpoint ​

Если компания настроит на этапе воронки заказов действие типа widget_action для вашего приложения, Bito возьмёт адрес из манифеста вашего согласованного релиза — компания не вводит URL вручную. Отправляется POST:

json
{
    "event": "pipeline_action",
    "db_name": "tenant_db",
    "sale_order_id": "6712f0a9c4d1e87b3a0f1122",
    "organization_id": "6712f0a9c4d1e87b3a0f1130",
    "state_id": "6712f0a9c4d1e87b3a0f1140",
    "client_id": "69c50a2d50f7b939923a469b"
}

При загрузке проверяется только форма: это должен быть точный https:// URL. При отправке адрес проверяется повторно — не-HTTPS, loopback, приватные и link-local адреса отклоняются, а у запроса есть жёсткий таймаут.

Левое меню ​

bito.left_menu даёт установленному виджету собственные страницы в левом меню админ-панели. Он построен по образцу left_menu и колбэка initMenuPage из amoCRM. Пункты меню и заголовок страницы рисует Bito; содержимое страницы — то, что отрисует ваш виджет. Каждая такая страница монтируется в точке widget_page.

bito.left_menu заменяет bito.settings_page и bito.menu. Оба поля удалены: манифест, в котором ещё объявлено любое из них, отклоняется с сообщением, указывающим на left_menu.

Каждый ключ left_menu — это блок: widget_page или ключ модуля.

БлокПоляПравила
widget_pagetitle_key (обязательно), icon, submenuСобственный раздел виджета верхнего уровня. Без submenu это одна страница; в submenu не более 10 пунктов.
ключ модуляsubmenu (обязательно)От 1 до 3 страниц в группе Расширения этого модуля. title_key и icon отклоняются — у модуля остаются собственные название и иконка.

Ключ модуля должен быть одним из следующих; любой другой ключ отклоняется. Префикс — начало URL страницы.

Ключ модуляПрефикс URL
saleMain/sale
customerMain/customer
distributionMain/distribution
logisticsOrder/logistics
supplyMain/supply
warehouseMain/warehouse
financeMain/finance
hrMain/hr
integrationMain/integration
manufacturingMain/manufacturing
referenceMain/reference
toolsMain/tools

submenu — объект: ключ — код подпункта, значение — { "title_key": "..." }. Код подпункта должен соответствовать ^[a-z][a-z0-9_]{1,31}$ — строчная латинская буква, затем от 1 до 31 строчной буквы, цифры или подчёркивания. Пункты показываются в том порядке, в котором вы их объявили.

Названия ​

Каждый title_key обязан быть в i18n/<default_language>.json, иначе загрузка отклоняется. Названия берутся из архива при загрузке, поэтому текст меню не расходится с проверенным пакетом. При показе каждое название берётся из bundle языка пользователя; если ключа там нет, используется значение из default_language.

Иконка ​

icon необязательна и допустима только в widget_page. Загрузка отклоняется, если это не:

  • путь к файлу архива внутри images/ с окончанием .png;
  • PNG-файл размером не более 16 КБ (16 384 байта);
  • квадрат со стороной от 48 до 128 px;
  • изображение с альфа-каналом (тип цвета PNG 4 — оттенки серого + альфа, или 6 — RGBA).

Bito использует только форму иконки. Альфа-канал PNG применяется как маска и заливается текущим цветом темы меню, поэтому иконка следует светлой и тёмной теме, наведению и активному состоянию; ваши собственные цвета не показываются никогда. Рисуйте один глиф на прозрачном фоне. Без иконки у пункта показывается стандартный глиф расширения Bito.

Код виджета и URL ​

{widget_code} в URL меню — код виджета приложения, то есть system_id, который вы выбираете и занимаете в Моих приложениях (см. FAQ). Если у приложения нет кода (например, у частного виджета), вместо него используется client_id: /widget/{client_id}/{subitem}.

СтраницаURLlocation
widget_page без подменю/widget/{widget_code}widget_page
Пункт подменю widget_page/widget/{widget_code}/{subitem}widget_page
Пункт подменю модуля{prefix}/widget/{widget_code}/{subitem}, например /sale/widget/acme_crm/order_syncключ модуля, например saleMain

URL блока или подпункта, которого нет в манифесте, показывает сообщение «недоступно» вместо виджета. Странице модуля всегда нужен код подпункта. Какая страница открыта, виджет узнаёт из initMenuPage и bito.context.menu.

Видимость и доступ ​

  • Меню показывает разделы widget_page не более чем пяти виджетов; разделы остальных виджетов не показываются. На пункты в модулях это ограничение не влияет.
  • Пункты меню и страницы видны всем пользователям компании; Bito не фильтрует их по ролям. Что может видеть и делать каждый пользователь, решает ваш виджет.
  • RBAC Bito по-прежнему применяется к каждому вызову bito.ajax, поэтому страница не может прочитать или изменить ничего сверх прав вошедшего пользователя.
  • Пункты появляются после установки приложения. Ваш код на странице запускается только после того, как компания приняла согласие на релиз, — как в любой точке монтирования.

Отличия от amoCRM ​

  • Поле названия — title_key (ключ i18n).
  • Нет sort: порядок объявления и есть порядок показа.
  • Нет is_hidden.
  • Иконка — PNG, который используется как форма и окрашивается цветами темы Bito, а не SVG, показанный как нарисован.
  • Ключи модулей — собственные ключи Bito, перечисленные выше.

Языковые словари ​

Плоский JSON-объект ключ → строка. Bito выбирает на сервере один словарь при монтировании: язык пользователя, если он объявлен в манифесте, иначе default_language. Виджет получает только его — через bito.i18n().

json
{
    "title": "История звонков",
    "empty": "Пока ничего нет",
    "greeting": "Здравствуйте, {{name}}"
}

Словарь с невалидным JSON пишется в лог и считается пустым — виджет всё равно смонтируется, а каждый ключ вернёт сам себя.

templates/ ​

Каждый файл из templates/ подкладывается в ответ mount и адресуется по пути после префикса templates/: templates/card.twig — это card.twig. Рендеринг — простая подстановка в двойных фигурных скобках, и ничего больше: ни циклов, ни условий, ни фильтров. Расширение .twig разрешено для привычности; это не движок Twig.

style.css ​

Если файл есть, он подключается к документу хоста как <link> на время работы виджета и удаляется при размонтировании. Он ничем не ограничен по области: голое правило .card { ... } перекрасит админ-панель. Добавляйте префикс каждому селектору.

script.js ​

AMD-модуль. См. жизненный цикл CustomWidget.

Лимиты ​

Все они проверяются по центральному каталогу ZIP до того, как что-либо распаковано, поэтому zip-бомба отклоняется, так и не будучи развёрнутой.

ЛимитЗначение
Сжатый архив10 МиБ (10 485 760 байт)
Суммарный несжатый размер50 МиБ (52 428 800 байт)
Один файл в несжатом виде5 МиБ (5 242 880 байт)
Количество записей1000
Коэффициент распаковки, на запись100:1

Из-за чего запись отклоняется ​

ПравилоПодробности
Абсолютные путиВедущий / или любой абсолютный путь
Выход за пределыЛюбой сегмент пути ..
Обратные слеши\ в любом месте имени записи
Символические ссылкиОтклоняются безусловно, независимо от того, какую ОС заявляет архив
Зашифрованные записиЗаписи под паролем не принимаются
Вложенные архивы.zip .rar .7z .tar .gz .tgz .bz2
Бит исполненияЛюбой из битов режима 0o111 в Unix
Дубликаты путейОдно и то же имя записи дважды
Неизвестное расширениеВсё, чего нет в списке ниже
Неверная кодировка текстаТекстовый файл, не являющийся валидным UTF-8
Не изображениеPNG, JPEG и WebP проверяются по сигнатуре; расширению не верят

Разрешённые расширения: .js .json .css .html .twig .txt .png .jpg .jpeg .webp .woff2

Типы содержимого назначаются на сервере по этому расширению, а не по тому, что заявляет архив или запрос загрузки.

Отчёт валидации ​

upload отвечает отчётом, и он сохраняется в релизе независимо от того, прошла проверка или нет:

json
{
    "ok": false,
    "errors": [
        "Extension not allowed: vendor/lib.map",
        "Missing i18n/ru.json declared in manifest.json"
    ],
    "entries": 14,
    "uncompressed_bytes": 189342
}

Когда ok равно true, отчёт также содержит разобранный manifest. Отправить релиз на проверку нельзя, пока сохранённый отчёт не скажет ok.

Bito 2.0 Integration API