DjangoアプリケーションからWebプッシュ通知を送信する方法
著者は、 Open Internet / Free Speech Fund を選択して、 Write forDOnationsプログラムの一環として寄付を受け取りました。
序章
Webは絶えず進化しており、以前はネイティブモバイルデバイスでしか利用できなかった機能を実現できるようになりました。 JavaScript サービスワーカーの導入により、バックグラウンド同期、オフラインキャッシュ、プッシュ通知の送信などを行うための新しい機能がWebに提供されました。
プッシュ通知を使用すると、ユーザーはオプトインしてモバイルおよびWebアプリケーションの更新を受信できます。 また、ユーザーは、カスタマイズされた関連コンテンツを使用して、既存のアプリケーションを再利用できます。
このチュートリアルでは、Ubuntu 18.04でDjangoアプリケーションをセットアップします。このアプリケーションは、ユーザーがアプリケーションにアクセスする必要があるアクティビティがあるたびにプッシュ通知を送信します。 これらの通知を作成するには、 Django-Webpush パッケージを使用し、サービスワーカーを設定して登録し、クライアントに通知を表示します。 通知付きの動作中のアプリケーションは次のようになります。
前提条件
このガイドを開始する前に、次のものが必要です。
- 非rootユーザーとアクティブなファイアウォールを備えた1つのUbuntu18.04サーバー。 Ubuntu 18.04サーバーの作成方法の詳細については、この初期サーバーセットアップガイドのガイドラインに従うことができます。
pip
およびvenv
は、これらのガイドラインに従ってインストールされます。- ホームディレクトリに作成された
djangopush
というDjangoプロジェクトは、 Ubuntu18.04でサンプルのDjangoプロジェクトを作成する際のガイドラインに従って設定します。 サーバーのIPアドレスをsettings.py
ファイルのALLOWED_HOSTSディレクティブに必ず追加してください。
ステップ1—Django-WebpushのインストールとVapidキーの取得
Django-Webpushは、開発者がWebプッシュ通知をDjangoアプリケーションに統合して送信できるようにするパッケージです。 このパッケージを使用して、アプリケーションからプッシュ通知をトリガーして送信します。 このステップでは、Django-Webpushをインストールし、サーバーを識別して各リクエストの一意性を確保するために必要な任意のアプリケーションサーバー識別(VAPID)キーを取得します。
前提条件で作成した~/djangopush
プロジェクトディレクトリにいることを確認してください。
cd ~/djangopush
仮想環境をアクティブ化します。
source my_env/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 = [ ..., 'webpush', ] ...
ファイルを保存して、エディターを終了します。
アプリケーションでmigrationsを実行して、データベーススキーマに加えた変更を適用します。
python manage.py migrate
出力は次のようになり、移行が成功したことを示します。
OutputOperations to perform: Apply all migrations: admin, auth, contenttypes, sessions, webpush Running migrations: Applying webpush.0001_initial... OK
Webプッシュ通知を設定する次のステップは、VAPIDキーを取得することです。 これらのキーはアプリケーションサーバーを識別し、サブスクリプションを特定のサーバーに制限するため、プッシュサブスクリプションURLの機密性を減らすために使用できます。
VAPIDキーを取得するには、 wep-push-codelabWebアプリケーションに移動します。 ここでは、自動的に生成されたキーが提供されます。 秘密鍵と公開鍵をコピーします。
次に、settings.py
にVAPID情報の新しいエントリを作成します。 まず、ファイルを開きます。
nano ~/djangopush/djangopush/settings.py
次に、WEBPUSH_SETTINGS
という新しいディレクティブを、VAPIDの公開鍵と秘密鍵、およびAUTH_PASSWORD_VALIDATORS
の下にある電子メールとともに追加します。
〜/ djangopush / djangopush / settings.py
... AUTH_PASSWORD_VALIDATORS = [ ... ] WEBPUSH_SETTINGS = { "VAPID_PUBLIC_KEY": "your_vapid_public_key", "VAPID_PRIVATE_KEY": "your_vapid_private_key", "VAPID_ADMIN_EMAIL": "[email protected]" } # Internationalization # https://docs.djangoproject.com/en/2.0/topics/i18n/ ...
プレースホルダー値your_vapid_public_key
、your_vapid_private_key
、および[email protected]
を独自の情報に置き換えることを忘れないでください。 あなたのメールアドレスは、プッシュサーバーで問題が発生した場合に通知される方法です。
次に、アプリケーションのホームページを表示し、サブスクライブしたユーザーへのプッシュ通知をトリガーするビューを設定します。
ステップ2—ビューを設定する
このステップでは、基本的なhome
ビューを、send_push
ビューとともにホームページのHttpResponse応答オブジェクトとともにセットアップします。 ビューは、Webリクエストから応答オブジェクトを返す関数です。 send_push
ビューは、Django-Webpushライブラリを使用して、ユーザーがホームページに入力したデータを含むプッシュ通知を送信します。
~/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
ビューで、ユーザーがプッシュ通知を送信できるホームページが表示されます。 次のコードをファイルに追加します。
〜/ 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
で、django-webpush
パッケージを使用して送信されたプッシュ通知を処理します。 POSTリクエストのみに制限され、クロスサイトリクエストフォージェリ(CSRF)保護が免除されます。 これを行うと、Postmanまたはその他のRESTfulサービスを使用してビューをテストできます。 ただし、本番環境では、ビューがCSRFに対して脆弱なままにならないように、このデコレータを削除する必要があります。
send_push
ビューを作成するには、最初に次のインポートを追加してJSON応答を有効にし、webpush
ライブラリのsend_user_notification
関数にアクセスします。
〜/ djangopush / djangopush / views.py
from django.http.response import JsonResponse, HttpResponse from django.views.decorators.http import require_GET, require_POST from django.shortcuts import get_object_or_404 from django.contrib.auth.models import User from django.views.decorators.csrf import csrf_exempt from webpush import send_user_notification import json
次に、require_POST
デコレータを追加します。これは、ユーザーから送信されたリクエスト本文を使用して、プッシュ通知を作成およびトリガーします。
〜/ 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
ビューには2つのデコレータを使用しています。ビューをPOSTリクエストのみに制限するrequire_POST
デコレータと、ビューをCSRF保護から除外するcsrf_exempt
デコレータです。 。
このビューはPOSTデータを想定し、次のことを行います。リクエストのbody
を取得し、 json パッケージを使用して、json.loadsを使用してJSONドキュメントをPythonオブジェクトに逆シリアル化します。 。 json.loads
は、構造化されたJSONドキュメントを取得し、それをPythonオブジェクトに変換します。
ビューは、リクエスト本文オブジェクトに次の3つのプロパティがあることを想定しています。
head
:プッシュ通知のタイトル。body
:通知の本文。id
:リクエストユーザーのid
。
必要なプロパティのいずれかが欠落している場合、ビューは404「見つかりません」ステータスのJSONResponse
を返します。 指定された主キーを持つユーザーが存在する場合、ビューはdjango.shortcuts
ライブラリのget_object_or_404関数を使用して、一致する主キーを持つuser
を返します。 ユーザーが存在しない場合、関数は404エラーを返します。
このビューでは、webpush
ライブラリのsend_user_notification
関数も使用されます。 この関数は3つのパラメーターを取ります。
User
:プッシュ通知の受信者。payload
:通知head
およびbody
を含む通知情報。ttl
:ユーザーがオフラインの場合に通知を保存する最大時間(秒単位)。
エラーが発生しない場合、ビューは200の「成功」ステータスとデータオブジェクトを持つJSONResponse
を返します。 KeyError
が発生した場合、ビューは500の「内部サーバーエラー」ステータスを返します。 KeyError
は、オブジェクトの要求されたキーが存在しない場合に発生します。
次のステップでは、作成したビューに一致する対応するURLルートを作成します。
ステップ3—URLをビューにマッピングする
Djangoを使用すると、URLconf
と呼ばれるPythonモジュールを使用してビューに接続するURLを作成できます。 このモジュールは、URLパス式をPython関数(ビュー)にマップします。 通常、URL構成ファイルは、プロジェクトの作成時に自動生成されます。 このステップでは、このファイルを更新して、前のステップで作成したビューの新しいルートと、django-webpush
アプリのURLを含めます。これにより、ユーザーがプッシュ通知をサブスクライブするためのエンドポイントが提供されます。
ビューの詳細については、Djangoビューの作成方法を参照してください。
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, include
次に、前の手順で作成したビューをインポートし、urlpatterns
リストを更新してビューをマップします。
〜/ djangopush / djangopush / urls.py
"""webpushdjango URL Configuration ... """ from django.contrib import admin from django.urls import path, include from .views import home, send_push urlpatterns = [ path('admin/', admin.site.urls), path('', home), path('send_push', send_push), path('webpush/', include('webpush.urls')), ]
ここで、urlpatterns
リストは、django-webpush
パッケージのURLを登録し、ビューをURL/send_push
および/home
にマップします。
/home
ビューをテストして、意図したとおりに機能していることを確認しましょう。 プロジェクトのルートディレクトリにいることを確認してください。
cd ~/djangopush
次のコマンドを実行してサーバーを起動します。
python manage.py runserver your_server_ip:8000
http://your_server_ip:8000
に移動します。 次のホームページが表示されます。
この時点で、CTRL+C
を使用してサーバーを強制終了できます。次に、render
関数を使用して、テンプレートの作成とビューでのレンダリングに進みます。
ステップ4—テンプレートの作成
Djangoのテンプレートエンジンを使用すると、HTMLファイルに似たテンプレートを使用してアプリケーションのユーザー向けレイヤーを定義できます。 このステップでは、home
ビューのテンプレートを作成してレンダリングします。
プロジェクトのルートディレクトリにtemplates
というフォルダを作成します。
mkdir ~/djangopush/templates
この時点でプロジェクトのルートフォルダでls
を実行すると、出力は次のようになります。
Output/djangopush /templates db.sqlite3 manage.py /my_env
templates
フォルダーにhome.html
というファイルを作成します。
nano ~/djangopush/templates/home.html
次のコードをファイルに追加して、ユーザーが情報を入力してプッシュ通知を作成できるフォームを作成します。
{% 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
には、2つのフィールドを持つフォームが含まれています。input
要素は通知の先頭/タイトルを保持し、textarea
要素は通知本文を保持します。
ファイルのhead
セクションには、VAPID公開鍵とユーザーのIDを保持する2つのmeta
タグがあります。 これらの2つの変数は、ユーザーを登録してプッシュ通知を送信するために必要です。 サーバーにAJAXリクエストを送信し、id
を使用してユーザーを識別するため、ここではユーザーのIDが必要です。 現在のユーザーが登録ユーザーの場合、テンプレートはid
をコンテンツとしてmeta
タグを作成します。
次のステップは、テンプレートの場所をDjangoに指示することです。 これを行うには、settings.py
を編集し、TEMPLATES
リストを更新します。
settings.py
ファイルを開きます。
nano ~/djangopush/djangopush/settings.py
DIRS
リストに以下を追加して、テンプレートディレクトリへのパスを指定します。
〜/ djangopush / djangopush / settings.py
... TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates')], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ ... ], }, }, ] ...
次に、views.py
ファイルで、home
ビューを更新して、home.html
テンプレートをレンダリングします。 ファイルを開きます。
nano ~/djangpush/djangopush/views.py
まず、settings.py
ファイルのプロジェクトのすべての設定を含むsettings
構成や、django.shortcuts
のrender
関数など、いくつかのインポートを追加します。 :
〜/ djangopush / djangopush / views.py
... from django.shortcuts import render, get_object_or_404 ... import json from django.conf import settings ...
次に、home
ビューに追加した初期コードを削除し、作成したテンプレートのレンダリング方法を指定する次のコードを追加します。
〜/ djangopush / djangopush / views.py
... @require_GET def home(request): webpush_settings = getattr(settings, 'WEBPUSH_SETTINGS', {}) vapid_key = webpush_settings.get('VAPID_PUBLIC_KEY') user = request.user return render(request, 'home.html', {user: user, 'vapid_key': vapid_key})
このコードは、次の変数を割り当てます。
webpush_settings
:これには、settings
構成からWEBPUSH_SETTINGS
属性の値が割り当てられます。vapid_key
:これは、webpush_settings
オブジェクトからVAPID_PUBLIC_KEY
値を取得して、クライアントに送信します。 この公開鍵は秘密鍵と照合され、公開鍵を持つクライアントがサーバーからのプッシュメッセージの受信を許可されていることを確認します。user
:この変数は着信要求から取得されます。 ユーザーがサーバーにリクエストを送信するたびに、そのユーザーの詳細がuser
フィールドに保存されます。
レンダリング関数は、HTMLファイルと、現在のユーザーとサーバーのvapid公開鍵を含むコンテキストオブジェクトを返します。 ここでは、request
、レンダリングされるtemplate
、およびテンプレートで使用される変数を含むオブジェクトの3つのパラメーターを取ります。
テンプレートを作成し、home
ビューを更新したら、静的ファイルを提供するようにDjangoを構成することに進むことができます。
ステップ5—静的ファイルの提供
Webアプリケーションには、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/'
次に、Djangoが静的ファイルを検索するSTATICFILES_DIRS
というディレクトリのリストを追加します。
〜/ djangopush / djangopush / settings.py
... STATIC_URL = '/static/' STATICFILES_DIRS = [ os.path.join(BASE_DIR, "static"), ]
これで、urls.py
ファイルで定義されたパスのリストにSTATIC_URL
を追加できます。
ファイルを開きます。
nano ~/djangopush/djangopush/urls.py
次のコードを追加します。これにより、static
のURL構成がインポートされ、urlpatterns
リストが更新されます。 ここでのヘルパー関数は、settings.py
ファイルで提供したSTATIC_URL
およびSTATIC_ROOT
プロパティを使用して、プロジェクトの静的ファイルを提供します。
〜/ djangopush / djangopush / urls.py
... from django.conf import settings from django.conf.urls.static import static urlpatterns = [ ... ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
静的ファイル設定を構成したら、アプリケーションのホームページのスタイル設定に進むことができます。
ステップ6—ホームページのスタイリング
静的ファイルを提供するようにアプリケーションを設定した後、外部スタイルシートを作成し、それをhome.html
ファイルにリンクして、ホームページのスタイルを設定できます。 すべての静的ファイルは、プロジェクトのルートフォルダーのstatic
ディレクトリに保存されます。
static
フォルダー内にstatic
フォルダーとcss
フォルダーを作成します。
mkdir -p ~/djangopush/static/css
css
フォルダー内のstyles.css
という名前のcssファイルを開きます。
nano ~/djangopush/static/css/styles.css
ホームページに次のスタイルを追加します。
〜/ djangopush / static / 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
ファイルにリンクできます。 home.html
ファイルを開きます。
nano ~/djangopush/templates/home.html
head
セクションを更新して、外部スタイルシートへのリンクを含めます。
〜/ djangopush / templates / home.html
{% load static %} <!DOCTYPE html> <html lang="en"> <head> ... <link href="{% static '/css/styles.css' %}" rel="stylesheet"> </head> <body> ... </body> </html>
メインプロジェクトディレクトリにいることを確認し、サーバーを再起動して作業を検査します。
cd ~/djangopush python manage.py runserver your_server_ip:8000
http://your_server_ip:8000
にアクセスすると、次のようになります。
ここでも、CTRL+C
を使用してサーバーを強制終了できます。
home.html
ページの作成とスタイル設定が正常に完了したので、ユーザーがホームページにアクセスするたびに通知をプッシュするようにユーザーをサブスクライブできます。
ステップ7—サービスワーカーの登録とプッシュ通知へのユーザーのサブスクライブ
Webプッシュ通知は、サブスクライブしているアプリケーションに更新がある場合にユーザーに通知したり、過去に使用したアプリケーションに再度アクセスするようにユーザーに促したりできます。 これらは、 pushAPIとnotificationsAPIの2つのテクノロジーに依存しています。 どちらのテクノロジーも、サービスワーカーの存在に依存しています。
サーバーがServiceWorkerに情報を提供し、ServiceWorkerが通知APIを使用してこの情報を表示するとプッシュが呼び出されます。
ユーザーをプッシュにサブスクライブしてから、サブスクリプションからサーバーに情報を送信してユーザーを登録します。
static
ディレクトリに、js
というフォルダを作成します。
mkdir ~/djangopush/static/js
registerSw.js
というファイルを作成します。
nano ~/djangopush/static/js/registerSw.js
次のコードを追加します。このコードは、サービスワーカーの登録を試みる前に、ユーザーのブラウザーでサービスワーカーがサポートされているかどうかを確認します。
〜/ djangopush / static / 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
関数の下に次のコードを追加して、ユーザーがプッシュ通知をサブスクライブする前に受信できるかどうかを確認します。
〜/ djangopush / static / 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
の値を使用して、ユーザーが通知を有効にしているかどうか。- ユーザーが通知を表示するためのアプリケーション権限を付与したかどうか。
- ブラウザが
PushManager
APIをサポートしているかどうか。 これらのチェックのいずれかが失敗した場合、showNotAllowed
関数が呼び出され、サブスクリプションが中止されます。
showNotAllowed
機能は、ボタンにメッセージを表示し、ユーザーが通知を受信できない場合はメッセージを無効にします。 また、ユーザーがアプリケーションによる通知の表示を制限している場合、またはブラウザーがプッシュ通知をサポートしていない場合にも、適切なメッセージを表示します。
ユーザーがプッシュ通知を受信できることを確認したら、次のステップはpushManager
を使用してそれらをサブスクライブすることです。 showNotAllowed
関数の下に次のコードを追加します。
〜/ djangopush / static / 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
関数は、パラメーターとして渡されたサブスクリプション情報を使用して呼び出されます。
アクティブなサブスクリプションが存在しない場合、Base64 URLセーフでエンコードされたVAPID公開鍵は、urlB64ToUint8Array
関数を使用してUint8Arrayに変換されます。 次に、pushManager.subscribe
は、VAPID公開鍵とuserVisible
値をオプションとして使用して呼び出されます。 利用可能なオプションの詳細については、こちらをご覧ください。
ユーザーを正常にサブスクライブした後、次のステップはサブスクリプションデータをサーバーに送信することです。 データは、django-webpush
パッケージによって提供されるwebpush/save_information
エンドポイントに送信されます。 subscribe
関数の下に次のコードを追加します。
〜/ djangopush / static / 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 / static / 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();
次に、home.html
のregisterSw.js
ファイルにscript
タグを追加します。 ファイルを開きます。
nano ~/djangopush/templates/home.html
body
要素の終了タグの前にscript
タグを追加します。
〜/ djangopush / templates / home.html
{% load static %} <!DOCTYPE html> <html lang="en"> <head> ... </head> <body> ... <script src="{% static '/js/registerSw.js' %}"></script> </body> </html>
Service Workerはまだ存在しないため、アプリケーションを実行したままにするか、アプリケーションを再起動しようとすると、エラーメッセージが表示されます。 サービスワーカーを作成してこれを修正しましょう。
ステップ8—サービスワーカーの作成
プッシュ通知を表示するには、アプリケーションのホームページにアクティブなServiceWorkerがインストールされている必要があります。 push
イベントをリッスンし、準備ができたらメッセージを表示するServiceWorkerを作成します。
Service Workerのスコープをドメイン全体にする必要があるため、アプリケーションのルートにインストールする必要があります。 このプロセスの詳細については、サービスワーカーの登録方法の概要を説明しています。 私たちのアプローチは、templates
フォルダーにsw.js
ファイルを作成し、それをビューとして登録することです。
ファイルを作成します。
nano ~/djangopush/templates/sw.js
次のコードを追加します。これは、サービスワーカーにプッシュイベントをリッスンするように指示します。
〜/ djangopush / templates / 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' }) ); });
サービスワーカーはプッシュイベントをリッスンします。 コールバック関数では、event
データがテキストに変換されます。 イベントデータに含まれていない場合は、デフォルトのtitle
およびbody
文字列を使用します。 showNotification
関数は、通知のタイトル、表示される通知のヘッダー、およびoptionsオブジェクトをパラメーターとして受け取ります。 optionsオブジェクトには、通知の視覚的オプションを構成するためのいくつかのプロパティが含まれています。
Service Workerがドメイン全体で機能するには、アプリケーションのルートにサービスワーカーをインストールする必要があります。 TemplateView を使用して、ServiceWorkerがドメイン全体にアクセスできるようにします。
urls.py
ファイルを開きます。
nano ~/djangopush/djangopush/urls.py
urlpatterns
リストに新しいインポートステートメントとパスを追加して、クラスベースのビューを作成します。
〜/ djangopush / djangopush / urls.py
... from django.views.generic import TemplateView urlpatterns = [ ..., path('sw.js', TemplateView.as_view(template_name='sw.js', content_type='application/x-javascript')) ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
TemplateView
のようなクラスベースのビューを使用すると、柔軟で再利用可能なビューを作成できます。 この場合、TemplateView.as_view
メソッドは、最近作成されたサービスワーカーをテンプレートとして渡し、application/x-javascript
をテンプレートのcontent_type
として渡すことにより、サービスワーカーのパスを作成します。
これで、サービスワーカーが作成され、ルートとして登録されました。 次に、プッシュ通知を送信するようにホームページにフォームを設定します。
ステップ9—プッシュ通知の送信
ホームページのフォームを使用して、ユーザーはサーバーの実行中にプッシュ通知を送信できる必要があります。 PostmanなどのRESTfulサービスを使用してプッシュ通知を送信することもできます。 ユーザーがホームページのフォームからプッシュ通知を送信すると、データにはhead
とbody
、および受信ユーザーのid
が含まれます。 データは次のように構成する必要があります。
{ head: "Title of the notification", body: "Notification body", id: "User's id" }
フォームのsubmit
イベントをリッスンし、ユーザーが入力したデータをサーバーに送信するために、~/djangopush/static/js
ディレクトリにsite.js
というファイルを作成します。
ファイルを開きます。
nano ~/djangopush/static/js/site.js
まず、submit
イベントリスナーをフォームに追加します。これにより、フォーム入力の値と、テンプレートのmeta
タグに格納されているユーザーIDを取得できます。
〜/ djangopush / static / 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
タグから、名前属性user_id
や、タグのcontent
属性に格納されているユーザーのIDなどの情報を取得します。 この情報を使用して、サーバー上の/send_push
エンドポイントにPOST要求を送信できます。
サーバーにリクエストを送信するには、ネイティブの FetchAPIを使用します。 ここではFetchを使用しています。これは、ほとんどのブラウザーでサポートされており、機能するために外部ライブラリを必要としないためです。 追加したコードの下で、pushForm
関数を更新して、AJAXリクエストを送信するためのコードを含めます。
〜/ djangopush / static / 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; } });
3つの必須パラメーターhead
、body
、およびid
が存在する場合、リクエストを送信し、送信ボタンを一時的に無効にします。
完成したファイルは次のようになります。
〜/ djangopush / static / 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 / templates / home.html
{% load static %} <!DOCTYPE html> <html lang="en"> <head> ... </head> <body> ... <script src="{% static '/js/site.js' %}"></script> </body> </html>
この時点で、アプリケーションを実行したままにするか、アプリケーションを再起動しようとすると、サービスワーカーはセキュアドメインまたはlocalhost
でのみ機能できるため、エラーが表示されます。 次のステップでは、 ngrok を使用して、Webサーバーへの安全なトンネルを作成します。
ステップ10—アプリケーションをテストするための安全なトンネルを作成する
サービスワーカーは、localhost
を除くすべてのサイトで機能するために安全な接続を必要とします。これは、接続が乗っ取られ、応答がフィルタリングおよび作成される可能性があるためです。 このため、ngrokを使用してサーバー用の安全なトンネルを作成します。
2番目のターミナルウィンドウを開き、ホームディレクトリにいることを確認します。
cd ~
前提条件でクリーンな18.04サーバーを使用して開始した場合は、unzip
をインストールする必要があります。
sudo apt update && sudo apt install unzip
ngrokをダウンロード:
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 your_server_ip:8000
アプリケーションの安全なトンネルを作成する前に、これを行う必要があります。
2番目のターミナルウィンドウで、プロジェクトフォルダーに移動し、仮想環境をアクティブ化します。
cd ~/djangopush source my_env/bin/activate
アプリケーションへの安全なトンネルを作成します。
ngrok http your_server_ip:8000
次の出力が表示されます。これには、安全なngrokURLに関する情報が含まれています。
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://ngrok_secure_url -> 203.0.113.0:8000 Forwarding https://ngrok_secure_url -> 203.0.113.0:8000 Connections ttl opn rt1 rt5 p50 p90 0 0 0.00 0.00 0.00 0.00
コンソール出力からngrok_secure_url
をコピーします。 settings.py
ファイルのALLOWED_HOSTS
のリストに追加する必要があります。
別のターミナルウィンドウを開き、プロジェクトフォルダーに移動して、仮想環境をアクティブ化します。
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', 'ngrok_secure_url'] ...
安全な管理ページに移動してログインします:https://ngrok_secure_url/admin/
。 次のような画面が表示されます。
この画面にDjango管理者ユーザー情報を入力します。 これは、前提条件の手順で管理インターフェースにログインしたときに入力した情報と同じである必要があります。 これで、プッシュ通知を送信する準備が整いました。
ブラウザでhttps://ngrok_secure_url
にアクセスします。 通知を表示する許可を求めるプロンプトが表示されます。 許可ボタンをクリックして、ブラウザにプッシュ通知を表示させます。
記入済みのフォームを送信すると、次のような通知が表示されます。
注:通知を送信する前に、サーバーが実行されていることを確認してください。
通知を受け取った場合、アプリケーションは期待どおりに機能しています。
サーバー上でプッシュ通知をトリガーし、サービスワーカーの助けを借りて、通知を受信して表示するWebアプリケーションを作成しました。 また、アプリケーションサーバーからプッシュ通知を送信するために必要なVAPIDキーを取得する手順も実行しました。
結論
このチュートリアルでは、通知APIを使用して、ユーザーをサブスクライブしてプッシュ通知をサブスクライブし、サービスワーカーをインストールし、プッシュ通知を表示する方法を学習しました。
クリックしたときにアプリケーションの特定の領域を開くように通知を構成することで、さらに先に進むことができます。 このチュートリアルのソースコードはここにあります。