SelSup Developers

Расширения кабинета · Руководства

Как разработать модуль для SelSup

Пошаговое руководство по созданию своей панели в SelSup — от HTML-страницы и настройки доступа до получения данных и вызова методов через JavaScript.

Разделы: 15Руководство для разработчика
→

Модуль — это ваше веб-приложение, которое открывается внутри кабинета SelSup. Оно может показывать свой интерфейс, получать разрешённые данные об открытой странице и вызывать доступные методы SelSup. Ниже разберём весь путь на примере простой панели с метками.

Разработчики модулей и интеграций

1

Что можно разработать

Для дополнительной страницы или вкладки создайте веб-приложение на HTML и JavaScript. Можно использовать React, Vue или другой фреймворк. Размещаете и обновляете приложение вы; SelSup открывает его по указанному адресу во встроенном окне — iframe.

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

Встроенная панель и серверная интеграция могут дополнять друг друга. Например, панель показывает результат обмена, а ваш сервер загружает данные по расписанию.

i
С чего начать

Сначала соберите панель, которая получает данные об открытой странице. Затем добавьте одну кнопку для чтения меток. Когда чтение работает, подключайте нужные действия с данными.

2

Подключение модуля к SelSup

Добавлять модули и менять их настройки может администратор кабинета. Для своего кабинета можно использовать личное расширение без публикации в магазине.

  1. 1

    Скачайте готовый пример

    Архив по ссылке в разделе «Готовый пример» содержит HTML-страницу, JavaScript-код подключения и локальный стенд. Сборщик и React для запуска не нужны.

  2. 2

    Проверьте панель локально

    Выполните npm test и npm start в папке примера. Откройте http://localhost:4174/ — это учебный кабинет с вымышленными данными. Сама панель загружается с http://127.0.0.1:4173/.

  3. 3

    Укажите адрес кабинета

    В config.js задайте точный origin кабинета из адресной строки браузера — схему, домен и порт, если он есть. Путь /application/ в origin не входит. Для рабочей версии уберите локальные адреса из списка разрешённых.

  4. 4

    Разместите панель на HTTPS-сайте

    Опубликуйте index.html, app.js, config.js, selsup-extension.js и styles.css на отдельном домене, который не использует общий cookie-домен с SelSup. Сервер должен разрешать открытие страницы внутри iframe.

  5. 5

    Добавьте модуль в кабинет

    Откройте «Интеграции → Мои расширения → Добавить расширение». Введите название, описание и HTTPS-адрес страницы. Выберите иконку, место отображения и необходимые разрешения, затем сохраните.

3

Расположение модуля в кабинете

MENU_PAGE — отдельная страница в меню SelSup. В настройках выберите раздел меню и пункт, перед которым нужно разместить ссылку. Если позиция не выбрана, ссылка появится в конце списка.

PRODUCT_TAB — вкладка в карточке товара. При разрешённой передаче данных товара модуль получает product.modelId — идентификатор модели, а не отдельного размера или SKU.

ORDER_TAB — вкладка в заказе. При разрешённой передаче данных заказа модуль получает order.id.

Вкладки доступны у сохранённых товаров и заказов с идентификатором. Панель вкладки загружается при её открытии. Для новой несохранённой карточки данные ещё не передаются.

4

Разрешения на данные и методы API

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

USER разрешает передать user.id и user.locale — идентификатор сотрудника и язык интерфейса. CLIENT разрешает client.id — идентификатор кабинета. ORGANIZATION разрешает список доступных сотруднику организаций с их id и name.

PRODUCT разрешает product.modelId во вкладке товара, ORDER — order.id во вкладке заказа. В отдельной странице меню этих полей нет.

Разработчик указывает методы, которые нужны приложению. При установке администратор кабинета выбирает, какие из них разрешить. Пока метод не разрешён, вызвать его нельзя. Это относится и к расширениям, установленным до появления вызовов API.

Включённый метод выполняется с правами сотрудника, который открыл модуль. Разрешение приложению не добавляет сотруднику прав. Доступ относится к методу в рамках доступного ему кабинета и не ограничивается одним товаром открытой вкладки.

5

Данные, которые получает модуль

SelSup передаёт сообщение в iframe через window.postMessage. В обработчике message данные находятся в event.data. Это обычный JavaScript-объект, а не JSON-строка; JSON.parse для него не нужен. JSON ниже показывает структуру этого объекта.

Внешний объект содержит type, version и context. Поле context — сведения о текущем открытии модуля. version, extensionId и placement входят в него всегда. apiMethods сообщает список разрешённых методов; если поля нет, считайте список пустым.

Поля user, client, organizations, product и order появляются только при соответствующих разрешениях. Проверяйте наличие поля перед чтением. Полные карточки товаров и заказов, контакты, пароли и токены в этих сообщениях не передаются.

i
Идентификатор — не подтверждение личности

Эти данные помогают показать нужный интерфейс. Ваш сервер не должен выдавать доступ к защищённым данным только по присланному client.id или user.id. Для своего сервера нужна собственная проверка пользователя.

Сообщение для страницы в меню при разрешённой передаче данных · JSON
{
  "type": "selsup.extension.context",
  "version": 1,
  "context": {
    "version": 1,
    "extensionId": "example-extension-id",
    "placement": "MENU_PAGE",
    "apiMethods": ["tagService.findTags"],
    "user": {"id": 101, "locale": "ru"},
    "client": {"id": 202},
    "organizations": [{"id": 303, "name": "Учебная организация"}]
  }
}
Содержимое context во вкладке товара · JSON
{
  "version": 1,
  "extensionId": "example-extension-id",
  "placement": "PRODUCT_TAB",
  "apiMethods": ["productService.updateFavourite"],
  "product": {"modelId": 404}
}
Содержимое context во вкладке заказа · JSON
{
  "version": 1,
  "extensionId": "example-extension-id",
  "placement": "ORDER_TAB",
  "apiMethods": [],
  "order": {"id": 505}
}

6

Подключение и обмен сообщениями

Отправляйте объекты напрямую, без JSON.stringify. Не используйте «*» вместо origin. Готовый selsup-extension.js выполняет подключение, проверяет отправителя и поля входящих данных, а также связывает запросы с ответами. Для нового проекта удобнее взять его из примера.

При смене сотрудника, кабинета или открытой карточки прежняя панель уничтожается вместе с ожидающими ответами. Не сохраняйте прежние разрешения для следующего открытия.

  1. 1

    Подпишитесь на входящие сообщения

    Сначала зарегистрируйте обработчик message. Принимайте сообщения только от window.parent и с заранее заданного точного origin кабинета. Проверяйте type, version и структуру данных.

  2. 2

    Сообщите о готовности

    Отправьте в родительское окно объект с type = selsup.extension.ready и version = 1. В targetOrigin укажите origin кабинета. SelSup передаст данные сообщением selsup.extension.context.

  3. 3

    Получите обновлённые данные при необходимости

    Отправьте selsup.extension.request-context с version = 1. SelSup повторно проверит доступ и пришлёт context. Частые запросы могут объединяться; не используйте их как непрерывный опрос.

Сообщения от панели в SelSup · JavaScript
// Замените адрес на точный origin вашего кабинета.
const hostOrigin = 'https://your-cabinet.example.com';
window.parent.postMessage({
  type: 'selsup.extension.ready',
  version: 1
}, hostOrigin);

// После подключения можно запросить обновление:
window.parent.postMessage({
  type: 'selsup.extension.request-context',
  version: 1
}, hostOrigin);

7

Вызов методов через JavaScript

Для вызовов используйте connection.callApi(method, params) из готового примера. method — строка с точным именем из списка ниже, params — объект параметров. Метод возвращает Promise с результатом или ошибкой.

Поддерживаются только перечисленные методы. Произвольные сервисы SelSup, clientService и authService через панель недоступны. Передавать свой URL, HTTP-заголовки или настройки запроса нельзя. Токен публичного API для этих вызовов не нужен — запрос выполняет сам кабинет.

Идентификаторы передавайте положительными целыми числами в пределах Number.MAX_SAFE_INTEGER. Для updateFavourite нужен ID модели из product.modelId. Поле favourite принимает логическое значение true или false.

name — строка до 100 символов, не пустая после удаления пробелов по краям. query — необязательная строка до 100 символов. page — номер страницы от 1 до 10000, по умолчанию 1. limit — от 1 до 100, по умолчанию 30. Лишние параметры приводят к ошибке INVALID_REQUEST.

findTags возвращает только rows с id и name. Поля общего количества и hasNextPage здесь нет. Для просмотра следующих страниц увеличивайте page; пустая страница означает, что меток больше нет на момент запроса.

Доступные методы и результаты · Text
tagService.findTags
  Параметры: {query?, page?, limit?}
  Результат: {rows: [{id, name}]}

tagService.createTag
  Параметры: {name}
  Результат: {id, name}

tagService.updateTag
  Параметры: {id, name}
  Результат: {id, name}

tagService.deleteTag
  Параметры: {id}
  Результат: null

productService.updateFavourite
  Параметры: {id, favourite}
  Результат: null

8

Рабочий пример подключения и чтения меток

Скопируйте selsup-extension.js из архива в папку своей страницы. Создайте index.html и app.js с кодом ниже. В настройках модуля разрешите tagService.findTags. Замените origin кабинета в app.js на свой.

Кнопка включится после получения разрешения. Пример читает метки только по нажатию, показывает ошибку и освобождает обработчики при закрытии страницы. Обмен с SelSup выполняет connectExtension; объект window.api внутри панели не создаётся.

index.html · HTML
<!doctype html>
<html lang="ru">
  <meta charset="utf-8">
  <title>Метки SelSup</title>
  <button id="load" disabled>Показать метки</button>
  <pre id="result">Подключаемся к SelSup…</pre>
  <script type="module" src="./app.js"></script>
</html>
app.js · JavaScript
import {connectExtension} from './selsup-extension.js';

const button = document.querySelector('#load');
const output = document.querySelector('#result');
let context;
let busy = false;
let nextCallAt = 0;

function updateButton() {
  button.disabled = busy || Date.now() < nextCallAt ||
    !context?.apiMethods?.includes('tagService.findTags');
}

const connection = connectExtension({
  allowedHostOrigins: ['https://your-cabinet.example.com'],
  onContext(value) {
    context = value;
    output.textContent = JSON.stringify(value, null, 2);
    updateButton();
  }
});

button.addEventListener('click', async () => {
  busy = true;
  nextCallAt = Date.now() + 1000;
  updateButton();
  try {
    const result = await connection.callApi('tagService.findTags', {
      query: '', page: 1, limit: 30
    });
    output.textContent = JSON.stringify(result.rows, null, 2);
  } catch (error) {
    output.textContent = `Не удалось получить метки: ${error.code}`;
  } finally {
    busy = false;
    window.setTimeout(updateButton, Math.max(0, nextCallAt - Date.now()));
  }
});

window.addEventListener('pagehide', () => connection.dispose(), {once: true});
Другие действия — выполняйте по отдельному нажатию кнопки · JavaScript
// Каждый метод нужно отдельно разрешить в настройках.
// Между запусками выдерживайте не менее секунды.
const created = await connection.callApi('tagService.createTag', {
  name: 'Проверить упаковку'
});

// При следующем действии пользователя:
const renamed = await connection.callApi('tagService.updateTag', {
  id: created.id, name: 'Упаковка проверена'
});

// Во вкладке товара, если context.product присутствует:
if (context.product) {
  await connection.callApi('productService.updateFavourite', {
    id: context.product.modelId, favourite: true
  });
}

9

Формат сообщений при самостоятельном подключении

Если пишете обмен самостоятельно, отправьте объект api-request и дождитесь api-response с тем же requestId. requestId — уникальная строка длиной от 1 до 64 символов, содержащая латинские буквы, цифры, дефис или подчёркивание. Подходит crypto.randomUUID().

Перед обработкой ответа проверьте origin, source, type и version, затем найдите ожидающий запрос по requestId. При ok = true результат находится в result; при ok = false код ошибки находится в error.code. Текст ответа внутреннего HTTP API в панель не передаётся.

Запрос на создание метки · JSON
{
  "type": "selsup.extension.api-request",
  "version": 1,
  "requestId": "request-1",
  "method": "tagService.createTag",
  "params": {"name": "Проверить упаковку"}
}
Успешный ответ · JSON
{
  "type": "selsup.extension.api-response",
  "version": 1,
  "requestId": "request-1",
  "ok": true,
  "result": {"id": 601, "name": "Проверить упаковку"}
}
Ответ с ошибкой · JSON
{
  "type": "selsup.extension.api-response",
  "version": 1,
  "requestId": "request-1",
  "ok": false,
  "error": {"code": "ACCESS_DENIED"}
}

10

Ограничения и обработка ошибок

Одновременно может выполняться один запрос. Между запусками должно пройти не менее секунды — даже если предыдущий запрос уже завершился. Последние 256 requestId запоминаются для защиты от повторного запроса.

ACCESS_DENIED означает, что метод недоступен или у сотрудника недостаточно прав. INVALID_REQUEST — неподдерживаемые параметры. RATE_LIMITED — слишком частый вызов либо уже выполняющийся запрос. DUPLICATE_REQUEST — повтор requestId. REQUEST_FAILED — запрос не удалось завершить.

Вспомогательный код примера дополнительно возвращает NOT_CONNECTED до получения данных от SelSup, DISCONNECTED после закрытия соединения и TIMEOUT, если ответ не получен за 20 секунд.

После ошибки или таймаута изменения уже могли сохраниться. Сначала проверьте результат, затем решайте, повторять ли действие. Закрытие вкладки прекращает ожидание ответа, но не отменяет уже выполненные изменения на сервере.

11

Готовый пример и публикация панели

Скачайте архив, распакуйте его и откройте README.md. Пример включает панель, код подключения и учебный кабинет. Его можно перенести в свой GitHub-репозиторий. Локальный стенд работает без токенов и реальных данных, но не проверяет настоящие права сотрудника SelSup.

Для локальной проверки нужны Node.js 22 или новее. Устанавливать npm-зависимости не требуется. Учебные HTTP-адреса подходят только для стенда; в SelSup нужен публичный HTTPS-адрес.

Замените origin в frame-ancestors на адрес кабинета. Этот параметр работает только в HTTP-заголовке, а не в meta-теге. Заголовок X-Frame-Options не должен запрещать открытие панели в кабинете.

В примере connect-src запрещает сетевые запросы самой панели. Если ей нужен ваш сервер, добавьте его адрес в политику. Вызовы через postMessage не требуют такого сетевого разрешения.

GitHub Pages позволяет опубликовать статические файлы, но не позволяет произвольно задавать HTTP-заголовки. Для настройки frame-ancestors используйте хостинг или прокси с поддержкой заголовков.

Скачать архив примера · URL
https://api.selsup.ru/assets/examples/selsup-extension-example.zip
Запуск локального стенда · Shell
cd selsup-extension-example
npm test
npm start
Пример HTTP-заголовков для статической панели · HTTP
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self'; connect-src 'none'; form-action 'none'; base-uri 'none'; frame-ancestors https://your-cabinet.example.com
Referrer-Policy: no-referrer
X-Content-Type-Options: nosniff

12

Что учитывать при разработке интерфейса

Панель работает в iframe с ограничениями браузера. Формы с отправкой, всплывающие окна, скачивания и переходы в основном окне запрещены. Обычные кнопки с JavaScript-обработчиками работают.

Не запрашивайте внутри панели пароль SelSup и не помещайте токен API в её код, адрес страницы или localStorage. Для методов встроенной панели токен не нужен. Для фоновой интеграции храните токен на своём сервере.

Ваш сайт видит IP посетителя и получает разрешённые данные. Сообщите пользователям, для чего они используются. Выводите полученные строки через textContent или штатные средства фреймворка, без вставки непроверенного HTML.

13

Фоновый обмен через публичное API

Панель работает, пока открыта в кабинете. Для загрузки заказов по расписанию и обмена с внешней системой создайте серверное приложение. Оно обращается к https://api.selsup.ru/api/ с токеном из настроек API и принимает вебхуки на свой HTTPS-адрес. Получить токен можно по ссылке ниже.

Вебхук сообщает об изменении заказа или остатка. Сначала сохраните уведомление, затем обработайте его в очереди. Периодически сверяйте результат через API, чтобы восстановить изменения после простоя.

Получить токен

14

Добавление нового маркетплейса или службы доставки

Интеграция нового маркетплейса или службы доставки в общие фоновые процессы требует изменений серверного кода SelSup. Это отдельная разработка с проверкой кода, тестированием и выпуском версии. Загрузить такой серверный модуль через форму «Мои расширения» нельзя.

15

Проверка и публикация модуля

  1. 1

    Проверьте три варианта доступа

    Убедитесь, что панель работает с разрешёнными данными, без них и после отзыва разрешений. Отдельно проверьте каждый используемый метод API и сотрудника с ограниченными правами.

  2. 2

    Проверьте поведение при смене страницы

    Откройте другой товар или заказ, смените кабинет и обновите страницу панели. Старые данные не должны попадать в новые действия.

  3. 3

    Проверьте ошибки

    Отключите сайт панели, вызовите метод без разрешения и проверьте частые нажатия. Пользователь должен понимать, удалось ли выполнить действие и что делать дальше.

  4. 4

    Отправьте расширение в магазин при необходимости

    Личное расширение можно отправить на проверку SelSup. Изменение карточки и запрашиваемых методов требует повторной проверки. Сохранённые установки других клиентов автоматически не получают новые настройки и разрешения.