序章

Webは絶えず進化しており、以前はネイティブモバイルデバイスでしか利用できなかった機能を実現できるようになりました。 JavaScript サービスワーカーの導入により、バックグラウンド同期、オフラインキャッシュ、プッシュ通知の送信などを行うための新しい機能がWebに提供されました。

プッシュ通知を使用すると、ユーザーはオプトインしてモバイルおよびWebアプリケーションの更新を受信できます。 また、ユーザーは、カスタマイズされた関連コンテンツを使用して、既存のアプリケーションを再利用できます。

このチュートリアルでは、Ubuntu 18.04でDjangoアプリケーションをセットアップします。このアプリケーションは、ユーザーがアプリケーションにアクセスする必要があるアクティビティがあるたびにプッシュ通知を送信します。 これらの通知を作成するには、 Django-Webpush パッケージを使用し、サービスワーカーを設定して登録し、クライアントに通知を表示します。 通知付きの動作中のアプリケーションは次のようになります。

前提条件

このガイドを開始する前に、次のものが必要です。

  • 非rootユーザーとアクティブなファイアウォールを備えた1つのUbuntu18.04サーバー。 Ubuntu 18.04サーバーの作成方法の詳細については、この初期サーバーセットアップガイドのガイドラインに従うことができます。
  • pipvenv これらのガイドラインに従ってインストールされます。
  • と呼ばれるDjangoプロジェクト djangopush ホームディレクトリに作成し、 Ubuntu18.04でサンプルDjangoプロジェクトを作成する際のガイドラインに従って設定します。 必ずサーバーのIPアドレスをALLOWED_HOSTSディレクティブに追加してください。 settings.py ファイル。

ステップ1—Django-WebpushのインストールとVapidキーの取得

Django-Webpushは、開発者がWebプッシュ通知をDjangoアプリケーションに統合して送信できるようにするパッケージです。 このパッケージを使用して、アプリケーションからプッシュ通知をトリガーして送信します。 このステップでは、Django-Webpushをインストールし、サーバーを識別して各リクエストの一意性を確保するために必要な任意のアプリケーションサーバー識別(VAPID)キーを取得します。

あなたがにいることを確認してください ~/djangopush 前提条件で作成したプロジェクトディレクトリ:

  1. cd ~/djangopush

仮想環境をアクティブ化します。

  1. source my_env/bin/activate

のバージョンをアップグレードする pip 最新であることを確認するには:

  1. pip install --upgrade pip

Django-Webpushをインストールします。

  1. pip install django-webpush

パッケージをインストールした後、それをアプリケーションのリストに追加します settings.py ファイル。 最初に開く settings.py:

  1. nano ~/djangopush/djangopush/settings.py

追加 webpush のリストに INSTALLED_APPS:

〜/ djangopush / djangopush / settings.py
...

INSTALLED_APPS = [
    ...,
    'webpush',
]
...

ファイルを保存して、エディターを終了します。

アプリケーションでmigrationsを実行して、データベーススキーマに加えた変更を適用します。

  1. python manage.py migrate

出力は次のようになり、移行が成功したことを示します。

Output
Operations 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情報について。 まず、ファイルを開きます。

  1. 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 viewHttpResponse応答オブジェクトのホームページ、および send_push 見る。 ビューは、Webリクエストから応答オブジェクトを返す関数です。 The send_push ビューはDjango-Webpushライブラリを使用して、ユーザーがホームページに入力したデータを含むプッシュ通知を送信します。

に移動します ~/djangopush/djangopush フォルダ:

  1. cd ~/djangopush/djangopush

ランニング ls フォルダ内には、プロジェクトのメインファイルが表示されます。

Output
/__init__.py /settings.py /urls.py /wsgi.py

このフォルダ内のファイルは、によって自動生成されます django-admin 前提条件でプロジェクトを作成するために使用したユーティリティ。 The settings.py ファイルには、インストールされているアプリケーションや静的ルートフォルダーなどのプロジェクト全体の構成が含まれています。 The urls.py ファイルには、プロジェクトのURL構成が含まれています。 ここで、作成したビューに一致するルートを設定します。

内に新しいファイルを作成します ~/djangopush/djangopush と呼ばれるディレクトリ views.py、プロジェクトのビューが含まれます。

  1. 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>')

The home ビューはによって装飾されています require_GET デコレータ。ビューをGETリクエストのみに制限します。 ビューは通常、ビューに対して行われたすべての要求に対する応答を返します。 このビューは、応答として単純なHTMLタグを返します。

次に作成するビューは send_push、を使用して送信されたプッシュ通知を処理します django-webpush パッケージ。 POSTリクエストのみに制限され、クロスサイトリクエストフォージェリ(CSRF)保護が免除されます。 これを行うと、Postmanまたはその他のRESTfulサービスを使用してビューをテストできます。 ただし、本番環境では、ビューがCSRFに対して脆弱なままにならないように、このデコレータを削除する必要があります。

を作成するには send_push ビューで、最初に次のインポートを追加してJSON応答を有効にし、 send_user_notification の機能 webpush 図書館:

〜/ 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"})

に2つのデコレータを使用しています send_push ビュー: require_POST ビューをPOSTリクエストのみに制限するデコレータ、および csrf_exempt CSRF保護からビューを免除するデコレータ。

このビューはPOSTデータを想定しており、次のことを行います。 body リクエストの処理を行い、 json パッケージを使用して、json.loadsを使用してJSONドキュメントをPythonオブジェクトに逆シリアル化します。 json.loads 構造化されたJSONドキュメントを取得し、Pythonオブジェクトに変換します。

ビューは、リクエスト本文オブジェクトに次の3つのプロパティがあることを想定しています。

  • head:プッシュ通知のタイトル。
  • body:通知の本文。
  • idid リクエストユーザーの。

必要なプロパティのいずれかが欠落している場合、ビューは JSONResponse 404「見つかりません」ステータス。 指定された主キーを持つユーザーが存在する場合、ビューは user get_object_or_404関数を使用して一致する主キーを使用します。 django.shortcuts 図書館。 ユーザーが存在しない場合、関数は404エラーを返します。

ビューも利用します send_user_notification からの機能 webpush 図書館。 この関数は3つのパラメーターを取ります。

  • User:プッシュ通知の受信者。
  • payload:通知を含む通知情報 headbody.
  • ttl:ユーザーがオフラインの場合に通知を保存する最大時間(秒単位)。

エラーが発生しない場合、ビューは JSONResponse 200の「成功」ステータスとデータオブジェクトがあります。 もし KeyError 発生すると、ビューは500の「内部サーバーエラー」ステータスを返します。 A KeyError オブジェクトの要求されたキーが存在しない場合に発生します。

次のステップでは、作成したビューに一致する対応するURLルートを作成します。

ステップ3—URLをビューにマッピングする

Djangoを使用すると、URLを作成してビューに接続することができます。 URLconf. このモジュールは、URLパス式をPython関数(ビュー)にマップします。 通常、URL構成ファイルは、プロジェクトの作成時に自動生成されます。 このステップでは、このファイルを更新して、前のステップで作成したビューの新しいルートと、 django-webpush アプリ。プッシュ通知をユーザーにサブスクライブするためのエンドポイントを提供します。

ビューの詳細については、Djangoビューの作成方法を参照してください。

開ける urls.py:

  1. 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 リストはのURLを登録します django-webpush ビューをパッケージ化してURLにマッピングします /send_push/home.

テストしてみましょう /home 意図したとおりに機能していることを確認してください。 プロジェクトのルートディレクトリにいることを確認してください。

  1. cd ~/djangopush

次のコマンドを実行してサーバーを起動します。

  1. python manage.py runserver your_server_ip:8000

案内する http://your_server_ip:8000. 次のホームページが表示されます。

この時点で、サーバーを強制終了できます CTRL+C、そしてテンプレートの作成に移り、を使用してビューにレンダリングします。 render 関数。

ステップ4—テンプレートの作成

Djangoのテンプレートエンジンを使用すると、HTMLファイルに似たテンプレートを使用してアプリケーションのユーザー向けレイヤーを定義できます。 このステップでは、テンプレートを作成してレンダリングします。 home 見る。

というフォルダを作成します templates プロジェクトのルートディレクトリ:

  1. mkdir ~/djangopush/templates

実行した場合 ls この時点でのプロジェクトのルートフォルダーでは、出力は次のようになります。

Output
/djangopush /templates db.sqlite3 manage.py /my_env

というファイルを作成します home.html の中に templates フォルダ:

  1. 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>

The body ファイルのには、次の2つのフィールドを持つフォームが含まれています。 input 要素は通知の見出し/タイトルと textarea 要素は通知本文を保持します。

の中に head ファイルのセクションには、2つあります meta VAPID公開鍵とユーザーのIDを保持するタグ。 これらの2つの変数は、ユーザーを登録してプッシュ通知を送信するために必要です。 サーバーにAJAXリクエストを送信するため、ここではユーザーのIDが必要です。 id ユーザーを識別するために使用されます。 現在のユーザーが登録ユーザーの場合、テンプレートは meta 彼らのタグ id コンテンツとして。

次のステップは、テンプレートの場所をDjangoに指示することです。 これを行うには、編集します settings.py を更新します TEMPLATES リスト。

を開きます settings.py ファイル:

  1. 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 テンプレート。 ファイルを開きます。

  1. nano ~/djangpush/djangopush/views.py

まず、以下を含むいくつかの追加のインポートを追加します settings 構成。これには、プロジェクトのすべての設定が含まれます。 settings.py ファイル、および render からの機能 django.shortcuts:

〜/ 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:これには、の値が割り当てられます WEBPUSH_SETTINGS からの属性 settings 構成。
  • vapid_key:これは VAPID_PUBLIC_KEY からの値 webpush_settings クライアントに送信するオブジェクト。 この公開鍵は秘密鍵と照合され、公開鍵を持つクライアントがサーバーからのプッシュメッセージの受信を許可されていることを確認します。
  • user:この変数は、着信要求から取得されます。 ユーザーがサーバーにリクエストを送信するたびに、そのユーザーの詳細が user 分野。

レンダリング関数は、HTMLファイルと、現在のユーザーとサーバーのvapid公開鍵を含むコンテキストオブジェクトを返します。 ここでは3つのパラメータが必要です。 requesttemplate レンダリングされるオブジェクト、およびテンプレートで使用される変数を含むオブジェクト。

テンプレートを作成し、 home ビューが更新されたら、静的ファイルを提供するようにDjangoを構成することに進むことができます。

ステップ5—静的ファイルの提供

Webアプリケーションには、CSS、JavaScript、およびDjangoが「静的ファイル」と呼ぶその他の画像ファイルが含まれます。 Djangoを使用すると、プロジェクト内の各アプリケーションからすべての静的ファイルを、それらが提供される単一の場所に収集できます。 このソリューションはと呼ばれます django.contrib.staticfiles. このステップでは、設定を更新して、静的ファイルが保存される場所をDjangoに通知します。

開ける settings.py:

  1. 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/'
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, "static"),
]

これで、 STATIC_URL で定義されたパスのリストに urls.py ファイル。

ファイルを開きます。

  1. nano ~/djangopush/djangopush/urls.py

次のコードを追加します。これにより、 static URL構成を更新し、 urlpatterns リスト。 ここでのヘルパー関数は、 STATIC_URLSTATIC_ROOT で提供したプロパティ settings.py プロジェクトの静的ファイルを提供するファイル:

〜/ 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 フォルダと css 内のフォルダ static フォルダ:

  1. mkdir -p ~/djangopush/static/css

と呼ばれるcssファイルを開きます styles.csscss フォルダ:

  1. 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 ファイル:

  1. 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>

メインプロジェクトディレクトリにいることを確認し、サーバーを再起動して作業を検査します。

  1. cd ~/djangopush
  2. 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:

  1. mkdir ~/djangopush/static/js

というファイルを作成します registerSw.js:

  1. 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');
};

The initializeState 関数は以下をチェックします:

  • ユーザーが通知を有効にしているかどうか、の値を使用して reg.showNotification.
  • ユーザーが通知を表示するためのアプリケーション権限を付与したかどうか。
  • ブラウザがサポートしているかどうか PushManager API。 これらのチェックのいずれかが失敗した場合、 showNotAllowed 関数が呼び出され、サブスクリプションが中止されます。

The 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 関数。 pushManager.subscribe 次に、VAPID公開鍵と userVisible オプションとしての値。 利用可能なオプションの詳細については、こちらをご覧ください。

ユーザーを正常にサブスクライブした後、次のステップはサブスクリプションデータをサーバーに送信することです。 データはに送信されます webpush/save_information によって提供されるエンドポイント django-webpush パッケージ。 以下のコードを追加します 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();

The save_information エンドポイントには、サブスクリプションのステータスに関する情報が必要です(subscribeunsubscribe)、サブスクリプションデータ、およびブラウザ。 最後に、 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();

次に、 script のタグ registerSw.js のファイル home.html. ファイルを開きます。

  1. nano ~/djangopush/templates/home.html

追加します script の終了タグの前のタグ body エレメント:

〜/ 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 イベントを実行し、準備ができたらメッセージを表示します。

Service Workerのスコープをドメイン全体にする必要があるため、アプリケーションのルートにインストールする必要があります。 このプロセスの詳細については、サービスワーカーの登録方法の概要を説明しています。 私たちのアプローチは、 sw.js のファイル templates フォルダ。ビューとして登録します。

ファイルを作成します。

  1. 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 データはテキストに変換されます。 デフォルトを使用します titlebody イベントデータに文字列がない場合は文字列。 The showNotification 関数は、通知のタイトル、表示される通知のヘッダー、およびoptionsオブジェクトをパラメーターとして受け取ります。 optionsオブジェクトには、通知の視覚的オプションを構成するためのいくつかのプロパティが含まれています。

Service Workerがドメイン全体で機能するには、アプリケーションのルートにサービスワーカーをインストールする必要があります。 TemplateView を使用して、ServiceWorkerがドメイン全体にアクセスできるようにします。

を開きます urls.py ファイル:

  1. 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 メソッドは、最近作成されたService Workerをテンプレートとして渡すことにより、ServiceWorkerのパスを作成します。 application/x-javascript として content_type テンプレートの。

これで、サービスワーカーが作成され、ルートとして登録されました。 次に、プッシュ通知を送信するようにホームページにフォームを設定します。

ステップ9—プッシュ通知の送信

ホームページのフォームを使用して、ユーザーはサーバーの実行中にプッシュ通知を送信できる必要があります。 PostmanなどのRESTfulサービスを使用してプッシュ通知を送信することもできます。 ユーザーがホームページのフォームからプッシュ通知を送信すると、データには headbody、および id 受信ユーザーの。 データは次のように構成する必要があります。

{
    head: "Title of the notification",
    body: "Notification body",
    id: "User's id"
}

聞くために submit フォームのイベントとユーザーが入力したデータをサーバーに送信し、というファイルを作成します site.js の中に ~/djangopush/static/js ディレクトリ。

ファイルを開きます。

  1. nano ~/djangopush/static/js/site.js

まず、を追加します submit フォーム入力の値とに保存されているユーザーIDを取得できるようにするフォームのイベントリスナー meta テンプレートのタグ:

〜/ 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
});

The pushForm 関数は input, textarea、 と button フォーム内。 また、から情報を取得します meta name属性を含むタグ user_id およびに保存されているユーザーのID content タグの属性。 この情報を使用して、POSTリクエストをに送信できます。 /send_push サーバー上のエンドポイント。

サーバーにリクエストを送信するには、ネイティブの 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:

  1. 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>

この時点で、アプリケーションを実行したままにするか、アプリケーションを再起動しようとすると、エラーが表示されます。これは、ServiceWorkerが安全なドメインまたはでのみ機能できるためです。 localhost. 次のステップでは、 ngrok を使用して、Webサーバーへの安全なトンネルを作成します。

ステップ10—アプリケーションをテストするための安全なトンネルを作成する

サービスワーカーは、を除くすべてのサイトで機能するために安全な接続を必要とします localhost これは、接続が乗っ取られ、応答がフィルタリングおよび作成される可能性があるためです。 このため、ngrokを使用してサーバー用の安全なトンネルを作成します。

2番目のターミナルウィンドウを開き、ホームディレクトリにいることを確認します。

  1. cd ~

前提条件でクリーンな18.04サーバーを使用して開始した場合は、インストールする必要があります unzip:

  1. sudo apt update && sudo apt install unzip

ngrokをダウンロード:

  1. wget https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip
  2. unzip ngrok-stable-linux-amd64.zip

動く ngrok/usr/local/bin、にアクセスできるように ngrok ターミナルからのコマンド:

  1. sudo mv ngrok /usr/local/bin

最初のターミナルウィンドウで、プロジェクトディレクトリにいることを確認し、サーバーを起動します。

  1. cd ~/djangopush
  2. python manage.py runserver your_server_ip:8000

アプリケーションの安全なトンネルを作成する前に、これを行う必要があります。

2番目のターミナルウィンドウで、プロジェクトフォルダーに移動し、仮想環境をアクティブ化します。

  1. cd ~/djangopush
  2. source my_env/bin/activate

アプリケーションへの安全なトンネルを作成します。

  1. ngrok http your_server_ip:8000

次の出力が表示されます。これには、安全なngrokURLに関する情報が含まれています。

Output
ngrok 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 コンソール出力から。 あなたはそれをのリストに追加する必要があります ALLOWED_HOSTS あなたの中で settings.py ファイル。

別のターミナルウィンドウを開き、プロジェクトフォルダーに移動して、仮想環境をアクティブ化します。

  1. cd ~/djangopush
  2. source my_env/bin/activate

を開きます settings.py ファイル:

  1. 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を使用して、ユーザーをサブスクライブしてプッシュ通知をサブスクライブし、サービスワーカーをインストールし、プッシュ通知を表示する方法を学習しました。

クリックしたときにアプリケーションの特定の領域を開くように通知を構成することで、さらに先に進むことができます。 このチュートリアルのソースコードはここにあります。