_Автор выбрал Open Internet / Free Speech Fund для получения пожертвования в рамках https://do.co/w4do-cta [Писать для DOnations] программа.
Вступление
Сеть постоянно развивается, и теперь она может выполнять функции, которые ранее были доступны только на родных мобильных устройствах. Введение JavaScript service worker предоставило веб-пользователям новые возможности для таких задач, как фоновая синхронизация, автономное кэширование и отправка https: // developer. .mozilla.org / ru / docs / Web / API / Push_API [push-уведомления].
Push-уведомления позволяют пользователям получать обновления для мобильных и веб-приложений. Они также позволяют пользователям повторно взаимодействовать с существующими приложениями, используя настраиваемый и соответствующий контент.
В этом руководстве вы настроите приложение Django в Ubuntu 18.04, которое отправляет push-уведомления всякий раз, когда есть действие, которое требует от пользователя посетить приложение. Для создания этих уведомлений вы будете использовать пакет Django-Webpush, а также настроить и зарегистрировать работника службы для отображения уведомлений клиенту. Рабочее приложение с уведомлениями будет выглядеть так:
изображение: https: //assets.digitalocean.com/articles/django_push_18_04/web_push_final.png [финал веб-публикации]
Предпосылки
Прежде чем начать это руководство, вам понадобится следующее:
-
Один сервер Ubuntu 18.04 с некорневым пользователем и активным межсетевым экраном. Вы можете следовать инструкциям, приведенным в этом https://www.digitalocean.com/community/tutorials/initial-server-setup-with-ubuntu-18-04 руководстве по установке серверного сервера] для получения дополнительной информации о том, как создать Ubuntu 18.04. сервер.
-
+ pip +
и+ venv +
установлены после этих https://www.digitalocean.com/community/tutorials/how-to-install-the-django-web-framework-on-ubuntu-18-04#install- с-пип-в-виртуальной окружающей среды [руководящих принципов]. -
Проект Django под названием
++
, созданный в вашем домашнем каталоге, настроен в соответствии с этими рекомендациями на https://www.digitalocean.com/community/tutorials/how-to-install-the-django-web-framework-on- ubuntu-18-04 # creation-a-sample-project [создание примера проекта Django в Ubuntu 18.04]. Обязательно https://www.digitalocean.com/community/tutorials/how-to-install-the-django-web-framework-on-ubuntu-18-04#modifying-allowed_hosts-in-the-django-settings [добавьте IP-адрес вашего сервера в директиву+ ALLOWED_HOSTS +
] в вашем файле+ settings.py +
.
Шаг 1 - Установка Django-Webpush и получение Vapid ключей
Django-Webpush - это пакет, который позволяет разработчикам интегрировать и отправлять веб-push-уведомления в приложения Django. Мы будем использовать этот пакет для запуска и отправки push-уведомлений из нашего приложения. На этом этапе вы установите Django-Webpush и получите ключи _VoluID Application Server Identification (VAPID) _, необходимые для идентификации вашего сервера и обеспечения уникальности каждого запроса.
Убедитесь, что вы находитесь в каталоге проекта + ~ / +
, который вы создали в предварительных условиях:
cd ~/
Активируйте вашу виртуальную среду:
source /bin/activate
Обновите вашу версию + pip +
, чтобы обеспечить ее актуальность:
pip install --upgrade pip
Установите Django-Webpush:
pip install django-webpush
После установки пакета добавьте его в список приложений в вашем файле + settings.py +
. Сначала откройте + settings.py +
:
nano ~/djangopush/djangopush/settings.py
Добавьте + webpush +
в список + INSTALLED_APPS +
:
~ / Djangopush / djangopush / settings.py
...
INSTALLED_APPS = [
...
]
...
Сохраните файл и выйдите из редактора.
Запустите migrations в приложении, чтобы применить внесенные изменения в схему базы данных:
python manage.py migrate
Вывод будет выглядеть следующим образом, указывая на успешную миграцию:
OutputOperations to perform:
Apply all migrations: admin, auth, contenttypes, sessions, webpush
Running migrations:
Applying webpush.0001_initial... OK
Следующим шагом в настройке push-уведомлений является получение VAPID-ключей. Эти ключи идентифицируют сервер приложений и могут использоваться для уменьшения секретности URL-адресов принудительной подписки, поскольку они ограничивают подписки для конкретного сервера.
Чтобы получить ключи VAPID, перейдите в веб-приложение wep-push-codelab. Здесь вам дадут автоматически сгенерированные ключи. Скопируйте закрытый и открытый ключи.
Затем создайте новую запись в + settings.py +
для вашей информации VAPID. Сначала откройте файл:
nano ~/djangopush/djangopush/settings.py
Затем добавьте новую директиву + WEBPUSH_SETTINGS +
с вашими открытыми и закрытыми ключами VAPID и адресом электронной почты ниже + AUTH_PASSWORD_VALIDATORS +
:
~ / Djangopush / djangopush / settings.py
...
AUTH_PASSWORD_VALIDATORS = [
...
]
# Internationalization
# https://docs.djangoproject.com/en/2.0/topics/i18n/
...
Не забудьте заменить значения заполнителей `, `
и ++
своей собственной информацией. Ваш электронный адрес будет таким, каким вы будете уведомлены, если у сервера push возникнут какие-либо проблемы.
Далее мы настроим представления, которые будут отображать домашнюю страницу приложения и запускать push-уведомления для подписанных пользователей.
Шаг 2 - Настройка просмотров
На этом шаге мы настроим простой + home +
view с помощью https://docs.djangoproject.com/en. /2.1/ref/request-response/#django.http.HttpRequest [+ HttpResponse +
объект ответа] для нашей домашней страницы вместе с представлением + send_push +
. Представления - это функции, которые возвращают объекты ответа из веб-запросов. Представление + send_push +
будет использовать библиотеку Django-Webpush для отправки push-уведомлений, которые содержат данные, введенные пользователем на домашней странице.
Перейдите в папку + ~ / djangopush / djangopush +
:
cd ~/djangopush/djangopush
Запуск + ls +
внутри папки покажет вам основные файлы проекта:
Output/__init__.py
/settings.py
/urls.py
/wsgi.py
Файлы в этой папке автоматически генерируются утилитой + django-admin +
, которую вы использовали для создания своего проекта в предварительных условиях. Файл + settings.py +
содержит конфигурации всего проекта, такие как установленные приложения и статическая корневая папка. Файл + urls.py +
содержит конфигурации URL для проекта. Здесь вы можете настроить маршруты в соответствии с вашими созданными видами.
Создайте новый файл в каталоге + ~ / djangopush / djangopush +
с именем + views.py +
, который будет содержать представления для вашего проекта:
nano ~/djangopush/djangopush/views.py
Первый вид, который мы создадим, - это вид + home +, который отобразит домашнюю страницу, на которую пользователи могут отправлять push-уведомления. Добавьте следующий код в файл:
~ / Djangopush / djangopush / views.py
from django.http.response import HttpResponse
from django.views.decorators.http import require_GET
@require_GET
def home(request):
return HttpResponse('<h1>Home Page<h1>')
Представление + home +
оформляется декоратором + require_GET +
, который ограничивает представление только запросами GET. Представление обычно возвращает ответ на каждый сделанный ему запрос. Это представление возвращает простой тег HTML в качестве ответа.
Следующее представление, которое мы создадим, это + send_push +
, которое будет обрабатывать отправленные push-уведомления, используя пакет + django-webpush +
. Он будет ограничен только запросами POST и будет освобожден от защиты Cross Site Request Forgery (CSRF). Это позволит вам проверить представление, используя Postman или любую другую службу RESTful. В производстве, однако, вы должны удалить этот декоратор, чтобы не оставлять ваши представления уязвимыми для CSRF.
Чтобы создать представление + send_push +
, сначала добавьте следующий импорт, чтобы включить ответы JSON, и получите доступ к функции + send_user_notification +
в библиотеке + webpush +
:
~ / Djangopush / djangopush / views.py
from django.http.response import , HttpResponse
from django.views.decorators.http import require_GET,
Затем добавьте декоратор + require_POST +
, который будет использовать тело запроса, отправленное пользователем, для создания и запуска push-уведомления:
~ / Djangopush / djangopush / views.py
@require_GET
def home(request):
...
@require_POST
@csrf_exempt
def send_push(request):
try:
body = request.body
data = json.loads(body)
if 'head' not in data or 'body' not in data or 'id' not in data:
return JsonResponse(status=400, data={"message": "Invalid data format"})
user_id = data['id']
user = get_object_or_404(User, pk=user_id)
payload = {'head': data['head'], 'body': data['body']}
send_user_notification(user=user, payload=payload, ttl=1000)
return JsonResponse(status=200, data={"message": "Web push successful"})
except TypeError:
return JsonResponse(status=500, data={"message": "An error occurred"})
Мы используем два декоратора для представления + send_push +
: декоратор + require_POST +
, который ограничивает представление только запросами POST, и декоратор + csrf_exempt +
, который освобождает представление от защиты CSRF.
Это представление ожидает данные POST и выполняет следующие действия: получает «+ body » запроса и, используя пакет https://docs.python.org/3/library/json.html[json], десериализует документ JSON. к объекту Python, используя https://docs.python.org/3.6/library/json.html [` json.loads `]. ` json.loads +` берет структурированный документ JSON и преобразует его в объект Python.
Представление ожидает, что объект тела запроса будет иметь три свойства:
-
+ head +
: заголовок push-уведомления. -
+ body +
: тело уведомления. -
+ id +
:+ id +
пользователя запроса.
Если какое-либо из обязательных свойств отсутствует, представление вернет + JSONResponse +
со статусом 404 «Не найдено». Если пользователь с указанным первичным ключом существует, представление вернет + user +
с соответствующим первичным ключом, используя https://docs.djangoproject.com/en/2.1/topics/http/shortcuts/#get-object -или-404 [+ get_object_or_404 +
function] из библиотеки + django.shortcuts +
. Если пользователь не существует, функция вернет ошибку 404.
Представление также использует функцию + send_user_notification +
из библиотеки + webpush +
. Эта функция принимает три параметра:
-
+ Пользователь +
: получатель push-уведомления. -
+ payload +
: информация уведомления, которая включает в себя уведомление+ head +
и+ body +
. -
+ ttl +
: максимальное время в секундах, в течение которого уведомление должно храниться, если пользователь находится в автономном режиме.
Если ошибок не возникает, представление возвращает «+ JSONResponse » со статусом «200» и объектом данных. Если происходит ` KeyError `, представление вернет статус 500 «Внутренняя ошибка сервера». ` KeyError +` возникает, когда запрошенный ключ объекта не существует.
На следующем шаге мы создадим соответствующие URL-маршруты, соответствующие представлениям, которые мы создали.
Шаг 3 - Сопоставление URL-адресов с представлениями
Django позволяет создавать URLs, которые подключаются к представлениям с помощью модуля Python, называемого + URLconf +
. Этот модуль сопоставляет выражения пути URL с функциями Python (ваши представления). Обычно файл конфигурации URL генерируется автоматически при создании проекта. На этом этапе вы обновите этот файл, добавив в него новые маршруты для представлений, созданных на предыдущем шаге, а также URL-адреса для приложения + django-webpush +
, которое предоставит конечные точки для подписки пользователей на push-уведомления.
Для получения дополнительной информации о представлениях см. Https://www.digitalocean.com/community/tutorials/how-to-create-django-views[How To Create Django Views].
Откройте + urls.py +
:
nano ~/djangopush/djangopush/urls.py
Файл будет выглядеть так:
~ / Djangopush / djangopush / urls.py
"""untitled URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/2.1/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
urlpatterns = [
path('admin/', admin.site.urls),
]
Следующим шагом является сопоставление созданных представлений с URL-адресами Во-первых, добавьте импорт + include +
, чтобы гарантировать, что все маршруты для библиотеки Django-Webpush будут добавлены в ваш проект:
~ / Djangopush / djangopush / urls.py
"""webpushdjango URL Configuration
...
"""
from django.contrib import admin
from django.urls import path,
Затем импортируйте представления, созданные вами на последнем шаге, и обновите список + urlpatterns +
, чтобы отобразить ваши представления:
~ / Djangopush / djangopush / urls.py
"""webpushdjango URL Configuration
...
"""
from django.contrib import admin
from django.urls import path,
urlpatterns = [
path('admin/', admin.site.urls),
]
Здесь список + urlpatterns +
регистрирует URL-адреса пакета + django-webpush +
и отображает ваши взгляды на URL-адреса + / send_push +
и + / home +
.
Давайте проверим представление «+ / home +», чтобы убедиться, что оно работает так, как задумано. Убедитесь, что вы находитесь в корневом каталоге проекта:
cd ~/djangopush
Запустите свой сервер, выполнив следующую команду:
python manage.py runserver :8000
Перейдите к + http: //: 8000 +
. Вы должны увидеть следующую домашнюю страницу:
изображение: https: //assets.digitalocean.com/articles/django_push_18_04/django_push_home.png [Начальный вид домашней страницы]
На этом этапе вы можете уничтожить сервер с помощью + CTRL + C +
, и мы перейдем к созданию шаблонов и их отображению в наших представлениях с использованием функции + render +
.
Шаг 4 - Создание шаблонов
Шаблонный движок Django позволяет вам определять пользовательские слои вашего приложения с помощью шаблонов, которые похожи на HTML-файлы. На этом шаге вы создадите и отобразите шаблон для представления + home +
.
Создайте папку с именем + templates +
в корневом каталоге вашего проекта:
mkdir ~/djangopush/templates
Если вы запустите + ls +
в корневой папке вашего проекта на этом этапе, результат будет выглядеть следующим образом:
Output/djangopush
/templates
db.sqlite3
manage.py
/my_env
Создайте файл с именем + home.html
в папке` + templates`:
nano ~/djangopush/templates/home.html
Добавьте следующий код в файл, чтобы создать форму, в которую пользователи могут вводить информацию для создания push-уведомлений:
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="vapid-key" content="{{ vapid_key }}">
{% if user.id %}
<meta name="user_id" content="{{ user.id }}">
{% endif %}
<title>Web Push</title>
<link href="https://fonts.googleapis.com/css?family=PT+Sans:400,700" rel="stylesheet">
</head>
<body>
<div>
<form id="send-push__form">
<h3 class="header">Send a push notification</h3>
<p class="error"></p>
<input type="text" name="head" placeholder="Header: Your favorite airline 😍">
<textarea name="body" id="" cols="30" rows="10" placeholder="Body: Your flight has been cancelled 😱😱😱"></textarea>
<button>Send Me</button>
</form>
</div>
</body>
</html>
+ Body +
файла включает в себя форму с двумя полями: элемент + input
будет содержать заголовок / заголовок уведомления, а элемент` + textarea` будет содержать тело уведомления.
В разделе + head
файла есть два тега` + meta`, которые будут содержать открытый ключ VAPID и идентификатор пользователя. Эти две переменные необходимы для регистрации пользователя и отправки им push-уведомлений. Идентификатор пользователя здесь необходим, потому что вы будете отправлять запросы AJAX на сервер, а + id +
будет использоваться для идентификации пользователя. Если текущий пользователь является зарегистрированным пользователем, то шаблон создаст тег + meta
со своим` + id` в качестве содержимого.
Следующим шагом будет сообщить Django, где найти ваши шаблоны. Для этого вы отредактируете + settings.py +
и обновите список + TEMPLATES +
.
Откройте файл + settings.py +
:
nano ~/djangopush/djangopush/settings.py
Добавьте следующее в список + DIRS +
, чтобы указать путь к каталогу шаблонов:
~ / Djangopush / djangopush / settings.py
...
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
...
],
},
},
]
...
Затем в файле + views.py
обновите представление` + home + , чтобы отобразить шаблон
+ home.html`. Откройте файл:
nano ~/djangpush/djangopush/views.py
Сначала добавьте несколько дополнительных импортов, в том числе конфигурацию + settings +
, которая содержит все настройки проекта из файла + settings.py +
, и функцию + render +
из + django.shortcuts +
:
~ / Djangopush / djangopush / views.py
...
from django.shortcuts import , get_object_or_404
...
import json
...
Затем удалите исходный код, который вы добавили в представление + home +
, и добавьте следующее, которое указывает, как будет отображаться только что созданный шаблон:
~ / Djangopush / djangopush / views.py
...
@require_GET
def home(request):
Код назначает следующие переменные:
-
+ webpush_settings +
: ему присваивается значение атрибута+ WEBPUSH_SETTINGS +
из конфигурации+ settings +
. -
+ vapid_key +
: получает значение+ VAPID_PUBLIC_KEY +
из объекта+ webpush_settings +
для отправки клиенту. Этот открытый ключ сверяется с закрытым ключом, чтобы гарантировать, что клиент с открытым ключом может получать push-сообщения от сервера. -
+ user +
: эта переменная приходит из входящего запроса. Всякий раз, когда пользователь отправляет запрос на сервер, сведения об этом пользователе хранятся в поле «+ пользователь +».
Https://docs.djangoproject.com/en/2.1/topics/http/shortcuts/#render [+ render +
function] вернет файл HTML и https://docs.djangoproject.com/en/2.1 /ref/templates/api/#django.template.Context[context object], содержащий текущего пользователя и vapid открытый ключ сервера. Здесь требуется три параметра: + request +
, + template +
для визуализации и объект, который содержит переменные, которые будут использоваться в шаблоне.
Создав наш шаблон и обновив представление + home +
, мы можем перейти к настройке Django для обслуживания наших статических файлов.
Шаг 5 - Обслуживание статических файлов
Веб-приложения включают в себя CSS, JavaScript и другие файлы изображений, которые Django называет «статическими файлами». Django позволяет вам собирать все статические файлы из каждого приложения в вашем проекте в одном месте, откуда они обслуживаются. Это решение называется + django.contrib.staticfiles +
. На этом этапе мы обновим наши настройки, чтобы сообщить Django, где будут храниться наши статические файлы.
Откройте + settings.py +
:
nano ~/djangopush/djangopush/settings.py
В + settings.py +
, сначала убедитесь, что + STATIC_URL +
был определен:
~ / Djangopush / djangopush / settings.py
...
STATIC_URL = '/static/'
Затем добавьте список каталогов с именем + STATICFILES_DIRS +
, где Django будет искать статические файлы:
~ / Djangopush / djangopush / settings.py
...
STATIC_URL = '/static/'
Теперь вы можете добавить + STATIC_URL +
в список путей, определенных в вашем файле + urls.py +
.
Откройте файл:
nano ~/djangopush/djangopush/urls.py
Добавьте следующий код, который импортирует конфигурацию URL + static +
и обновит список + urlpatterns +
. Вспомогательная функция здесь использует свойства + STATIC_URL +
и + STATIC_ROOT +
, которые мы указали в файле + settings.py +
, для обслуживания статических файлов проекта:
~ / Djangopush / djangopush / urls.py
...
urlpatterns = [
...
]
С нашими настройками статических файлов мы можем перейти к стилизации домашней страницы приложения.
Шаг 6 - стилизация домашней страницы
После настройки приложения для обслуживания статических файлов вы можете создать внешнюю таблицу стилей и связать ее с файлом + home.html +
для стилизации домашней страницы. Все ваши статические файлы будут храниться в каталоге + static +
в корневой папке вашего проекта.
Создайте папку + static +
и папку + css +
в папке + static +
:
mkdir -p ~/djangopush/static/css
Откройте файл css с именем + styles.css +
внутри папки + css +
:
nano ~/djangopush/static/css/styles.css
Добавьте следующие стили для домашней страницы:
~ / Djangopush / статический / CSS / styles.css
body {
height: 100%;
background: rgba(0, 0, 0, 0.87);
font-family: 'PT Sans', sans-serif;
}
div {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
form {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 35%;
margin: 10% auto;
}
form > h3 {
font-size: 17px;
font-weight: bold;
margin: 15px 0;
color: orangered;
text-transform: uppercase;
}
form > .error {
margin: 0;
font-size: 15px;
font-weight: normal;
color: orange;
opacity: 0.7;
}
form > input, form > textarea {
border: 3px solid orangered;
box-shadow: unset;
padding: 13px 12px;
margin: 12px auto;
width: 80%;
font-size: 13px;
font-weight: 500;
}
form > input:focus, form > textarea:focus {
border: 3px solid orangered;
box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.2);
outline: unset;
}
form > button {
justify-self: center;
padding: 12px 25px;
border-radius: 0;
text-transform: uppercase;
font-weight: 600;
background: orangered;
color: white;
border: none;
font-size: 14px;
letter-spacing: -0.1px;
cursor: pointer;
}
form > button:disabled {
background: dimgrey;
cursor: not-allowed;
}
Создав таблицу стилей, вы можете связать ее с файлом + home.html +
с помощью static tags тегов , Откройте файл + home.html +
:
nano ~/djangopush/templates/home.html
Обновите раздел + head +
, добавив ссылку на внешнюю таблицу стилей:
~ / Djangopush / шаблоны / home.html
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
...
</head>
<body>
...
</body>
</html>
Убедитесь, что вы находитесь в основной директории проекта и снова запустите сервер, чтобы проверить свою работу:
cd ~/djangopush
python manage.py runserver :8000
Когда вы посещаете + http: //: 8000 +
, это должно выглядеть так:
image: https: //assets.digitalocean.com/articles/django_push_18_04/push_styled_home.png [просмотр домашней страницы] + Опять же, вы можете убить сервер с помощью + CTRL + C +
.
Теперь, когда вы успешно создали страницу + home.html
и стилизовали ее, вы можете подписать пользователей на push-уведомления, когда они посещают домашнюю страницу.
Шаг 7 - Регистрация работника сервиса и подписка пользователей на push-уведомления
Push-уведомления через Интернет могут уведомлять пользователей об обновлениях приложений, на которые они подписаны, или предлагать им повторно подключиться к приложениям, которые они использовали в прошлом. Они используют две технологии: push API и https://developer.mozilla.org/en-US/docs/. Web / API / Notifications_API [уведомления] API. Обе технологии основаны на присутствии сервисного работника.
Push вызывается, когда сервер предоставляет информацию работнику службы, а работник службы использует API уведомлений для отображения этой информации.
Мы подпишем наших пользователей на push, а затем отправим информацию из подписки на сервер, чтобы зарегистрировать их.
В каталоге + static +
создайте папку с именем + js +
:
mkdir ~/djangopush/static/js
Создайте файл с именем + registerSw.js +
:
nano ~/djangopush/static/js/registerSw.js
Добавьте следующий код, который проверяет, поддерживаются ли работники службы в браузере пользователя, прежде чем пытаться зарегистрировать работника службы:
~ / djangopush / статический / JS / registerSw.js
const registerSw = async () => {
if ('serviceWorker' in navigator) {
const reg = await navigator.serviceWorker.register('sw.js');
initialiseState(reg)
} else {
showNotAllowed("You can't send push notifications ☹️😢")
}
};
Во-первых, перед регистрацией их функция + registerSw +
проверяет, поддерживает ли браузер обслуживающий персонал. После регистрации он вызывает функцию + initializeState +
с регистрационными данными. Если сервисные работники не поддерживаются в браузере, он вызывает функцию + showNotAllowed +
.
Затем добавьте следующий код ниже функции + registerSw +
, чтобы проверить, имеет ли пользователь право на получение push-уведомлений, прежде чем пытаться подписаться на них:
~ / djangopush / статический / JS / registerSw.js
...
const initialiseState = (reg) => {
if (!reg.showNotification) {
showNotAllowed('Showing notifications isn\'t supported ☹️😢');
return
}
if (Notification.permission === 'denied') {
showNotAllowed('You prevented us from showing notifications ☹️🤔');
return
}
if (!'PushManager' in window) {
showNotAllowed("Push isn't allowed in your browser 🤔");
return
}
subscribe(reg);
}
const showNotAllowed = (message) => {
const button = document.querySelector('form>button');
button.innerHTML = `${message}`;
button.setAttribute('disabled', 'true');
};
Функция + initializeState +
проверяет следующее:
-
Независимо от того, активировал ли пользователь уведомления, используя значение
+ reg.showNotification +
. -
Предоставил ли пользователь разрешение приложения на отображение уведомлений.
-
Независимо от того, поддерживает ли браузер API
+ PushManager +
. Если любая из этих проверок не удалась, вызывается функция+ showNotAllowed +
и подписка прерывается.
Функция + showNotAllowed +
отображает сообщение на кнопке и отключает его, если пользователь не имеет права получать уведомления. Также отображаются соответствующие сообщения, если пользователь запретил приложению отображать уведомления или если браузер не поддерживает push-уведомления.
Как только мы убедимся, что пользователь имеет право получать push-уведомления, следующим шагом будет подписка на них с помощью + pushManager +
. Добавьте следующий код ниже функции + showNotAllowed +
:
~ / djangopush / статический / JS / registerSw.js
...
function urlB64ToUint8Array(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);
const outputData = outputArray.map((output, index) => rawData.charCodeAt(index));
return outputData;
}
const subscribe = async (reg) => {
const subscription = await reg.pushManager.getSubscription();
if (subscription) {
sendSubData(subscription);
return;
}
const vapidMeta = document.querySelector('meta[name="vapid-key"]');
const key = vapidMeta.content;
const options = {
userVisibleOnly: true,
// if key exists, create applicationServerKey property
...(key && {applicationServerKey: urlB64ToUint8Array(key)})
};
const sub = await reg.pushManager.subscribe(options);
sendSubData(sub)
};
Вызов функции + pushManager.getSubscription +
возвращает данные для активной подписки. Когда активная подписка существует, вызывается функция + sendSubData +
с информацией о подписке, передаваемой в качестве параметра.
Когда активной подписки не существует, открытый ключ VAPID, кодированный URL-адресом Base64, преобразуется в массив Uint8Array с помощью функции + urlB64ToUint8Array +
. + pushManager.subscribe +
затем вызывается с открытым ключом VAPID и значением + userVisible +
в качестве параметров. Вы можете узнать больше о доступных опциях here.
После успешной подписки пользователя следующим шагом будет отправка данных подписки на сервер. Данные будут отправлены в конечную точку + webpush / save_information +
, предоставленную пакетом + django-webpush +
. Добавьте следующий код ниже функции + subscribe +
:
~ / djangopush / статический / JS / registerSw.js
...
const sendSubData = async (subscription) => {
const browser = navigator.userAgent.match(/(firefox|msie|chrome|safari|trident)/ig)[0].toLowerCase();
const data = {
status_type: 'subscribe',
subscription: subscription.toJSON(),
browser: browser,
};
const res = await fetch('/webpush/save_information', {
method: 'POST',
body: JSON.stringify(data),
headers: {
'content-type': 'application/json'
},
credentials: "include"
});
handleResponse(res);
};
const handleResponse = (res) => {
console.log(res.status);
};
registerSw();
Конечной точке + save_information +
требуется информация о статусе подписки (+ subscribe +
и + unsubscribe +
), данных о подписке и браузере. Наконец, мы вызываем функцию + registerSw () +
, чтобы начать процесс подписки пользователя.
Готовый файл выглядит так:
~ / djangopush / статический / JS / registerSw.js
const registerSw = async () => {
if ('serviceWorker' in navigator) {
const reg = await navigator.serviceWorker.register('sw.js');
initialiseState(reg)
} else {
showNotAllowed("You can't send push notifications ☹️😢")
}
};
const initialiseState = (reg) => {
if (!reg.showNotification) {
showNotAllowed('Showing notifications isn\'t supported ☹️😢');
return
}
if (Notification.permission === 'denied') {
showNotAllowed('You prevented us from showing notifications ☹️🤔');
return
}
if (!'PushManager' in window) {
showNotAllowed("Push isn't allowed in your browser 🤔");
return
}
subscribe(reg);
}
const showNotAllowed = (message) => {
const button = document.querySelector('form>button');
button.innerHTML = `${message}`;
button.setAttribute('disabled', 'true');
};
function urlB64ToUint8Array(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);
const outputData = outputArray.map((output, index) => rawData.charCodeAt(index));
return outputData;
}
const subscribe = async (reg) => {
const subscription = await reg.pushManager.getSubscription();
if (subscription) {
sendSubData(subscription);
return;
}
const vapidMeta = document.querySelector('meta[name="vapid-key"]');
const key = vapidMeta.content;
const options = {
userVisibleOnly: true,
// if key exists, create applicationServerKey property
...(key && {applicationServerKey: urlB64ToUint8Array(key)})
};
const sub = await reg.pushManager.subscribe(options);
sendSubData(sub)
};
const sendSubData = async (subscription) => {
const browser = navigator.userAgent.match(/(firefox|msie|chrome|safari|trident)/ig)[0].toLowerCase();
const data = {
status_type: 'subscribe',
subscription: subscription.toJSON(),
browser: browser,
};
const res = await fetch('/webpush/save_information', {
method: 'POST',
body: JSON.stringify(data),
headers: {
'content-type': 'application/json'
},
credentials: "include"
});
handleResponse(res);
};
const handleResponse = (res) => {
console.log(res.status);
};
registerSw();
Затем добавьте тег + script
для файла` + register.js` в + home.html +
. Откройте файл:
nano ~/djangopush/templates/home.html
Добавьте тег + script
перед закрывающим тегом элемента` + body`:
~ / Djangopush / шаблоны / home.html
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
...
</head>
<body>
...
</body>
</html>
Поскольку работника службы еще не существует, если вы оставите приложение запущенным или попытаетесь запустить его снова, вы увидите сообщение об ошибке. Давайте исправим это, создав сервисного работника.
Шаг 8 - Создание сервисного работника
Чтобы отобразить push-уведомление, вам понадобится активный сервисный работник, установленный на домашней странице вашего приложения. Мы создадим работника службы, который будет прослушивать события + push +
и отображать сообщения, когда будет готов.
Поскольку мы хотим, чтобы область обслуживания работника составляла весь домен, нам нужно будет установить его в корне приложения. Вы можете прочитать больше о процессе в этой статье, описывающей how для регистрации работника службы. Наш подход заключается в создании файла + sw.js
в папке` + templates`, который мы затем зарегистрируем как представление.
Создайте файл:
nano ~/djangopush/templates/sw.js
Добавьте следующий код, который сообщает работнику службы прослушивания событий push:
~ / djangopush / шаблоны / sw.js
// Register event listener for the 'push' event.
self.addEventListener('push', function (event) {
// Retrieve the textual payload from event.data (a PushMessageData object).
// Other formats are supported (ArrayBuffer, Blob, JSON), check out the documentation
// on https://developer.mozilla.org/en-US/docs/Web/API/PushMessageData.
const eventInfo = event.data.text();
const data = JSON.parse(eventInfo);
const head = data.head || 'New Notification 🕺🕺';
const body = data.body || 'This is default content. Your notification didn\'t have one 🙄🙄';
// Keep the service worker alive until the notification is created.
event.waitUntil(
self.registration.showNotification(head, {
body: body,
icon: 'https://i.imgur.com/MZM3K5w.png'
})
);
});
Сервисный работник слушает событие push. В функции обратного вызова данные + event +
преобразуются в текст. Мы используем строки + title +
и + body +
по умолчанию, если в данных события их нет. Функция + showNotification +
получает заголовок уведомления, заголовок отображаемого уведомления и options Объект в качестве параметров. Объект параметров содержит несколько свойств для настройки визуальных параметров уведомления.
Чтобы работники службы работали для всего домена, вам необходимо установить его в корне приложения. Мы будем использовать https://docs.djangoproject.com/en/2.1/topics/class-based-views/ [+ TemplateView +
], чтобы предоставить работнику сервиса доступ ко всему домену.
Откройте файл + urls.py +
:
nano ~/djangopush/djangopush/urls.py
Добавьте новый оператор импорта и путь в список + urlpatterns +
, чтобы создать представление на основе классов:
~ / Djangopush / djangopush / urls.py
...
urlpatterns = [
...
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
Представления на основе классов, такие как + TemplateView +
, позволяют создавать гибкие многоразовые представления. В этом случае метод + TemplateView.as_view +
создает путь для работника службы, передавая недавно созданного работника службы в качестве шаблона и + application / x-javascript +
в качестве + content_type +
шаблона.
Вы создали сервисного работника и зарегистрировали его как маршрут. Далее вы настроите форму на главной странице для отправки push-уведомлений.
Шаг 9 - Отправка push-уведомлений
Используя форму на домашней странице, пользователи должны иметь возможность отправлять push-уведомления во время работы вашего сервера. Вы также можете отправлять push-уведомления, используя любой сервис RESTful, например Postman. Когда пользователь отправляет push-уведомления из формы на домашней странице, данные будут содержать + head +
и + body +
, а также + id +
принимающего пользователя. Данные должны быть структурированы следующим образом:
{
head: "Title of the notification",
body: "Notification body",
id: "User's id"
}
Чтобы прослушать событие + submit +
формы и отправить данные, введенные пользователем на сервер, мы создадим файл с именем + site.js +
в каталоге + ~ / djangopush / static / js +
.
Откройте файл:
nano ~/djangopush/static/js/site.js
Во-первых, добавьте слушатель события + submit +
в форму, который позволит вам получить значения входных данных формы и идентификатор пользователя, хранящийся в теге + meta +
вашего шаблона:
~ / djangopush / статический / JS / site.js
const pushForm = document.getElementById('send-push__form');
const errorMsg = document.querySelector('.error');
pushForm.addEventListener('submit', async function (e) {
e.preventDefault();
const input = this[0];
const textarea = this[1];
const button = this[2];
errorMsg.innerText = '';
const head = input.value;
const body = textarea.value;
const meta = document.querySelector('meta[name="user_id"]');
const id = meta ? meta.content : null;
...
// TODO: make an AJAX request to send notification
});
Функция + pushForm +
возвращает внутри формы + input
,` + textarea` и + button
. Он также получает информацию из тега + meta +
, включая атрибут name + user_id +
и идентификатор пользователя, хранящийся в атрибуте + content +
тега. С помощью этой информации он может отправить POST-запрос конечной точке + / send_push +
на сервере.
Для отправки запросов на сервер мы будем использовать собственный API-интерфейс Fetch. Мы используем Fetch здесь, потому что он поддерживается большинством браузеров и не требует внешних библиотек для работы. Ниже добавленного кода обновите функцию + pushForm +
, включив в нее код для отправки запросов AJAX:
~ / djangopush / статический / JS / site.js
const pushForm = document.getElementById('send-push__form');
const errorMsg = document.querySelector('.error');
pushForm.addEventListener('submit', async function (e) {
...
const id = meta ? meta.content : null;
if (head && body && id) {
button.innerText = 'Sending...';
button.disabled = true;
const res = await fetch('/send_push', {
method: 'POST',
body: JSON.stringify({head, body, id}),
headers: {
'content-type': 'application/json'
}
});
if (res.status === 200) {
button.innerText = 'Send another 😃!';
button.disabled = false;
input.value = '';
textarea.value = '';
} else {
errorMsg.innerText = res.message;
button.innerText = 'Something broke 😢.. Try again?';
button.disabled = false;
}
}
else {
let error;
if (!head || !body){
error = 'Please ensure you complete the form 🙏🏾'
}
else if (!id){
error = "Are you sure you're logged in? 🤔. Make sure! 👍🏼"
}
errorMsg.innerText = error;
}
});
Если присутствуют три обязательных параметра + head +
, + body +
и + id +
, мы отправляем запрос и временно отключаем кнопку отправки.
Готовый файл выглядит так:
~ / djangopush / статический / JS / site.js
const pushForm = document.getElementById('send-push__form');
const errorMsg = document.querySelector('.error');
pushForm.addEventListener('submit', async function (e) {
e.preventDefault();
const input = this[0];
const textarea = this[1];
const button = this[2];
errorMsg.innerText = '';
const head = input.value;
const body = textarea.value;
const meta = document.querySelector('meta[name="user_id"]');
const id = meta ? meta.content : null;
if (head && body && id) {
button.innerText = 'Sending...';
button.disabled = true;
const res = await fetch('/send_push', {
method: 'POST',
body: JSON.stringify({head, body, id}),
headers: {
'content-type': 'application/json'
}
});
if (res.status === 200) {
button.innerText = 'Send another 😃!';
button.disabled = false;
input.value = '';
textarea.value = '';
} else {
errorMsg.innerText = res.message;
button.innerText = 'Something broke 😢.. Try again?';
button.disabled = false;
}
}
else {
let error;
if (!head || !body){
error = 'Please ensure you complete the form 🙏🏾'
}
else if (!id){
error = "Are you sure you're logged in? 🤔. Make sure! 👍🏼"
}
errorMsg.innerText = error;
}
});
Наконец, добавьте файл + site.js
в` + home.html`:
nano ~/djangopush/templates/home.html
Добавьте тег + script
:
~ / Djangopush / шаблоны / home.html
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
...
</head>
<body>
...
</body>
</html>
В этот момент, если вы оставите ваше приложение работающим или попытаетесь запустить его снова, вы увидите ошибку, так как работники службы могут работать только в защищенных доменах или на + localhost +. На следующем шаге мы будем использовать ngrok для создания безопасного туннеля к нашему веб-серверу.
Шаг 10 - Создание безопасного туннеля для тестирования приложения
Работникам сервисов требуются безопасные соединения для работы на любом сайте, кроме + localhost +
, поскольку они могут разрешать захват соединений, а также фильтрацию и изготовление ответов. По этой причине мы создадим безопасный туннель для нашего сервера с https://ngrok.com [ngrok].
Откройте второе окно терминала и убедитесь, что вы находитесь в своем домашнем каталоге:
cd ~
Если вы начали с чистого сервера 18.04 в предварительных условиях, то вам нужно будет установить + unzip +
:
sudo apt update && sudo apt install unzip
Скачать нгрок:
wget https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip
unzip ngrok-stable-linux-amd64.zip
Переместите + ngrok +
в + / usr / local / bin +
, чтобы у вас был доступ к команде + ngrok +
из терминала:
sudo mv ngrok /usr/local/bin
В первом окне терминала убедитесь, что вы находитесь в каталоге вашего проекта и запустите свой сервер:
cd ~/djangopush
python manage.py runserver :8000
Вам нужно будет сделать это перед созданием безопасного туннеля для вашего приложения.
Во втором окне терминала перейдите в папку вашего проекта и активируйте виртуальную среду:
cd ~/djangopush
source my_env/bin/activate
Создайте безопасный туннель для вашего приложения:
ngrok http :8000
Вы увидите следующий вывод, который включает информацию о вашем безопасном URL-адресе ngrok:
Outputngrok by @inconshreveable (Ctrl+C to quit)
Session Status online
Session Expires 7 hours, 59 minutes
Version 2.2.8
Region United States (us)
Web Interface http://127.0.0.1:4040
Forwarding http:// -> 203.0.113.0:8000
Forwarding https:// -> 203.0.113.0:8000
Connections ttl opn rt1 rt5 p50 p90
0 0 0.00 0.00 0.00 0.00
Скопируйте ++
из вывода консоли. Вам нужно будет добавить его в список + ALLOWED_HOSTS
в вашем файле` + settings.py`.
Откройте другое окно терминала, перейдите в папку вашего проекта и активируйте виртуальную среду:
cd ~/djangopush
source my_env/bin/activate
Откройте файл + settings.py +
:
nano ~/djangopush/djangopush/settings.py
Обновите список + ALLOWED_HOSTS + с помощью защищенного туннеля ngrok:
~ / Djangopush / djangopush / settings.py
...
ALLOWED_HOSTS = ['your_server_ip', '']
...
Перейдите на защищенную страницу администратора, чтобы войти: + https: /// admin / +
. Вы увидите экран, который выглядит так:
изображение: https: //assets.digitalocean.com/articles/django_push_18_04/ngrok_login.png [ngrok admin login]
Введите информацию о вашем администраторе Django на этом экране. Это должна быть та же информация, которую вы ввели при входе в интерфейс администратора в https://www.digitalocean.com/community/tutorials/how-to-install-the-django-web-framework-on-ubuntu-18 -04 # test-the-development-server [обязательные шаги]. Теперь вы готовы отправлять push-уведомления.
Посетите + https: // +
в вашем браузере. Вы увидите приглашение с просьбой разрешить отображение уведомлений. Нажмите кнопку * Разрешить *, чтобы позволить вашему браузеру отображать push-уведомления:
изображение: https: //assets.digitalocean.com/articles/django_push_18_04/allow_push_two.png [запрос push-уведомлений]
Отправка заполненной формы будет отображать уведомление, подобное этому:
изображение: https: //assets.digitalocean.com/articles/django_push_18_04/web_push_final.png [скриншот уведомления]
Если вы получили уведомления, значит ваше приложение работает должным образом.
Вы создали веб-приложение, которое запускает push-уведомления на сервере и с помощью сервисных работников получает и отображает уведомления. Вы также прошли этапы получения ключей VAPID, необходимых для отправки push-уведомлений с сервера приложений.
Заключение
В этом руководстве вы узнали, как подписывать пользователей на push-уведомления, устанавливать сервисных работников и отображать push-уведомления с помощью API уведомлений.
Вы можете пойти еще дальше, настроив уведомления, чтобы открывать определенные области вашего приложения при нажатии. Исходный код этого руководства можно найти по адресу here.