Содержание ТЗ
06. Платформа коннекторов
Философия «одна связка — одна функция», манифест, SDK, стартовый каталог и сертификация.
1. Философия
Коннектор easy-connect — это узкий адаптер одной функции, а не универсальный конструктор. Для каждого сервиса мы публикуем только те триггеры и действия, которые нужны готовым связкам каталога.
Правила:
- Одна функция — один триггер/действие. Никаких «универсальных» методов с десятками параметров.
- Только необходимые поля. Схема действия содержит минимум обязательных полей; остальное — разумные значения по умолчанию.
- Предсказуемость. Одинаковые типы данных и формат ошибок во всех коннекторах.
- Безопасность. Минимальные OAuth scopes, allowlist доменов, запрет SSRF.
- Наблюдаемость. Латентность, ошибки и лимиты каждого коннектора видны в админке.
- Версионируемость. Изменения не ломают активные связки.
2. Типы коннекторов
| Тип | Пример | Механика |
|---|---|---|
| Webhook-источник | Tilda, формы, платёжные события | Провайдер шлёт POST на наш URL |
| Polling-источник | CRM без вебхуков, таблицы | Опрос по расписанию с курсором |
| OAuth-приложение | Google, VK, Яндекс | Authorization Code (+PKCE) |
| Токен-приложение | Telegram Bot API, SMS.ru | Ключ/токен из настроек |
| Гибрид | amoCRM | OAuth + вебхуки |
3. Манифест коннектора
Манифест — декларативное описание. Хранится в пакете коннектора и в БД
(service_versions.manifest). Валидируется Zod при сборке и старте.
3.1. Схема
type Manifest = {
key: string; // ^[a-z0-9-]+$
name: string;
version: string; // SemVer
category: "crm" | "messengers" | "tables" | "forms" | "sms" | "payments" | "other";
icon: string; // путь к SVG
description: string;
docsUrl?: string;
websiteUrl: string;
auth: AuthDefinition;
triggers: TriggerDefinition[];
actions: ActionDefinition[];
rateLimit?: { requestsPerSecond: number; burst?: number };
allowedHosts: string[]; // для SSRF-защиты
healthcheck?: { method: string; path: string; expectStatus: number[] };
};
type AuthDefinition =
| { type: "oauth2"; authorizationUrl: string; tokenUrl: string; scopes: string[];
pkce?: boolean; refresh?: boolean; clientIdEnv: string; clientSecretEnv: string }
| { type: "token"; fields: FieldDefinition[]; verify: ActionDefinition }
| { type: "bot_token"; fields: FieldDefinition[]; verify: ActionDefinition }
| { type: "basic"; fields: FieldDefinition[]; verify: ActionDefinition }
| { type: "webhook"; secret?: boolean };
type TriggerDefinition = {
key: string;
name: string;
description: string;
type: "webhook" | "polling" | "schedule";
sample: Record<string, unknown>;
outputSchema: FieldDefinition[];
dedup?: { strategy: "event_id" | "hash"; field?: string; windowHours?: number };
polling?: { defaultIntervalMinutes: number; minIntervalMinutes: number };
};
type ActionDefinition = {
key: string;
name: string;
description: string;
inputSchema: FieldDefinition[];
idempotent: boolean;
timeoutMs?: number;
sampleResult?: Record<string, unknown>;
};
type FieldDefinition = {
key: string;
label: string;
type: "string" | "text" | "number" | "boolean" | "date" | "datetime" | "email"
| "phone" | "url" | "select" | "multiselect" | "json";
required?: boolean;
default?: unknown;
placeholder?: string;
help?: string;
options?: { value: string; label: string }[];
maxLength?: number;
pattern?: string;
semantic?: "name" | "email" | "phone" | "message" | "date" | "amount" | "id";
};
3.2. Правила
keyуникален и не меняется после публикации.allowedHostsобязателен; wildcard только для поддоменов провайдера.- Секреты не описываются в манифесте как значения — только имена env-переменных.
sampleиспользуется для предпросмотра и тестов.semanticпомогает авто-маппингу.- Для polling обязателен
dedup.
4. Runtime-контракт (SDK)
Пакет @easy-connect/connector-sdk.
export interface ConnectorContext {
logger: Logger;
http: HttpClient; // allowlist, таймауты, ретраи, метрики
secrets: SecretReader; // только для текущего подключения
now(): Date;
traceId: string;
}
export interface Connector {
manifest: Manifest;
// OAuth
getAuthorizationUrl?(ctx: OAuthContext): Promise<string>;
exchangeCode?(ctx: OAuthContext, code: string): Promise<TokenSet>;
refreshToken?(ctx: ConnectorContext, refreshToken: string): Promise<TokenSet>;
// Проверка токена/доступа
verify(ctx: ConnectorContext): Promise<AccountInfo>;
// Webhook-источники
subscribe?(ctx: ConnectorContext, params: SubscribeParams): Promise<SubscribeResult>;
unsubscribe?(ctx: ConnectorContext, params: UnsubscribeParams): Promise<void>;
handleWebhook?(ctx: ConnectorContext, req: WebhookRequest): Promise<NormalizedEvent[]>;
verifyWebhook?(ctx: ConnectorContext, req: WebhookRequest): boolean;
// Polling
poll?(ctx: ConnectorContext, cursor: string | null): Promise<PollResult>;
pollIntervalMinutes?: number;
// Действия
execute(ctx: ConnectorContext, actionKey: string, input: Record<string, unknown>): Promise<ActionResult>;
// Валидация и предпросмотр
validateInput?(actionKey: string, input: Record<string, unknown>): ValidationResult;
getSample?(triggerKey?: string): Promise<Record<string, unknown>>;
}
4.1. Требования к реализации
- Не хранить состояние между вызовами (stateless).
- Не обращаться к БД напрямую.
- Не логировать токены, заголовки авторизации и тела с PII.
- Использовать только
ctx.http, а не глобальный fetch/axios. - Таймаут по умолчанию 30 с; для polling — 60 с.
- Ошибки бросать типизированные (
ConnectorError) с кодом. - Все внешние ID приводить к строке.
- Даты — ISO 8601 UTC.
4.2. Ошибки
class ConnectorError extends Error {
constructor(
public type: "auth" | "rate_limit" | "validation" | "provider" | "network" | "internal",
public code: string,
message: string,
public retryAfterMs?: number,
public details?: unknown
) {}
}
5. Жизненный цикл коннектора
Идея → Исследование API → Манифест → Реализация → Фикстуры
→ Contract-тесты → Sandbox-проверка → Ревью → Публикация (beta)
→ Мониторинг 2 недели → Published → Поддержка → Deprecated
5.1. Этапы
- Исследование. Документация, лимиты, типы авторизации, вебхуки, ошибки.
- Манифест. Схемы триггеров/действий, семантика, примеры.
- Реализация. Только необходимые методы.
- Фикстуры. Реальные ответы API с обезличенными данными.
- Тесты. Успех, 401, 429, 5xx, таймаут, невалидный ввод.
- Ревью. Безопасность, scopes, allowlist, обработка ошибок.
- Публикация.
beta→activeпосле 2 недель без критичных инцидентов. - Поддержка. Мониторинг, обновления при изменениях API.
- Deprecated. За 60 дней до отключения, миграция связок.
5.2. Версионирование
- SemVer:
major.minor.patch. major— несовместимое изменение схемы/поведения; новые активации на новую, старые — на прежнюю до миграции.minor— новые поля/методы, обратная совместимость.patch— исправления.- Активация хранит
connector_versionиbundle_version.
6. Пример: Telegram (bot_token)
См. полный пример в examples/connector-telegram.json.
Ключевые решения:
- Авторизация: bot-token, проверка через
getMe. - Триггер:
new_message(webhook черезsetWebhookили pollinggetUpdates). - Действие:
send_message(chat_id, text, parse_mode, disable_notification). - Лимиты: 30 сообщений/сек (глобально), 1 сообщение/сек в чат (ориентир).
- Ошибки 401 →
auth, 429 →rate_limitсretry_after.
7. Стартовый каталог (MVP)
Актуальный каталог реализации —
lib/catalog.ts: 15 сервисов и 40 связок. Он используется сайтом, страницами связок, прототипом настройки и является источником seed-данных для backend.Детальное ТЗ по первым пяти коннекторам (Tilda, Bitrix24, amoCRM, Telegram, Google Sheets) — в
10-connectors-top5.md.
7.1. 15 сервисов
| # | Сервис | Категория | Авторизация | Триггеры | Действия |
|---|---|---|---|---|---|
| 1 | Tilda | Формы | Webhook | Новая заявка | — |
| 2 | Telegram | Мессенджеры | Bot token | Новое сообщение (P1) | Отправить сообщение |
| 3 | amoCRM | CRM | OAuth | Новая сделка (P1) | Создать сделку/контакт |
| 4 | Битрикс24 | CRM | OAuth/webhook | Новая заявка (P1) | Создать лид/сделку |
| 5 | Google Sheets | Таблицы | OAuth | Новая строка | Добавить строку |
| 6 | Яндекс Формы | Формы | Webhook | Новая заявка | — |
| 7 | VK | Соцсети | OAuth | Новое сообщение (P1) | Отправить сообщение |
| 8 | МойСклад | Учёт | Token | Новый заказ (P1) | Создать заказ/контрагента |
| 9 | SMS.ru | SMS | Token | — | Отправить SMS |
| 10 | SMS Aero | SMS | Token | — | Отправить SMS |
| 11 | ЮKassa | Платежи | Webhook | Успешный платёж | — |
| 12 | Тильда/CRM webhook | Универсальный | Webhook | Входящий JSON | — |
| 13 | Notion | Базы знаний | OAuth | — | Добавить запись (P1) |
| 14 | Trello | Задачи | Token | — | Создать карточку |
| 15 | Email (SMTP) | SMTP | — | Отправить письмо |
Триггеры с пометкой P1 могут быть добавлены в течение беты. Для запуска MVP достаточно действий и вебхук-источников.
7.2. 40 связок (примеры)
| # | Связка | Категория |
|---|---|---|
| 1 | Tilda → Telegram | Формы |
| 2 | Tilda → amoCRM | Формы |
| 3 | Tilda → Битрикс24 | Формы |
| 4 | Tilda → Google Sheets | Формы |
| 5 | Tilda → SMS.ru | Формы |
| 6 | Tilda → Email | Формы |
| 7 | Яндекс Формы → Telegram | Формы |
| 8 | Яндекс Формы → amoCRM | Формы |
| 9 | Яндекс Формы → Google Sheets | Формы |
| 10 | Google Sheets → Telegram | Таблицы |
| 11 | Google Sheets → amoCRM | Таблицы |
| 12 | Google Sheets → SMS.ru | Таблицы |
| 13 | Google Sheets → Email | Таблицы |
| 14 | ЮKassa → Telegram | Платежи |
| 15 | ЮKassa → Google Sheets | Платежи |
| 16 | ЮKassa → amoCRM | Платежи |
| 17 | ЮKassa → Email | Платежи |
| 18 | МойСклад → Telegram | Учёт |
| 19 | МойСклад → Google Sheets | Учёт |
| 20 | МойСклад → amoCRM | Учёт |
| 21 | VK → Telegram | Соцсети |
| 22 | VK → Google Sheets | Соцсети |
| 23 | VK → amoCRM | Соцсети |
| 24 | Telegram → Google Sheets | Мессенджеры |
| 25 | Telegram → Notion | Мессенджеры |
| 26 | Telegram → Email | Мессенджеры |
| 27 | Trello → Telegram | Задачи |
| 28 | Trello → Google Sheets | Задачи |
| 29 | Notion → Telegram | Знания |
| 30 | Notion → Email | Знания |
| 31 | Webhook → Telegram | Универсальные |
| 32 | Webhook → Google Sheets | Универсальные |
| 33 | Webhook → amoCRM | Универсальные |
| 34 | Webhook → SMS.ru | Универсальные |
| 35 | Webhook → Email | Универсальные |
| 36 | Tilda → Notion | Формы |
| 37 | Яндекс Формы → SMS.ru | Формы |
| 38 | МойСклад → SMS.ru | Учёт |
| 39 | Google Sheets → Trello | Таблицы |
| 40 | Telegram → Trello | Мессенджеры |
7.3. Приоритизация
- Формы → мессенджер/CRM (самый частый запрос).
- Платежи → уведомления/CRM.
- Таблицы ↔ мессенджеры.
- Универсальный webhook (закрывает длинный хвост).
- Остальное — по заявкам.
8. Сертификация коннектора
8.1. Чек-лист
- Манифест валиден, ключи уникальны.
- Минимальные scopes; лишние не запрашиваются.
-
allowedHostsзаполнен, SSRF невозможен. - Секреты не логируются и не возвращаются.
- Обработаны 401/403/404/409/429/5xx/timeout.
- Есть
sampleи фикстуры. - Contract-тесты зелёные.
- Проверен на sandbox-аккаунте.
- Описаны лимиты и ограничения.
- Есть инструкция «где взять токен».
- Проверена идемпотентность действия (если заявлена).
- Проверено поведение при отзыве доступа.
- Мониторинг и healthcheck настроены.
- Ревью безопасности пройдено.
8.2. Ответственные
| Роль | Зона |
|---|---|
| Connector Engineer | Реализация, тесты |
| Security Reviewer | Scopes, SSRF, секреты |
| QA | Contract/e2e, ошибки |
| Product | Схемы, тексты, каталог |
9. Ограничения и антипаттерны
Запрещено:
- Универсальные «выполнить произвольный запрос».
- Динамические URL из пользовательского ввода.
- Хранение токенов в открытом виде.
- Автоматические повторы неидемпотентных действий без предупреждения.
- Скрытые платежи/подписки на стороне провайдера.
- Парсинг сайтов без официального API (кроме разрешённых случаев).
Не рекомендуется:
- Больше 5 обязательных полей в действии.
- Зависимость от нестабильных внутренних API.
- Сложные преобразования данных (это задача связки, а не коннектора).
10. Поддержка и мониторинг коннекторов
- Healthcheck каждые 5 минут (где возможно).
- Метрики ошибок по типам и кодам.
- Алерт при error rate > 5 % за 15 минут.
- Автоматический circuit breaker.
- Журнал изменений API провайдера (ручной обзор раз в 2 недели).
- План миграции при breaking changes.
- Публичный статус на
/statusбез раскрытия внутренних деталей.
11. Примеры кода (эскиз)
11.1. Telegram: отправка сообщения
async execute(ctx, actionKey, input) {
if (actionKey !== "send_message") {
throw new ConnectorError("internal", "UNKNOWN_ACTION", "Неизвестное действие");
}
const res = await ctx.http.post(
`https://api.telegram.org/bot${ctx.secrets.get("bot_token")}/sendMessage`,
{
chat_id: input.chat_id,
text: input.text,
parse_mode: input.parse_mode ?? "HTML",
disable_notification: input.disable_notification ?? false,
},
{ timeoutMs: 15_000 }
);
if (!res.ok) {
if (res.status === 401) {
throw new ConnectorError("auth", "TELEGRAM_UNAUTHORIZED", "Токен бота недействителен");
}
if (res.status === 429) {
throw new ConnectorError("rate_limit", "TELEGRAM_RATE_LIMIT",
"Telegram ограничил частоту", (res.body.retry_after ?? 1) * 1000);
}
throw new ConnectorError("provider", "TELEGRAM_ERROR", res.body.description);
}
return { message_id: res.body.result.message_id };
}
11.2. Tilda: приём вебхука
async handleWebhook(ctx, req) {
const payload = req.body;
if (!payload || typeof payload !== "object") {
throw new ConnectorError("validation", "TILDA_EMPTY", "Пустой payload");
}
return [{
dedupKey: payload.transactionid ?? hash(payload),
data: {
name: payload.Name ?? payload.name ?? "",
phone: payload.Phone ?? payload.phone ?? "",
email: payload.Email ?? payload.email ?? "",
message: payload.Comment ?? payload.message ?? "",
form_id: payload.formid ?? null,
received_at: new Date().toISOString(),
},
}];
}
Примеры иллюстративные; финальные сигнатуры — по SDK.