enВойти в Senler

RefLink: пример плагина

RefLink — открытый полнофункциональный плагин реферальных кампаний для Senler. В одном репозитории находятся frontend, backend, общие контракты, миграции, пользовательская документация и файлы самостоятельного запуска.

Этот раздел объясняет приложение в том порядке, в котором проходит реальная работа: как открывается встроенная страница, как backend получает доступ к проекту, как AI вызывает действия через MCP и как выполняется шаг автоматизации. После этого можно переходить к конкретным папкам и файлам.

Как пользоваться примером

RefLink полезен, если новому плагину нужны несколько возможностей сразу:

  • встроенная страница внутри кабинета;
  • OAuth-доступ к API Senler от имени проекта;
  • собственный шаг автоматизации и форма его настройки;
  • действия backend, доступные AI через MCP;
  • webhook с проверкой подписи и защитой от повторной обработки;
  • общие типы между frontend и backend;
  • публичная документация на русском и английском языках.

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

RefLink не реализует конфигуратор инструмента агента. Для него используется тот же подход с Senler Bridge, но backend-операция размечается AgentToolConfigurator, а frontend обрабатывает запуск tool_configurator.

Технологии и их роль

ЧастьТехнологияДля чего используется
FrontendReact и Senler UIИнтерфейс встроенной страницы, форма настройки шага и взаимодействие с кабинетом через Senler Bridge.
BackendNestJSHTTP-контроллеры, проверка DTO, модули приложения, OAuth, сессии и обработчики webhook.
API-контрактOpenAPI и NestJS SwaggerОписание обычных endpoint и выбранных действий приложения для MCP.
API Senler@aisenler/sdk-fetchТипизированные вызовы каналов, лидов, переменных, агентов, сегментов и автоматизаций.
ХранилищеPostgreSQL и TypeORMКампании, OAuth-подключения, обработанные события и техническое состояние выдачи награды.
Локальный запускDocker ComposeСовместный запуск frontend, backend и PostgreSQL с воспроизводимой конфигурацией.

Как проходит запрос

У RefLink есть три основных сценария. Они используют один backend, но разные способы входа и разные полномочия.

1. Открытие встроенной страницы

  1. Пользователь открывает RefLink в проекте. Кабинет загружает frontend во встроенной странице и передаёт контекст запуска через Senler Bridge.
  2. Frontend получает launch_code и project_id в useEmbeddedRefLinkApp.ts. Значение project_id нужно интерфейсу для проверки соответствия, но само по себе оно не даёт доступ.
  3. Frontend отправляет одноразовый launch_code в POST /api/embedded/session.
  4. Backend проверяет подпись, срок и одноразовость кода в backend/src/core/session, извлекает из него проект и выдаёт собственный session_token RefLink.
  5. Все следующие запросы страницы используют этот session_token. EmbeddedSessionGuard снова извлекает проект из проверенной сессии, поэтому контроллерам не нужен project_id из формы или URL.
  6. Если проект ещё не подключён по OAuth, frontend показывает AuthorizationStep. После согласия backend получает project-scoped OAuth-токены Senler и сохраняет их для этого проекта.
  7. Для загрузки каналов, агентов, сегментов и автоматизаций backend берёт OAuth access token и вызывает API Senler через SenlerApiClient.

Итог: браузер знает только сессию RefLink, а доступ к API Senler и Client Secret остаются на backend.

2. Вызов действия приложения через MCP

  1. Разработчик помечает разрешённые операции декораторами SDK. Примеры чтения, создания, изменения и архивации кампаний находятся в campaign.controller.ts.
  2. NestJS Swagger формирует OpenAPI. Декоратор AppAction добавляет к выбранной операции x-senler-app-action; остальные endpoint не становятся действиями автоматически.
  3. После установки плагина Senler загружает OpenAPI по URL, указанному в настройках приложения. AI находит действие через search, при необходимости читает полную схему через describe_method и вызывает его через execute.
  4. Перед вызовом backend Senler создаёт короткую management session: отправляет подписанный одноразовый launch_code в POST /api/embedded/management-session.
  5. RefLink проверяет код и возвращает собственный management_token. Только этот токен отправляется в Authorization: Bearer при вызове действия. MCP-ключ или OAuth-токен пользователя в плагин не передаётся.
  6. Тот же EmbeddedSessionGuard проверяет management token и передаёт контроллеру подтверждённый проект. Поэтому действие приложения не принимает project_id от AI.
  7. Если действию дополнительно нужны данные Senler, backend отдельно использует сохранённый project-scoped OAuth access token.

Отдельный пример конфигуратора находится в app-action-configurator.controller.ts. Декоратор AutomationStepConfigurator сообщает MCP, что результат метода содержит нормализованные configuration и branches шага.

3. Настройка и выполнение шага автоматизации

  1. Когда пользователь добавляет шаг RefLink в редакторе, Senler открывает тот же frontend с типом запуска automation_step_configurator.
  2. useEmbeddedRefLinkApp.ts читает тип запуска, текущую конфигурацию и ветки из Senler Bridge.
  3. Вместо основной страницы App.tsx показывает StepConfigurator.
  4. При сохранении configurator-protocol.ts возвращает через Bridge нормализованную конфигурацию и привязки результатов к переменным автоматизации.
  5. Во время опубликованного запуска Senler не открывает форму повторно. Он вызывает подписанный webhook шага в webhook.controller.ts.
  6. Backend проверяет подпись, выполняет сценарий и возвращает поля, определённые в общем контракте REFLINK_STEP_RESULTS.

Итог: конфигуратор отвечает только за подготовку и сохранение настройки, а webhook — за выполнение шага во время автоматизации.

В каком порядке читать исходный код

ШагРаздел репозиторияЧто станет понятно
1README.mdНазначение приложения, режимы кампаний, запуск и публикация документации.
2docs/architecture.mdГде хранятся данные и почему реферальная статистика не дублируется в Postgres.
3docs/senler-app-setup.mdКакие URL, события, OAuth-права и поля результата указываются в настройках приложения.
4contracts/src/index.tsОбщие типы, режимы кампаний и результаты шага, которыми пользуются обе части приложения.
5frontend/srcПодключение Bridge, обмен кода на сессию и переключение между основной страницей и конфигуратором.
6backend/src/core/sessionПроверка одноразовых кодов, project-scoped сессии и защита endpoint.
7backend/src/resources/oauthOAuth callback, хранение подключения и обновление токенов.
8backend/src/integrations/senlerИзолированный адаптер для вызовов API Senler.
9backend/src/resources/campaignsCRUD кампаний, статистика, создание автоматизации и OpenAPI-действия.
10backend/src/resources/referralsПодписи webhook, дедупликация, атрибуция и выдача наград.
11docs/public и assets/catalogКак хранить пользовательскую документацию и материалы карточки приложения рядом с кодом.
12docker-compose.ymlКак связаны frontend, backend, база данных, health-check и переменные окружения.

Ключевые архитектурные решения

Проект определяется доверенным контекстом

project_id из query-параметра, тела запроса или аргументов AI нельзя считать доказательством доступа. RefLink определяет проект только после проверки launch_code, сессии приложения, management token, OAuth subject или подписи webhook. Затем сервисы получают уже подтверждённый ID через @EmbeddedProject().

Это решение предотвращает ситуацию, когда пользователь или AI подставляет ID другого проекта в обычный параметр.

Авторизация разделена по назначению

ЗначениеКто выдаётДля чего оно нужно
launch_codeSenlerОдноразово подтвердить запуск страницы или management session и передать подписанный контекст проекта.
session_tokenBackend RefLinkРазрешить открытому frontend обращаться к backend RefLink в рамках одного проекта.
management_tokenBackend RefLinkРазрешить Senler вызвать конкретное действие приложения через MCP.
OAuth access/refresh tokenSenler OAuthРазрешить backend RefLink обращаться к API Senler с подтверждёнными пользователем правами проекта.

Ни один из этих токенов не следует использовать вместо другого. Секреты OAuth хранятся в зашифрованном виде через SecretVaultService, а новая пара access/refresh сохраняется вместе под блокировкой подключения в OAuthConnectionService.

Один frontend обслуживает несколько контекстов

Общая точка входа уменьшает дублирование авторизации, загрузки данных, стилей и компонентов. Разница между основной страницей и формой шага определяется не отдельным URL, а проверенным context.launch.type от Senler Bridge.

Если приложение добавит конфигуратор инструмента агента, эту схему можно расширить третьей явной веткой tool_configurator, не смешивая сохранение настроек с обычной работой страницы.

SDK отделён от предметной логики

Контроллеры и сервисы кампаний не формируют HTTP-запросы к Senler напрямую. Все такие вызовы собраны в SenlerApiClient, который использует официальный SDK.

Так проще обновлять SDK, тестировать ответы и временно изолировать операции, которых ещё нет в опубликованной версии клиента.

Повтор webhook считается нормальным

Webhook может прийти повторно или обработчик может завершиться после частичного выполнения. WebhookReceiptRepository использует event ID для дедупликации, хранит статус обработки и позволяет забрать зависшую запись после ограниченного lease.

Предметные операции также сделаны атомарными или идемпотентными: первый пригласивший записывается только один раз, а ID приглашённого добавляется в массив без дублей.

Источник данных выбирается заранее

В PostgreSQL RefLink хранит настройки кампаний, OAuth-подключения и техническое состояние. Реферальные связи и счётчики остаются в переменных лидов Senler. Приложение не поддерживает вторую копию той же статистики и не синхронизирует два источника истины.

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

Что переиспользовать в новом приложении

  1. Создайте отдельные пакеты или каталоги для общих контрактов, frontend, backend и интеграционного адаптера.
  2. Реализуйте проверку launch_code и project-scoped сессию до разработки бизнес-форм.
  3. Добавьте OAuth только с необходимыми правами, храните токены зашифрованными и сохраняйте новую пару после refresh атомарно.
  4. Подключите Senler UI и Senler Bridge, затем явно обработайте каждый нужный context.launch.type.
  5. Оставьте бизнес-endpoint обычными контроллерами с DTO, а выбранные операции опубликуйте через AppAction, AgentToolConfigurator или AutomationStepConfigurator.
  6. Проверьте полученный OpenAPI командой senler-app validate-openapi до добавления URL в настройки приложения.
  7. Для webhook сначала реализуйте проверку подписи, event ID, идемпотентность и восстановление зависшей обработки, затем добавляйте предметный сценарий.
  8. Храните пользовательскую документацию и материалы каталога рядом с исходным кодом и проверяйте русскую и английскую версии в CI.

Что не копировать без проверки

  • OAuth-права RefLink: новому приложению может требоваться меньше или больше разрешений.
  • Срок жизни сессии: его выбирают по риску, способу повторного запуска и требованиям продукта.
  • Имена переменных лидов, webhook-события, поля результата и правила награды: это предметная модель реферального приложения.
  • URL endpoint и имена переменных окружения: они должны принадлежать вашему домену и инфраструктуре.
  • Все MCP-действия сразу: публикуйте только операции, которые AI действительно должен вызывать, и точно отмечайте read_only, destructive и idempotent.

Ссылки