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 на кроне и тем самым рассылать уведомления. Или как это правильно делается?


Ответы (0 шт):