---
name: kodoceh_web_push
description: "Web push на сайтах КодоЦех — где, как устроен, как проверять end-to-end (включая браузерный leg через настоящий Chrome)"
metadata: 
  node_type: memory
  type: project
  originSessionId: b11e39bd-4f6f-47c9-ba7b-680ccf041a5b
---

Web push («как в приложении») на 3 продуктах КодоЦех: **ВакХантер, Автеро, Гаечкин** (студия-зонтик без push). Проверено 2026-06-28 — все три рабочие.

- **Где код:** фронт — общий `packages/ui/src/EnablePush.tsx` (кнопка «Включить уведомления») + `apps/<prod>/public/sw.js` (push/notificationclick) + регистрация SW в `apps/<prod>/app/layout.tsx`. Бэк: VakHunter `web/webapp.py` (+`bot/web_push_bridge.py`), Автеро `app/webpush.py`, Гаечкин `gaechkin/api/cab_push.py`. Эндпоинты у всех: `GET /api/push/vapid`, `POST /api/push/subscribe`, `POST /api/push/test`.
- **VAPID самогенерируемый** (НЕ секрет провайдера): ключ создаётся при первом `vapid_public()` в файл (VakHunter `~/.vakhunter_vapid.pem`; Автеро `~/Services/avtero/data/vapid_private.pem`; Гаечкин `~/Разработки/Gaechkin/data/vapid_private.pem`). Push включается, если установлен `pywebpush` (+`py_vapid`,`cryptography`). Нет pywebpush → `vapid_public()=None` → `/push/vapid` отдаёт `enabled:false` → фронт прячет кнопку.
- **Порты бэков (мини):** VakHunter web :20452, Автеро :8201, Гаечкин :20150. Фронт-прокси: vakhunter `/api/vh/*`→:20452/api/*.
- **Как проверять (правило 7):** (1) `curl :PORT/api/push/vapid` → `{enabled:true,publicKey}` у всех 3; (2) мини достаёт push-сервисы: `curl https://{fcm.googleapis.com,updates.push.services.mozilla.com,web.push.apple.com}/` → 404/406/405 = доступны; (3) **серверный send-путь** — поднять локальный http-перехват, сгенерить крафт-подписку (валидные p256dh/auth через cryptography ec SECP256R1) с endpoint→перехват, вызвать `pywebpush.webpush(sub, data, vapid_private_key=<ключ продукта>, vapid_claims)` → ждём 201 + запрос с `Content-Encoding: aes128gcm` и `Authorization: vapid t=<JWT>`. Скрипты были в scratchpad `push_sendpath.py`.
- **Браузерный leg — КАК ПРОВЕРИТЬ АВТОМАТОМ (рабочий метод, 2026-06-28):** Playwright **bundled Chromium НЕ подписывается** (incognito Chrome блокирует Push API; persistent → «push service not available», т.к. в сборке нет Google API-ключей для FCM). Playwright **Firefox тоже** не поднимает push («Error retrieving push subscription»). **РЕШЕНИЕ — настоящий Google Chrome через `executable_path`:** скачать `curl -sL https://dl.google.com/chrome/mac/universal/stable/GGRO/googlechrome.dmg`, `hdiutil attach`, скопировать `.app`, `p.chromium.launch_persistent_context(profile, executable_path="…/Google Chrome.app/Contents/MacOS/Google Chrome", headless=True)` + `grant_permissions(["notifications"])` + добавить cookie сессии → `pushManager.subscribe(applicationServerKey)` регистрируется в **реальном FCM**, `/push/subscribe`→200, `/push/test`→`{ok:true,sent:1}`, а **видимое уведомление** читается через `reg.getNotifications()`. **Полный E2E пройден для всех 3 продуктов 2026-06-28** (FCM→SW→showNotification наблюдалось). Сессии: VakHunter `web.webapp.make_session(uid,name)`/cookie `vh_session`, Автеро `app.webauth.make_session({tg_id,name})`/`pk_session` (account резолвится по tg_id), Гаечкин `gaechkin.api.cab_session.make_session({tg_id,name})`/`gk_session`, путь /cabinet (не /app). Скрипт был scratchpad `push_e2e.py` (env CHROME_EXEC/BASE/API_PREFIX/COOKIE_NAME/COOKIE_VAL/APP_PATH). Реальный Chrome НЕ оставлен (удалён после теста; ~685M) — скачать заново при следующей проверке.

См. [[kodoceh_web_overview]], [[vakhunter_overview]].
