📋 Что получится
- Мгновенные уведомления в браузере
- Разные типы: успех, ошибка, предупреждение
- Звуковые оповещения (опционально)
- Счётчик непрочитанных
Любой бэкенд: Примеры ниже на PHP, но вы можете использовать любой язык — Node.js, Python, Go, Ruby, Java и др.
Pushler.ru предоставляет простой REST API для отправки событий.
🏗️ Архитектура
Событие
заказ, комментарий...
→
Ваш сервер
PHP / Node.js / etc
→
Pushler.ru
WebSocket
Браузер
пользователя
Поток данных:
- На сервере происходит событие (новый заказ, комментарий и т.д.)
- Сервер отправляет сообщение в Pushler.ru
- Pushler.ru доставляет через WebSocket в браузер
- JavaScript показывает уведомление
💻 Код сервера
Ниже примеры на PHP с нашим SDK. Для других языков используйте REST API или официальные SDK.
Установка PHP SDK
Bash
composer require pushler/php-sdk
Сервис уведомлений
PHP
<?php
// NotificationService.php
require_once 'vendor/autoload.php';
use PushlerRu\PushlerClient;
class NotificationService
{
private PushlerClient $pushler;
public function __construct(string $appKey, string $appSecret)
{
$this->pushler = new PushlerClient($appKey, $appSecret);
}
/**
* Отправить уведомление пользователю
*/
public function notify(int $userId, string $title, string $message, string $type = 'info'): void
{
$this->pushler->trigger(
"private-user-{$userId}",
'notification',
[
'id' => uniqid('notif_'),
'title' => $title,
'message' => $message,
'type' => $type, // info, success, warning, error
'timestamp' => date('c'),
]
);
}
/**
* Уведомление об успешном действии
*/
public function success(int $userId, string $title, string $message): void
{
$this->notify($userId, $title, $message, 'success');
}
/**
* Уведомление об ошибке
*/
public function error(int $userId, string $title, string $message): void
{
$this->notify($userId, $title, $message, 'error');
}
/**
* Предупреждение
*/
public function warning(int $userId, string $title, string $message): void
{
$this->notify($userId, $title, $message, 'warning');
}
}
// Создаём сервис
$notifications = new NotificationService(
'key_ваш_ключ',
'secret_ваш_секрет'
);
Использование при создании заказа
PHP
<?php
// create_order.php
require_once 'NotificationService.php';
// Получаем данные заказа из POST
$orderData = $_POST;
$userId = getCurrentUserId(); // Ваша функция авторизации
// Сохраняем заказ в БД
$pdo = getDbConnection();
$stmt = $pdo->prepare('
INSERT INTO orders (user_id, total, manager_id, created_at)
VALUES (?, ?, ?, NOW())
');
$stmt->execute([$userId, $orderData['total'], $orderData['manager_id']]);
$orderId = $pdo->lastInsertId();
// Уведомляем клиента
$notifications->success(
$userId,
'Заказ создан! 🎉',
"Заказ #{$orderId} успешно оформлен. Ожидайте подтверждения."
);
// Уведомляем менеджера
$notifications->notify(
$orderData['manager_id'],
'Новый заказ',
"Поступил заказ #{$orderId} на сумму {$orderData['total']} ₽",
'info'
);
echo json_encode(['order_id' => $orderId, 'status' => 'created']);
Endpoint авторизации канала
PHP
<?php
// auth.php — авторизация приватных каналов
require_once 'vendor/autoload.php';
use PushlerRu\PushlerClient;
$pushler = new PushlerClient('key_ваш_ключ', 'secret_ваш_секрет');
header('Content-Type: application/json');
$channelName = $_POST['channel_name'];
$socketId = $_POST['socket_id'];
$userId = getCurrentUserId();
// Проверяем авторизацию
if (!$userId) {
http_response_code(401);
echo json_encode(['error' => 'Unauthorized']);
exit;
}
// Формат канала: private-user-{userId}
// Пользователь может подписаться только на свой канал
if (preg_match('/^private-user-(\d+)$/', $channelName, $matches)) {
if ((int) $matches[1] !== $userId) {
http_response_code(403);
echo json_encode(['error' => 'Forbidden']);
exit;
}
}
// Генерируем авторизацию
$auth = $pushler->authorizeChannel($channelName, $socketId);
echo json_encode($auth);
🌐 Код клиента (JavaScript)
Подключение SDK
HTML
<!-- Подключение из CDN -->
<script src="https://cdn.pushler.ru/sdk/pushler.min.js"></script>
<!-- Или npm: npm install @pushler/js -->
Vanilla JavaScript
JavaScript
// Конфигурация
const USER_ID = 123; // ID текущего пользователя
const APP_KEY = 'key_ваш_ключ';
// Иконки для разных типов
const icons = {
info: 'ℹ️',
success: '✅',
warning: '⚠️',
error: '❌'
};
// Счётчик непрочитанных
let unreadCount = 0;
// Инициализация Pushler
const pushler = new PushlerClient({
appKey: APP_KEY,
authEndpoint: '/auth.php'
});
// Подписка на приватный канал пользователя
pushler.on('connected', () => {
console.log('Подключено к Pushler');
const channel = pushler.subscribe(`private-user-${USER_ID}`);
channel.on('notification', (data) => {
showNotification(data);
playSound();
updateBadge(++unreadCount);
});
});
// Показать уведомление
function showNotification(data) {
const container = document.getElementById('notifications');
const notification = document.createElement('div');
notification.className = `notification ${data.type}`;
notification.innerHTML = `
<span class="notification-icon">${icons[data.type] || icons.info}</span>
<div class="notification-content">
<div class="notification-title">${escapeHtml(data.title)}</div>
<div class="notification-message">${escapeHtml(data.message)}</div>
</div>
<button class="notification-close" onclick="closeNotification(this)">×</button>
`;
container.appendChild(notification);
// Автоматически скрыть через 5 секунд
setTimeout(() => {
closeNotification(notification.querySelector('.notification-close'));
}, 5000);
}
// Закрыть уведомление
function closeNotification(button) {
const notification = button.closest('.notification');
notification.style.animation = 'slideOut 0.3s ease forwards';
setTimeout(() => notification.remove(), 300);
}
// Обновить счётчик
function updateBadge(count) {
const badge = document.getElementById('notification-count');
if (count > 0) {
badge.textContent = count > 99 ? '99+' : count;
badge.style.display = 'inline-flex';
} else {
badge.style.display = 'none';
}
}
// Воспроизвести звук
function playSound() {
const audio = new Audio('/sounds/notification.mp3');
audio.volume = 0.3;
audio.play().catch(() => {}); // Игнорируем ошибку если звук заблокирован
}
// Экранирование HTML
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
Vue 3 компонент
Vue
<!-- NotificationToast.vue -->
<template>
<Teleport to="body">
<TransitionGroup
name="notification"
tag="div"
class="notifications-container"
>
<div
v-for="notification in notifications"
:key="notification.id"
:class="['notification', notification.type]"
>
<span class="icon">{{ icons[notification.type] }}</span>
<div class="content">
<div class="title">{{ notification.title }}</div>
<div class="message">{{ notification.message }}</div>
</div>
<button v-on:click="remove(notification.id)" class="close">×</button>
</div>
</TransitionGroup>
</Teleport>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import PushlerClient from '@pushler/js';
const props = defineProps({
userId: { type: Number, required: true },
appKey: { type: String, required: true }
});
const emit = defineEmits(['notification', 'unread-change']);
const notifications = ref([]);
const unreadCount = ref(0);
const icons = {
info: 'ℹ️',
success: '✅',
warning: '⚠️',
error: '❌'
};
let pushler = null;
let channel = null;
onMounted(() => {
pushler = new PushlerClient({
appKey: props.appKey,
authEndpoint: '/auth.php'
});
pushler.on('connected', () => {
channel = pushler.subscribe(`private-user-${props.userId}`);
channel.on('notification', handleNotification);
});
});
onUnmounted(() => {
channel?.unsubscribe();
pushler?.disconnect();
});
function handleNotification(data) {
notifications.value.push(data);
unreadCount.value++;
emit('notification', data);
emit('unread-change', unreadCount.value);
// Автоудаление через 5 секунд
setTimeout(() => remove(data.id), 5000);
}
function remove(id) {
const index = notifications.value.findIndex(n => n.id === id);
if (index > -1) {
notifications.value.splice(index, 1);
}
}
</script>
<style scoped>
.notifications-container {
position: fixed;
top: 20px;
right: 20px;
z-index: 9999;
display: flex;
flex-direction: column;
gap: 10px;
}
.notification {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 16px;
background: #1e293b;
border-radius: 12px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
min-width: 300px;
max-width: 400px;
}
.notification.success { border-left: 4px solid #22c55e; }
.notification.error { border-left: 4px solid #ef4444; }
.notification.warning { border-left: 4px solid #f59e0b; }
.notification.info { border-left: 4px solid #3b82f6; }
.icon { font-size: 1.5rem; }
.content { flex: 1; }
.title { font-weight: 600; color: #f1f5f9; }
.message { font-size: 0.9rem; color: #94a3b8; margin-top: 4px; }
.close {
background: none;
border: none;
color: #64748b;
font-size: 1.25rem;
cursor: pointer;
}
.notification-enter-active { animation: slideIn 0.3s ease; }
.notification-leave-active { animation: slideOut 0.3s ease; }
@keyframes slideIn {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
@keyframes slideOut {
from { transform: translateX(0); opacity: 1; }
to { transform: translateX(100%); opacity: 0; }
}
</style>
⚠️ Типичные ошибки
1. Уведомление не приходит
JavaScript
// ❌ Неправильно — публичный канал, но называется как приватный
pushler.subscribe('user-123');
// ✅ Правильно — приватный канал с префиксом
pushler.subscribe('private-user-123');
2. Ошибка авторизации канала
PHP
// ❌ Неправильно — endpoint не проверяет права
$auth = $pushler->authorizeChannel($channelName, $socketId);
echo json_encode($auth);
// ✅ Правильно — проверяем, что пользователь авторизован
$userId = getCurrentUserId();
if (!$userId) {
http_response_code(401);
echo json_encode(['error' => 'Unauthorized']);
exit;
}
// Проверяем, что канал принадлежит этому пользователю
if (preg_match('/^private-user-(\d+)$/', $channelName, $matches)) {
if ((int) $matches[1] !== $userId) {
http_response_code(403);
echo json_encode(['error' => 'Forbidden']);
exit;
}
}
$auth = $pushler->authorizeChannel($channelName, $socketId);
echo json_encode($auth);
3. Уведомление показывается дважды
JavaScript
// ❌ Неправильно — подписка в цикле/при каждом рендере
useEffect(() => {
channel.on('notification', handler); // Добавляется каждый раз!
});
// ✅ Правильно — подписка один раз + очистка
useEffect(() => {
channel.on('notification', handler);
return () => channel.off('notification', handler);
}, []); // Пустой массив зависимостей!
4. authEndpoint не указан
JavaScript
// ❌ Неправильно — нет authEndpoint
const pushler = new PushlerClient({ appKey: 'key_xxx' });
pushler.subscribe('private-user-123'); // Ошибка авторизации!
// ✅ Правильно — указываем endpoint
const pushler = new PushlerClient({
appKey: 'key_xxx',
authEndpoint: '/auth.php'
});
Готовы попробовать?
Создайте бесплатный аккаунт и начните интеграцию за пару минут