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.
Технологии и их роль
| Часть | Технология | Для чего используется |
|---|---|---|
| Frontend | React и Senler UI | Интерфейс встроенной страницы, форма настройки шага и взаимодействие с кабинетом через Senler Bridge. |
| Backend | NestJS | HTTP-контроллеры, проверка 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. Открытие встроенной страницы
- Пользователь открывает RefLink в проекте. Кабинет загружает frontend во встроенной странице и передаёт контекст запуска через Senler Bridge.
- Frontend получает
launch_codeиproject_idвuseEmbeddedRefLinkApp.ts. Значениеproject_idнужно интерфейсу для проверки соответствия, но само по себе оно не даёт доступ. - Frontend отправляет одноразовый
launch_codeвPOST /api/embedded/session. - Backend проверяет подпись, срок и одноразовость кода в
backend/src/core/session, извлекает из него проект и выдаёт собственныйsession_tokenRefLink. - Все следующие запросы страницы используют этот
session_token.EmbeddedSessionGuardснова извлекает проект из проверенной сессии, поэтому контроллерам не нуженproject_idиз формы или URL. - Если проект ещё не подключён по OAuth, frontend показывает
AuthorizationStep. После согласия backend получает project-scoped OAuth-токены Senler и сохраняет их для этого проекта. - Для загрузки каналов, агентов, сегментов и автоматизаций backend берёт OAuth access token и вызывает API Senler через
SenlerApiClient.
Итог: браузер знает только сессию RefLink, а доступ к API Senler и Client Secret остаются на backend.
2. Вызов действия приложения через MCP
- Разработчик помечает разрешённые операции декораторами SDK. Примеры чтения, создания, изменения и архивации кампаний находятся в
campaign.controller.ts. - NestJS Swagger формирует OpenAPI. Декоратор
AppActionдобавляет к выбранной операцииx-senler-app-action; остальные endpoint не становятся действиями автоматически. - После установки плагина Senler загружает OpenAPI по URL, указанному в настройках приложения. AI находит действие через
search, при необходимости читает полную схему черезdescribe_methodи вызывает его черезexecute. - Перед вызовом backend Senler создаёт короткую management session: отправляет подписанный одноразовый
launch_codeвPOST /api/embedded/management-session. - RefLink проверяет код и возвращает собственный
management_token. Только этот токен отправляется вAuthorization: Bearerпри вызове действия. MCP-ключ или OAuth-токен пользователя в плагин не передаётся. - Тот же
EmbeddedSessionGuardпроверяет management token и передаёт контроллеру подтверждённый проект. Поэтому действие приложения не принимаетproject_idот AI. - Если действию дополнительно нужны данные Senler, backend отдельно использует сохранённый project-scoped OAuth access token.
Отдельный пример конфигуратора находится в app-action-configurator.controller.ts. Декоратор AutomationStepConfigurator сообщает MCP, что результат метода содержит нормализованные configuration и branches шага.
3. Настройка и выполнение шага автоматизации
- Когда пользователь добавляет шаг RefLink в редакторе, Senler открывает тот же frontend с типом запуска
automation_step_configurator. useEmbeddedRefLinkApp.tsчитает тип запуска, текущую конфигурацию и ветки из Senler Bridge.- Вместо основной страницы
App.tsxпоказываетStepConfigurator. - При сохранении
configurator-protocol.tsвозвращает через Bridge нормализованную конфигурацию и привязки результатов к переменным автоматизации. - Во время опубликованного запуска Senler не открывает форму повторно. Он вызывает подписанный webhook шага в
webhook.controller.ts. - Backend проверяет подпись, выполняет сценарий и возвращает поля, определённые в общем контракте
REFLINK_STEP_RESULTS.
Итог: конфигуратор отвечает только за подготовку и сохранение настройки, а webhook — за выполнение шага во время автоматизации.
В каком порядке читать исходный код
| Шаг | Раздел репозитория | Что станет понятно |
|---|---|---|
| 1 | README.md | Назначение приложения, режимы кампаний, запуск и публикация документации. |
| 2 | docs/architecture.md | Где хранятся данные и почему реферальная статистика не дублируется в Postgres. |
| 3 | docs/senler-app-setup.md | Какие URL, события, OAuth-права и поля результата указываются в настройках приложения. |
| 4 | contracts/src/index.ts | Общие типы, режимы кампаний и результаты шага, которыми пользуются обе части приложения. |
| 5 | frontend/src | Подключение Bridge, обмен кода на сессию и переключение между основной страницей и конфигуратором. |
| 6 | backend/src/core/session | Проверка одноразовых кодов, project-scoped сессии и защита endpoint. |
| 7 | backend/src/resources/oauth | OAuth callback, хранение подключения и обновление токенов. |
| 8 | backend/src/integrations/senler | Изолированный адаптер для вызовов API Senler. |
| 9 | backend/src/resources/campaigns | CRUD кампаний, статистика, создание автоматизации и OpenAPI-действия. |
| 10 | backend/src/resources/referrals | Подписи webhook, дедупликация, атрибуция и выдача наград. |
| 11 | docs/public и assets/catalog | Как хранить пользовательскую документацию и материалы карточки приложения рядом с кодом. |
| 12 | docker-compose.yml | Как связаны frontend, backend, база данных, health-check и переменные окружения. |
Ключевые архитектурные решения
Проект определяется доверенным контекстом
project_id из query-параметра, тела запроса или аргументов AI нельзя считать доказательством доступа. RefLink определяет проект только после проверки launch_code, сессии приложения, management token, OAuth subject или подписи webhook. Затем сервисы получают уже подтверждённый ID через @EmbeddedProject().
Это решение предотвращает ситуацию, когда пользователь или AI подставляет ID другого проекта в обычный параметр.
Авторизация разделена по назначению
| Значение | Кто выдаёт | Для чего оно нужно |
|---|---|---|
launch_code | Senler | Одноразово подтвердить запуск страницы или management session и передать подписанный контекст проекта. |
session_token | Backend RefLink | Разрешить открытому frontend обращаться к backend RefLink в рамках одного проекта. |
management_token | Backend RefLink | Разрешить Senler вызвать конкретное действие приложения через MCP. |
| OAuth access/refresh token | Senler 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. Приложение не поддерживает вторую копию той же статистики и не синхронизирует два источника истины.
Это не универсальная схема данных, но универсальный принцип: до разработки нужно определить владельца каждого вида данных и хранить локально только то, что действительно принадлежит приложению.
Что переиспользовать в новом приложении
- Создайте отдельные пакеты или каталоги для общих контрактов, frontend, backend и интеграционного адаптера.
- Реализуйте проверку
launch_codeи project-scoped сессию до разработки бизнес-форм. - Добавьте OAuth только с необходимыми правами, храните токены зашифрованными и сохраняйте новую пару после refresh атомарно.
- Подключите Senler UI и Senler Bridge, затем явно обработайте каждый нужный
context.launch.type. - Оставьте бизнес-endpoint обычными контроллерами с DTO, а выбранные операции опубликуйте через
AppAction,AgentToolConfiguratorилиAutomationStepConfigurator. - Проверьте полученный OpenAPI командой
senler-app validate-openapiдо добавления URL в настройки приложения. - Для webhook сначала реализуйте проверку подписи, event ID, идемпотентность и восстановление зависшей обработки, затем добавляйте предметный сценарий.
- Храните пользовательскую документацию и материалы каталога рядом с исходным кодом и проверяйте русскую и английскую версии в CI.
Что не копировать без проверки
- OAuth-права RefLink: новому приложению может требоваться меньше или больше разрешений.
- Срок жизни сессии: его выбирают по риску, способу повторного запуска и требованиям продукта.
- Имена переменных лидов, webhook-события, поля результата и правила награды: это предметная модель реферального приложения.
- URL endpoint и имена переменных окружения: они должны принадлежать вашему домену и инфраструктуре.
- Все MCP-действия сразу: публикуйте только операции, которые AI действительно должен вызывать, и точно отмечайте
read_only,destructiveиidempotent.