Уведомления iOS могут потерять разрешение на отправку, если пользователь в данный момент активен

Уведомления на iOS могут потерять разрешение на отправку, если уведомление будет подавлено. Код Discourse настроен на выборочное подавление уведомлений, и в результате потеряет разрешение на отправку, если это произойдет три раза.

Вот код сервис-воркера для push-уведомлений.

В этом коде есть критическая ошибка на строке 178. Там сервис-воркер проверяет, активен ли пользователь (то есть не находится ли он в режиме бездействия). В этом случае событие push возвращает false без отображения уведомления.

(Также проверяется payload.hide_when_active, но оказывается, что hide_when_active всегда true, поэтому этот код всегда возвращает false, когда пользователь активен.)

Apple запрещает тихие push-уведомления и отзывает разрешение на push после трех событий, не приводящих к отображению уведомлений

Это недопустимо согласно правилам Apple для push-уведомлений.

https://webkit.org/blog/12945/meet-web-push/

Мощность и конфиденциальность

И проект с открытым исходным кодом WebKit, и Apple рассматривают конфиденциальность как фундаментальное право человека. Как и в случае с другими привилегированными функциями веб-платформы, запрос подписки на push-уведомления требует явного жеста пользователя. Также требуется установить флаг userVisibleOnly в true и выполнить это обещание, всегда показывая уведомление в ответ на push-сообщение.

Web Push API не является приглашением к тихому фоновому выполнению, так как это нарушило бы доверие пользователя и повлияло бы на время работы батареи.

Нарушения обещания userVisibleOnly приведут к отзыву подписки на push-уведомления.

(выделение мое)

Это подробнее объясняется в видео Apple WWDC о веб-push-уведомлениях, в моменте 9:57

https://developer.apple.com/videos/play/wwdc2022/10098/?time=596

Обратите внимание, что мы явно заявляем, что обещаем всегда делать push-уведомления видимыми для пользователя. Хотя стандарт JavaScript Push API опционально допускает тихое выполнение JavaScript в ответ на push, большинство браузеров этого не поддерживают. Safari не поддерживает этого.

… а затем в 13:35:

https://developer.apple.com/videos/play/wwdc2022/10098/?time=814

Как я упоминал, показывая код запроса подписки на push-уведомления, вы должны обещать, что push-уведомления будут видимы для пользователя. Обработка события push не является приглашением к тихому фоновому выполнению вашего JavaScript. Это нарушило бы как доверие пользователя, так и время работы его батареи. При обработке события push вы фактически обязаны отправить уведомление в Центр уведомлений. Другие браузеры имеют меры противодействия нарушению обещания о видимости push-уведомлений для пользователя, и Safari не является исключением. В бета-версии macOS Ventura после трех событий push, в которых вы не отправите уведомление своевременно, подписка вашего сайта на push-уведомления будет отозвана. Вам придется снова пройти процесс запроса разрешений.

(выделение мое)

Apple рекомендует показывать уведомления немедленно, а не после закрытия уведомлений

Рекомендуемый Apple код выглядит следующим образом, в моменте 11:39:

https://developer.apple.com/videos/play/wwdc2022/10098/?time=699

self.addEventListener('push', (event) => {
    let pushMessageJSON = event.data.json();

    // Наш сервер помещает все необходимое для отображения уведомления
    // в наши JSON-данные.
    event.waitUntil(self.registration.showNotification(pushMessageJSON.title, {
        body: pushMessageJSON.body,
        tag: pushMessageJSON.tag,
        actions: [{
            action: pushMessageJSON.actionURL,
            title: pushMessageJSON.actionTitle,
        }]
    }));
}

Помните, как при подписке на push наш JavaScript обещал, что они всегда будут видимы для пользователя? Это означает, что мы должны всегда показывать нативное уведомление платформы в ответ на каждый push. Лучше всего делать это как можно раньше в обработчике события push.

Код Discourse не следует рекомендуемой лучшей практике. Код Discourse сначала закрывает все уведомления, и только затем показывает уведомление.

Discourse всегда должен вызывать showNotification в ответ на событие push, и делать это как можно скорее.

8 лайков

@Falco / @featheredtoast, что вы думаете об этом?

Какое влияние окажет удаление как проверки закрытия, так и проверки простоя?

В целом я совсем не уверен насчёт этой проверки простоя; кажется, она лишь создаёт путаницу.

В худшем случае, если мы потребуем это на Android или в десктопных браузерах (кроме Safari), мы всегда сможем добавить здесь условную логику, но моё ощущение таково, что мы просто должны удалить этот код.

Отличная отладка, @dfabulich :hugs:

3 лайка

Я понимаю, что закрывать уведомления можно, полагаю, если это делается после вызова showNotification.

… но честно говоря, я не вижу смысла в закрытии уведомлений вообще. По-моему, лучше просто позволить им накапливаться в трее уведомлений.

https://developer.mozilla.org/ru/docs/Web/API/Notification/close

Примечание: Этот API не следует использовать только для того, чтобы убрать уведомление с экрана через фиксированный интервал времени, так как этот метод также удалит уведомление из любого трея уведомлений, что помешает пользователям взаимодействовать с ним после первоначального отображения. Корректным использованием этого API будет удаление уведомления, которое больше не актуально (например, пользователь уже прочитал уведомление на веб-странице в случае мессенджера или следующая песня уже воспроизводится в музыкальном приложении).

3 лайка

Судя по тому, что я прочитал на эту тему в выходные, мы даже не можем использовать async/промысы в обработчике события push — нужно показать уведомление как можно скорее, иначе Apple отзовёт права на отправку уведомлений.

Это полностью согласуется с тем, что говорит @dfabulich.

Я считаю, что мы здесь слишком «умничаем». Действительно, стоит убрать все проверки и просто показывать уведомление.

6 лайков

Спасибо тебе огромное @dfabulich за это открытие!

3 лайка

Хорошо, у меня есть PR по этому вопросу.

@Falco / @featheredtoast, стоит ли нам его слить?

Если нам нужно пропустить push-уведомления, это нужно делать на стороне сервера, а не клиента. Логика сворачивания всегда была несколько сомнительной: приложения к этому обычно не прибегают.

2 лайка

О, да, имеет гораздо больше смысла отправлять уведомления только тогда, когда они действительно нужны. Вскоре мы должны будем подавить избыточные уведомления в других местах, но я согласен с тем, чтобы принять это изменение здесь :+1:

Касательно сворачивания уведомлений: жаль, что мы от этого отказываемся, но… в идеале мы хотели бы реализовать автоматическое закрытие, как в других приложениях, когда пользователь проходит через уведомления на любом авторизованном устройстве. Однако это может потребовать дополнительной доработки. Цель та же — не допускать накопления устаревших уведомлений в виде «прилива». Снова же, это нормально, но нам нужно будет вернуться к этому позже, в зависимости от того, насколько сильно это будет вызывать неудобства.

2 лайка