Web Push SDK
Браузерный пакет pushdata подписывает браузер на Web Push, регистрирует его в PushData, снимает подписку при выходе и вместе с эталонным service worker отмечает доставку и клик. Работает только с публичным ключом приложения (pk_…), который безопасно хранить в JavaScript страницы; отправки и контакты — с вашего сервера, секретным ключом.
Пакет публикуется. Покаpushdataне появился в npm, соберите его из каталогаapp/sdkрепозитория PushData (pnpm install && pnpm build→dist/) и подключите какfile:-зависимость или скопируйтеdist/index.jsна сайт. API и шаги руководства те же.
Требования: сайт по HTTPS (или localhost), Chrome / Edge / Firefox / Opera, Safari 16.4+ (на iOS — после добавления сайта на экран «Домой»).
1. Ключи приложения
В дашборде откройте Приложения → ваше приложение → Настройки: понадобятся ID приложения и публичный ключ pk_… (раздел «API-ключи»). Секретный ключ pd_… на страницу не попадает.
2. Web Push в PushData
Приложения → ваше приложение → Провайдеры Push → Web Push: нажмите «Сгенерировать ключи VAPID» и укажите контакт (mailto: или URL сайта). Публичный ключ VAPID SDK получает сам (GET /api/v1/public/apps/:id/vapid).
3. Установка
npm install pushdataЭталонный service worker: скачайте app.pushdata.ru/sw.js и положите в корень сайта как /sw.js — воркер обязан быть на том же origin, что и страница. Он показывает уведомление, открывает click_action и сам отмечает delivered / clicked.
4. Подписка
import { PushDataClient } from 'pushdata'
const client = new PushDataClient({
appId: 'APP_ID', // Приложения → Настройки
publicKey: 'pk_…', // Приложения → Настройки → API-ключи (публичный)
apiUrl: 'https://api.pushdata.ru',
// При включённой проверке идентичности — HMAC id пользователя с вашего сервера (шаг 7):
// identityHash: async userId => (await fetch(`/api/pushdata-identity?user=${encodeURIComponent(userId)}`)).text(),
})
if (client.isSupported()) {
// По клику пользователя (браузеры блокируют запрос разрешения без жеста):
// разрешение, регистрация /sw.js, подписка VAPID, POST /api/v1/public/devices.
const device = await client.subscribe({ userId: 'user-123', tags: ['news'] })
console.log(device.id, device.created ? 'new' : 'refreshed')
}Идемпотентно: вызывайте subscribe при каждом визите вошедшего пользователя — подписка обновится, строка устройства останется той же. Без userId SDK держит случайный id браузера, чтобы устройство переподписывалось на свою же строку.
5. Выход пользователя
await client.unsubscribe() // отписка в браузере + DELETE /api/v1/public/devices
const { isSubscribed } = await client.getSubscriptionStatus()6. События
Эталонный воркер отмечает delivered при получении push и clicked при клике по уведомлению (POST /api/v1/events с pushdata_notification_id / pushdata_device_id из payload). Со страницы — client.trackEvent(notificationId, 'opened').
Свой воркер: передайте его путь в swPath и отмечайте события сами:
globalThis.addEventListener('push', (event) => {
const payload = event.data.json()
event.waitUntil(globalThis.registration.showNotification(payload.title, { body: payload.body, data: payload.data }))
})
globalThis.addEventListener('notificationclick', (event) => {
const { pushdata_notification_id, pushdata_device_id, click_action } = event.notification.data ?? {}
event.notification.close()
event.waitUntil(fetch('https://api.pushdata.ru/api/v1/events', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': 'Public pk_…' },
body: JSON.stringify({ type: 'clicked', notification_id: pushdata_notification_id, device_id: pushdata_device_id }),
}))
})6a. Пользовательские события
Сообщайте, что делает пользователь: события запускают сценарии с триггером-именем события и питают сегменты (Контакты → Пользовательские события):
await client.events.track('user-123', 'order_placed', { total: 1290, currency: 'RUB' }, { idempotencyKey: 'order-1001' })
// или для пользователя, на которого зарегистрировано это устройство (userId из subscribe(), иначе анонимный id):
await client.track('cart_updated', { items: 3 })trackMany(userId, events) отправляет до 100 за один запрос. При включённой проверке идентичности вызов несёт хеш так же, как inbox.
7. Проверка идентичности
При включённой Приложения → Настройки → Проверка идентичности подписка с userId должна нести заголовок X-Identity-Hash — hex HMAC-SHA256 от id пользователя на секрете идентичности приложения. Секрет не попадает в браузер: сделайте авторизованный эндпоинт, который возвращает хэш, и передайте функцию в identityHash. Без него PushData ответит 401 identity_unverified. Анонимной подписке хэш не нужен.
8. Inbox in-app
client.inbox — in-app-канал для вошедшего пользователя: тот же публичный ключ, хеш идентичности из identityHash, если проверка включена:
import { createInboxStore } from 'pushdata'
const items = await client.inbox.list('user-123', { unreadOnly: true, limit: 20 })
const unread = await client.inbox.unreadCount('user-123')
await client.inbox.markRead('user-123', items[0].id)
await client.inbox.markAllRead('user-123')
// живой поток: server-sent events с переподключением
const stream = client.inbox.stream('user-123', {
onEvent(e) {
if (e.type === 'message')
showToast(e.item.title, e.unread_count)
},
})
stream.close() // при выходе
// store для своего компонента (React: useSyncExternalStore(store.subscribe, store.getState))
const store = createInboxStore(client.inbox, { userId: 'user-123' })
store.getState() // { items, unreadCount, status: 'connecting' | 'live' | 'offline', loading, error }Готовый виджет <pushdata-inbox> (колокольчик, бейдж, панель) — import 'pushdata/inbox'; атрибуты и темизация — в разделе In-App.
Настройки уведомлений вошедшего пользователя — client.preferences.get(userId) / client.preferences.set(userId, { topic, channelType, enabled }), а import 'pushdata/preferences' регистрирует элемент <pushdata-preferences> (темы × каналы флажками, сохраняется при изменении) — см. Контакты → предпочтения.
9. Без пакета
Тот же сценарий на чистом JavaScript — регистрация воркера, подписка VAPID, POST /api/v1/public/devices с platform: "web", token (endpoint) и ключами web_push_p256dh / web_push_auth в base64url:
const API = 'https://api.pushdata.ru'
const PUBLIC_KEY = 'pk_…'
const VAPID_PUBLIC_KEY = '…' // Приложения → Провайдеры Push → Web Push
const registration = await navigator.serviceWorker.register(`/sw.js?key=${encodeURIComponent(PUBLIC_KEY)}&api=${encodeURIComponent(API)}`)
const toBytes = (s: string) => Uint8Array.from(atob(s.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0))
const toBase64Url = (buf: ArrayBuffer | null) => btoa(String.fromCharCode(...new Uint8Array(buf ?? new ArrayBuffer(0)))).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
const subscription = await registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: toBytes(VAPID_PUBLIC_KEY) })
const res = await fetch(`${API}/api/v1/public/devices`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': `Public ${PUBLIC_KEY}` },
body: JSON.stringify({
platform: 'web',
token: subscription.endpoint,
web_push_p256dh: toBase64Url(subscription.getKey('p256dh')),
web_push_auth: toBase64Url(subscription.getKey('auth')),
user_id: 'user-123',
}),
})
const { id: deviceId } = await res.json()Публичные эндпоинты отвечают на CORS для любого origin.
10. Проверка
- Откройте сайт, нажмите кнопку подписки, разрешите уведомления.
- Приложения → ваше приложение → Устройства: появилась строка с платформой Web и браузером.
- Отправить уведомление → цель «пользователь» или «устройство» → уведомление появляется в браузере; в карточке уведомления растут
deliveredиclicked.
| Симптом | Проверьте |
|---|---|
NOT_SUPPORTED | HTTPS, поддерживаемый браузер; Safari на iOS — сайт добавлен на экран «Домой» |
PERMISSION_DENIED | разрешение запрошено по клику; в настройках сайта уведомления не заблокированы |
VAPID_MISSING | ключи VAPID сгенерированы у провайдера Web Push |
401 unauthorized | publicKey — ключ pk_ именно этого приложения |
401 identity_unverified | проверка идентичности включена — передайте identityHash или подписывайте анонимно |
| воркер не регистрируется | /sw.js лежит в корне сайта на том же origin, отдаётся с Content-Type: application/javascript |
Ошибки SDK — PushDataError с code (NOT_SUPPORTED, PERMISSION_DENIED, VAPID_MISSING, INVALID_CONFIG или код сервиса: unauthorized, identity_unverified, validation_error, rate_limited).