PUSH-уведомления в локальной сети без доступа в Интернет - не работает pushManager.subscribe()
Пытаюсь реализовать PUSH-уведомления в локальной сети без доступа в Интернет.
Делал в основном по этому мануалу: Браузерные Push-уведомления на Javascript и PHP.
HTTPS-соединение реализовано самоподписанным сертификатом. Пробую на localhost.
Service Worker успешно регистрируется во всех браузерах.
Сейчас застопорился на том, что не работает метод pushManager.subscribe().
Некоторые браузеры выдают ошибку:
- Firefox: Error retrieving push subscription.
- Microsoft Edge: Registration failed - could not connect to push server.
В других же (Chrome, Opera) эта Promise просто висит и не выдаёт ни resolve, ни reject.
В Firefox пробовал поставить wss://localhost/ или wss://localhost/push/push.php в dom.push.serverURL в about:config - безуспешно.
Подскажите, пожалуйста, с чем это может быть связано, или хотя бы в какую сторону копать?
Насколько я понял, браузер пытается лезть в Интернет на вшитый в него адрес PUSH-сервера, и изменение dom.push.serverURL в Firefox - это как раз то, что мне нужно. Но тогда непонятно, что туда надо вписывать вместо wss://localhost/ или что нужно, чтобы развернуть этот wss://localhost/ и что там должно быть, что он должен отвечать...
У меня сейчас просто Apache2 и всё. На нём развёрнут localhost на 80 и 433 портах.
Также установка на все машины Firefox с изменённым dom.push.serverURL - неудачное решение, т.к. машин много. Может быть есть вариант как-то задать адрес PUSH-сервера непосредственно в коде? В manifest.json, например, или где-то ещё?
Мой код:
push.js
checkNotificationSupported().then(() => { // проверка поддержки ServiceWorker и PushManager
checkNotificationPermission().then(() => { // проверка разрешения на уведомления
navigator.serviceWorker.register('/service-worker.js', {
scope: '/'
}).then(() => {
navigator.serviceWorker.ready.then((registration) => {
document.getElementById('alert').addEventListener('click', () => {
//registration.showNotification('test'); // <-- работает
registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array('открытый ключ в Base64')
}).then(successSubscriptionHandler, (ex) => {
console.error('[Subscript]', ex);
});
});
}, console.error);
}, console.error);
}, console.error);
}, console.error);
function checkNotificationSupported() {
return new Promise((fulfilled, reject) => {
if (!('serviceWorker' in navigator)) {
reject(new Error('[Service workers are not supported by this browser]'));
return;
}
if (!('PushManager' in window)) {
reject(new Error('[Push notifications are not supported by this browser]'));
return;
}
if (!('showNotification' in ServiceWorkerRegistration.prototype)) {
reject(new Error('[Notifications are not supported by this browser]'));
return;
}
fulfilled();
});
}
function checkNotificationPermission() {
return new Promise((fulfilled, reject) => {
if (Notification.permission === 'denied') {
return reject(new Error('[Push messages are blocked]'));
}
if (Notification.permission === 'granted') {
return fulfilled();
}
if (Notification.permission === 'default') {
return Notification.requestPermission().then(result => {
if (result !== 'granted') {
reject(new Error('[Bad permission result]'));
} else {
fulfilled();
}
});
}
return reject(new Error('[Unknown permission]'));
});
}
function urlBase64ToUint8Array(base64String) {
const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
const base64 = (base64String + padding).replace(/\-/g, '+').replace(/_/g, '/');
const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}
function successSubscriptionHandler(subscription) {
console.log('1success1');
const key = subscription.getKey('p256dh');
const token = subscription.getKey('auth');
const contentEncoding = (PushManager.supportedContentEncodings || ['aesgcm'])[0];
const body = new FormData();
body.set('endpoint', subscription.endpoint);
body.set('publicKey', key ? btoa(String.fromCharCode.apply(null, new Uint8Array(key))) : null);
body.set('authToken', token ? btoa(String.fromCharCode.apply(null, new Uint8Array(token))) : null);
body.set('contentEncoding', contentEncoding);
return fetch('/push/subscribe.php', {
method: 'POST',
body: body,
}).then(subscription, console.error);
}
<button id="alert" type="button">Уведомления</button>
service-worker.js
self.addEventListener('push', function(event) {
if (!(self.Notification && self.Notification.permission === 'granted')) {
return;
}
const sendNotification = body => {
const title = "Заголовок уведомления";
return self.registration.showNotification(title, {
body,
});
};
if (event.data) {
const message = event.data.text();
event.waitUntil(sendNotification(message));
}
});
self.addEventListener('install', function(event) {
console.log('install');
});
self.addEventListener('activate', function(event) {
console.log('activate');
});
self.addEventListener('fetch', function(event) {
console.log('fetch');
});
subscribe.php (до сюда алгоритм уже не доходит)
<?php
$subscription = $_POST;
file_put_contents('1.json',json_encode($subscription,true));
if (!isset($subscription['endpoint'])) {
echo 'Error: not a subscription';
return;
}
Мой открытый ключ:
BGEyhQ_EoLTTXcVfA3j8qwDyXCj4gScVbubwetdHPOuiCmK5e0v_VuKquQf8Y6Om6TMdEqm1kHOA
6u5oGXeYB4w
Это, кстати, нормально, что он получился в 2 строчки?
В JS записываю его в переменную через обратные кавычки - `ключ`.
Есть ещё push.php из мануала:
<?php
require '../vendor/autoload.php'; // composer
use Minishlink\WebPush\WebPush;
use Minishlink\WebPush\Subscription;
$subscription = Subscription::create($subscriptionData);
$auth = array(
'VAPID' => array(
'subject' => 'https://localhost',
'publicKey' => file_get_contents('./push/keys/public_key.txt'),
'privateKey' => file_get_contents('./push/keys/private_key.txt'),
)
);
$webPush = new WebPush($auth);
$webPush->queueNotification(
$subscription,
"Тело пуш уведомления"
);
Библиотека minishlink/web-push, если я правильно понял, и есть для реализации своего PUSH-сервера.
Но чтобы её использовать, нужно сначала собрать инфу о подписке, что собственно и должен сделать метод pushManager.subscribe(), далее передать в функцию successSubscriptionHandler(), которая в свою очередь отправит инфу в subscribe.php, где нужно реализовать запись этой инфы в БД. Или я что-то неправильно понимаю?
Дальше я планировал запускать push.php на кроне и тем самым рассылать уведомления. Или как это правильно делается?