Перейти к содержимому
easy-connect
ГлавнаяДокументацияПлатформа коннекторов
Содержание ТЗ
Техническое заданиеВерсия 1.0 · 18.10.2026

06. Платформа коннекторов

Философия «одна связка — одна функция», манифест, SDK, стартовый каталог и сертификация.

1. Философия

Коннектор easy-connect — это узкий адаптер одной функции, а не универсальный конструктор. Для каждого сервиса мы публикуем только те триггеры и действия, которые нужны готовым связкам каталога.

Правила:

  1. Одна функция — один триггер/действие. Никаких «универсальных» методов с десятками параметров.
  2. Только необходимые поля. Схема действия содержит минимум обязательных полей; остальное — разумные значения по умолчанию.
  3. Предсказуемость. Одинаковые типы данных и формат ошибок во всех коннекторах.
  4. Безопасность. Минимальные OAuth scopes, allowlist доменов, запрет SSRF.
  5. Наблюдаемость. Латентность, ошибки и лимиты каждого коннектора видны в админке.
  6. Версионируемость. Изменения не ломают активные связки.

2. Типы коннекторов

ТипПримерМеханика
Webhook-источникTilda, формы, платёжные событияПровайдер шлёт POST на наш URL
Polling-источникCRM без вебхуков, таблицыОпрос по расписанию с курсором
OAuth-приложениеGoogle, VK, ЯндексAuthorization Code (+PKCE)
Токен-приложениеTelegram Bot API, SMS.ruКлюч/токен из настроек
ГибридamoCRMOAuth + вебхуки

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. Этапы

  1. Исследование. Документация, лимиты, типы авторизации, вебхуки, ошибки.
  2. Манифест. Схемы триггеров/действий, семантика, примеры.
  3. Реализация. Только необходимые методы.
  4. Фикстуры. Реальные ответы API с обезличенными данными.
  5. Тесты. Успех, 401, 429, 5xx, таймаут, невалидный ввод.
  6. Ревью. Безопасность, scopes, allowlist, обработка ошибок.
  7. Публикация. beta → active после 2 недель без критичных инцидентов.
  8. Поддержка. Мониторинг, обновления при изменениях API.
  9. 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 или polling getUpdates).
  • Действие: 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 сервисов

#СервисКатегорияАвторизацияТриггерыДействия
1TildaФормыWebhookНовая заявка—
2TelegramМессенджерыBot tokenНовое сообщение (P1)Отправить сообщение
3amoCRMCRMOAuthНовая сделка (P1)Создать сделку/контакт
4Битрикс24CRMOAuth/webhookНовая заявка (P1)Создать лид/сделку
5Google SheetsТаблицыOAuthНовая строкаДобавить строку
6Яндекс ФормыФормыWebhookНовая заявка—
7VKСоцсетиOAuthНовое сообщение (P1)Отправить сообщение
8МойСкладУчётTokenНовый заказ (P1)Создать заказ/контрагента
9SMS.ruSMSToken—Отправить SMS
10SMS AeroSMSToken—Отправить SMS
11ЮKassaПлатежиWebhookУспешный платёж—
12Тильда/CRM webhookУниверсальныйWebhookВходящий JSON—
13NotionБазы знанийOAuth—Добавить запись (P1)
14TrelloЗадачиToken—Создать карточку
15Email (SMTP)EmailSMTP—Отправить письмо

Триггеры с пометкой P1 могут быть добавлены в течение беты. Для запуска MVP достаточно действий и вебхук-источников.

7.2. 40 связок (примеры)

#СвязкаКатегория
1Tilda → TelegramФормы
2Tilda → amoCRMФормы
3Tilda → Битрикс24Формы
4Tilda → Google SheetsФормы
5Tilda → SMS.ruФормы
6Tilda → EmailФормы
7Яндекс Формы → TelegramФормы
8Яндекс Формы → amoCRMФормы
9Яндекс Формы → Google SheetsФормы
10Google Sheets → TelegramТаблицы
11Google Sheets → amoCRMТаблицы
12Google Sheets → SMS.ruТаблицы
13Google Sheets → EmailТаблицы
14ЮKassa → TelegramПлатежи
15ЮKassa → Google SheetsПлатежи
16ЮKassa → amoCRMПлатежи
17ЮKassa → EmailПлатежи
18МойСклад → TelegramУчёт
19МойСклад → Google SheetsУчёт
20МойСклад → amoCRMУчёт
21VK → TelegramСоцсети
22VK → Google SheetsСоцсети
23VK → amoCRMСоцсети
24Telegram → Google SheetsМессенджеры
25Telegram → NotionМессенджеры
26Telegram → EmailМессенджеры
27Trello → TelegramЗадачи
28Trello → Google SheetsЗадачи
29Notion → TelegramЗнания
30Notion → EmailЗнания
31Webhook → TelegramУниверсальные
32Webhook → Google SheetsУниверсальные
33Webhook → amoCRMУниверсальные
34Webhook → SMS.ruУниверсальные
35Webhook → EmailУниверсальные
36Tilda → NotionФормы
37Яндекс Формы → SMS.ruФормы
38МойСклад → SMS.ruУчёт
39Google Sheets → TrelloТаблицы
40Telegram → TrelloМессенджеры

7.3. Приоритизация

  1. Формы → мессенджер/CRM (самый частый запрос).
  2. Платежи → уведомления/CRM.
  3. Таблицы ↔ мессенджеры.
  4. Универсальный webhook (закрывает длинный хвост).
  5. Остальное — по заявкам.

8. Сертификация коннектора

8.1. Чек-лист

  • Манифест валиден, ключи уникальны.
  • Минимальные scopes; лишние не запрашиваются.
  • allowedHosts заполнен, SSRF невозможен.
  • Секреты не логируются и не возвращаются.
  • Обработаны 401/403/404/409/429/5xx/timeout.
  • Есть sample и фикстуры.
  • Contract-тесты зелёные.
  • Проверен на sandbox-аккаунте.
  • Описаны лимиты и ограничения.
  • Есть инструкция «где взять токен».
  • Проверена идемпотентность действия (если заявлена).
  • Проверено поведение при отзыве доступа.
  • Мониторинг и healthcheck настроены.
  • Ревью безопасности пройдено.

8.2. Ответственные

РольЗона
Connector EngineerРеализация, тесты
Security ReviewerScopes, SSRF, секреты
QAContract/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.