Каркас системы настроек (docs/features/settings) и фича TeleCart Pulse с heartbeat (docs/features/telecart-pulse); спека синхронизирована с opt-in. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
11 KiB
Дизайн: честный и отключаемый heartbeat
Дата: 2026-06-30 Статус: согласовано (ожидает финального ревью)
Проблема
Модуль скрытно отправляет на сервер разработчика (pulse.telecart.pro) сведения об
окружении, где он запущен (heartbeat). Покупатель об этом не уведомляется и не может
это отключить. Это нечестно по отношению к покупателю и создаёт риски (GDPR / 152-ФЗ,
претензии маркетплейсов).
Цель
Сделать heartbeat честным: прозрачным и отключаемым, сохранив поведение по умолчанию.
- Прозрачность: в админ-панели покупатель видит, что именно собирается, куда отправляется и зачем.
- Контроль: отдельный переключатель, которым покупатель управляет heartbeat.
- По умолчанию выключено (opt-in). Без явного включения покупателем на сервер ничего не уходит — в том числе у существующих установок. (Изначально планировался opt-out с дефолтом «включено»; решение пересмотрено в пользу opt-in из-за юридических рисков сбора идентифицирующих данных — домена и имени бота — без согласия.)
Границы (scope)
- Только heartbeat. Остальной TeleCart Pulse (Telegram-рассылки, события кампаний)
не трогаем — он гейтится наличием
pulse.api_keyи, по позиции владельца, прав не нарушает. - Концептуально heartbeat считаем частью фичи TeleCart Pulse (одна папка доков), но
у него отдельный собственный выключатель, не завязанный на
pulse.api_keyи не завязанный на секрет подписи. Pulse и heartbeat включаются/выключаются независимо.
Терминология
- heartbeat — периодическая (раз в час по кэшу) отправка информации об окружении:
domain,bot_name,php_version,module_version,opencart_version,opencart_version_core. Триггерится из SPA при открытии WebApp. - TeleCart Pulse — фича рассылок и аналитики кампаний; heartbeat живёт внутри её
namespace в коде (
framework/TeleCartPulse/).
Текущее устройство (как есть)
TeleCartPulseService::handleHeartbeat()(module/.../framework/TeleCartPulse/TeleCartPulseService.php:133) — ставит кэш-флагtelecart_pulse_heartbeat(ttl 3600), дёргаетgetMe(), формирует payload и шлёт POST наPULSE_API_HOST/heartbeat.- Отправка фактически происходит только если есть
heartbeatPayloadSigner(строки 168–170), который создаётся в провайдере при наличии секрета (pulse.heartbeat_secret/PULSE_HEARTBEAT_SECRET). TeleCartPulseServiceProvider(module/.../framework/TeleCartPulse/TeleCartPulseServiceProvider.php:22-35) читает значения из конфига и инжектит их вTeleCartPulseServiceчерез конструктор.- Пользовательской (per-install) настройки для heartbeat сейчас нет.
Решение
Принцип
Использовать существующий паттерн: провайдер читает значение из конфига и инжектит
его в сервис через конструктор (как уже делается для pulse.api_key и signer). Не вводить
новых механизмов гейтинга.
1. Backend
module/.../configs/app.php — секция pulse (строки 86–90): добавить флаг с дефолтом
false (opt-in).
'pulse' => [
'api_key' => '',
'heartbeat_enabled' => false, // новый флаг, opt-in
'batch_size' => 50,
'max_attempts' => 3,
],
TeleCartPulseServiceProvider — прочитать pulse.heartbeat_enabled и передать булевый
флаг в конструктор TeleCartPulseService (рядом с apiKey и heartbeatSigner). Привести
к bool явно.
TeleCartPulseService — принять флаг в конструкторе (новое поле private bool $heartbeatEnabled). В handleHeartbeat() — ранний return в самом начале метода, до
проверки/установки кэша и до getMe(), если флаг выключен:
public function handleHeartbeat(): void
{
if (! $this->heartbeatEnabled) {
return;
}
// ... существующая логика
}
Совместимость с PHP 7.4: без named arguments / промоушена свойств / union-типов. Параметр
конструктора — позиционный, со значением по умолчанию true для обратной совместимости
вызовов в тестах.
2. Frontend (админка) + прозрачность
frontend/admin/src/stores/settings.js — секция pulse (строки 89–92): добавить
heartbeat_enabled в дефолтное состояние state.
frontend/admin/src/views/TeleCartPulseView.vue — добавить ItemBool с переключателем,
привязанным к settings.items.pulse.heartbeat_enabled. В слот-описание — честное, но
нейтральное по тону раскрытие:
- перечислить, что отправляется: домен, имя бота, версия PHP, версия модуля, версии OpenCart;
- получателя назвать обобщённо — «разработчику» (без адреса сервера и периодичности: они не важны покупателю);
- явно указать, что персональные данные покупателей и заказы не передаются;
- описать цель мягко: для улучшения продукта и техподдержки (без акцента на проверке лицензии);
- отметить, что по умолчанию отправка отключена и её можно включить/отключить в любой момент.
Итоговая формулировка согласована с владельцем (нейтральный тон, вариант с оговоркой про отсутствие персональных данных).
3. Поведение для существующих установок
Дефолт false в конфиге мержится с сохранёнными настройками
(ApplicationFactory: Arr::mergeArraysRecursively). У установок без сохранённого значения
heartbeat выключен — на сервер ничего не уходит, пока покупатель явно не включит тумблер.
Когда покупатель сохраняет настройку, значение пишется в oc_setting
(module_telecart_settings) и читается на backend.
4. Гейтинг — итоговая логика
heartbeat реально отправляется только если одновременно: heartbeat_enabled = true
(покупатель включил) и есть signer (секрет настроен). Переключатель — пользовательский
контроль поверх существующего серверного гейта; они независимы.
Тестирование
Unit-тест на TeleCartPulseService::handleHeartbeat():
heartbeat_enabled = false→ метод выходит сразу: HTTP-запрос не выполняется, кэш-флагtelecart_pulse_heartbeatне ставится,getMe()не вызывается.heartbeat_enabled = true+ signer присутствует → payload формируется и уходит (как сейчас).
Запуск: make test-unit (или ./vendor/bin/phpunit --filter ... через make ssh).
Документация (в этом порядке)
docs/features/settings/CLAUDE.md— каркас системы настроек: путь поля «форма в админке →saveSettingsForm→ таблицаoc_setting(module_telecart_settings) →Config\Settings/ DTO → backend», и чек-лист «как добавить новый toggle» по слоям (config-дефолт → провайдер/DTO → store → view-компонентItemBool). По шаблонуdocs/features/_TEMPLATE.md. Добавить строку в реестр вdocs/features/CLAUDE.md.docs/features/telecart-pulse/CLAUDE.md— фича Pulse целиком: рассылки, события кампаний (гейтapi_key) и heartbeat с новым выключателем. Ссылка на[[settings]]. Добавить строку в реестр.
Затрагиваемые файлы (сводка)
module/.../configs/app.php— флагpulse.heartbeat_enabled.module/.../framework/TeleCartPulse/TeleCartPulseServiceProvider.php— чтение и инжект.module/.../framework/TeleCartPulse/TeleCartPulseService.php— поле + ранний return.frontend/admin/src/stores/settings.js— дефолт в state.frontend/admin/src/views/TeleCartPulseView.vue—ItemBool+ текст раскрытия.module/.../tests/Unit/...— тест на гейтинг heartbeat.docs/features/settings/CLAUDE.md,docs/features/telecart-pulse/CLAUDE.md,docs/features/CLAUDE.md(реестр).
Явно вне scope (YAGNI)
- Делаем opt-in (по умолчанию выключено); отдельный consent-баннер на первой установке не нужен — достаточно выключенного по умолчанию переключателя с раскрытием.
- Не трогаем события кампаний и рассылки Pulse.
- Не выносим heartbeat в отдельный namespace/фичу из
TeleCartPulse.