OneSignal を
- WebPush 通知 Icon クリックで、
購読の 状態を 切り替える。 - Segment
All
で、APサーバーから 通知する。
実装時に
[TOC]
前提
バックエンド、
バックエンド
Django 、
- Django (1.11.11)
- wagtail (1.13.1)
- puput (0.9.2.1)
フロントエンド
react と、
- react@16.2.0
- rmwc@1.1.2
WebPush 通知 Icon クリックで、 購読の 状態を 切り替える。
OneSignal の
OneSingnal の 初期化時に、 ベルを 非表示に する
notifyButton: { enable: false }
を
特に
Custom Code Examples
- main.js
<script async src="https://cdn.onesignal.com/sdks/OneSignalSDK.js" /> <script type="text/javascript">{` var OneSignal = window.OneSignal || []; OneSignal.push(["init", { appId: "${config.oneSignalAppId}", autoRegister: false, allowLocalhostAsSecureOrigin: true, notifyButton: { enable: false /* Set to false to hide */ } }]); `}</script>
画面右上に、 WebPush 通知 Icon を 設置する
以下、
プログラムの
Navbar.js
import React, {Component} from "react"; import {Toolbar, ToolbarRow, ToolbarSection, ToolbarMenuIcon, ToolbarTitle, ToolbarIcon} from "rmwc"; import config from "~/data/siteConfig"; const getSubscriptionState = function() { var OneSignal = window.OneSignal || []; return Promise.all([ OneSignal.isPushNotificationsEnabled(), OneSignal.isOptedOut(), OneSignal.getNotificationPermission() ]).then(function(result) { var isPushEnabled = result[0]; var isOptedOut = result[1]; var notificationPermission = result[2]; return { isPushEnabled: isPushEnabled, isOptedOut: isOptedOut, notificationPermission: notificationPermission }; }); }; export default class Navbar extends Component { constructor(props) { super(props); var self = this; self.state = {}; self.getNotificationsIcon = self.getNotificationsIcon.bind(self); self.setCurrentNotificationState = self.setCurrentNotificationState.bind(self); } setCurrentNotificationState() { var self = this; getSubscriptionState().then(function(state) { self.setState({isPushEnabled: state.isPushEnabled}); self.setState({isOptedOut: state.isOptedOut}); self.setState({notificationPermission: state.notificationPermission}); }); } componentDidMount() { var self = this; window.addEventListener("load", function() { self.setCurrentNotificationState(); var OneSignal = window.OneSignal || []; /* This example assumes you've already initialized OneSignal */ OneSignal.push(function() { // If we're on an unsupported browser, do nothing if (!OneSignal.isPushNotificationsSupported()) { return; } OneSignal.on("subscriptionChange", function() { self.setCurrentNotificationState(); }); OneSignal.on("notificationPermissionChange", function(permissionChange) { var currentPermission = permissionChange.to; if(currentPermission === "granted") { // Subscription false > true の場合は、subscriptionChange が呼び出される // self.setCurrentNotificationState(); は呼び出さなくてOK OneSignal.setSubscription(true); } else { self.setCurrentNotificationState(); } }); }); }); } onNotificationButtonClicked(e) { var self = this; getSubscriptionState().then(function(state) { var OneSignal = window.OneSignal || []; if (state.isPushEnabled) { /* Subscribed, opt them out */ OneSignal.setSubscription(false); } else { if (state.isOptedOut) { /* Opted out, opt them back in */ OneSignal.setSubscription(true); } else { /* Unsubscribed, subscribe them */ OneSignal.registerForPushNotifications(); } } }).then(function(){ // true > false への変化の場合、subscriptionChange が呼び出されないので、ここで、 // self.setCurrentNotificationState(); を呼び出しておく。 self.setCurrentNotificationState(); }); e.preventDefault(); } getNotificationsIcon() { if(this.state.notificationPermission === "denied") { return "notifications_off"; } if(this.state.isPushEnabled) { return "notifications_active"; } else { if(this.state.isOptedOut) { return "notifications_paused"; } } return "notifications_none"; } render() { return ( <Toolbar> <ToolbarRow> <ToolbarSection alignStart> <ToolbarMenuIcon use="menu" onClick={this.props.toggle}/> <ToolbarTitle>{config.siteTitle}</ToolbarTitle> </ToolbarSection> <ToolbarSection alignEnd> <ToolbarIcon tag="a" href={config.siteUrl + "/feed"} strategy="ligature" use="rss_feed"/> <ToolbarIcon tag="a" onClick={e => this.onNotificationButtonClicked(e)} strategy="ligature" use={this.getNotificationsIcon()} /> </ToolbarSection> </ToolbarRow> </Toolbar> ); } }
説明
WebPush ToolbarIcon に
ついて が<ToolbarIcon tag="a" onClick={e => this.onNotificationButtonClicked(e)} strategy="ligature" use={this.getNotificationsIcon()} />
WebPush Iconに なります。 ToolbarIcon は rmwc/toolbar.md at master · jamesmfriedman/rmwc に 説明が 記載されています。
use
属性で、getNotificationsIcon
ファンクションを指定して、 通知の 状態で、 Icon を 切り替えています。
onClick
属性で、onNotificationButtonClicked
ファンクションを指定して、 通知の 状態を 切り替えています。 componentDidMount に
ついて
コンポーネントのMount 完了時に、 EventListener を 追加しています。 タイミングが、 load
なのは、ComtentDomLoaded
のタイミングでは OneSignal の Javascript が 読み込まれておらず、 未定義エラーに なった ためです。
EventListener では、OneSignal の subscriptionChange
と、notificationPermissionChange
を追加しています。
subscriptionChange
は、通知購読の 状態が 変更された 場合に 呼び出される 処理で、 notificationPermissionChange
は通知の パーミッションの 変更時に 呼び出される 処理に なります。 subscriptionChange
はfalse > true
のケースのみ 呼び出される 処理のようで、 true > false
のケースでも 行いたい 処理が ある 場合は、 明示的に 呼び出す 必要が あります。
notificationPermissionChange
では、パーミッションが granted
の場合、 OneSignal.setSubscription(true);
を呼び出していますが、 これに よって、 false > true
となり、subscriptionChange
が起動し、 Icon の 表示が 切り 替わります。 getSubscriptionState に
ついて
Custom Code Examples に記載の ある プログラムを 参考に しつつ、 OneSignal.getNotificationPermission()
を追加して、 現状の 通知パーミッションの 状態を 取得するようにしました。
これは、通知 Icon の 切り替えで、 通知の 状態も 加味したかった ため 取得しています。 通知 Icon に
ついて
Icon は以下、 4つの 状態が あります。 notifications_off 通知は
拒否された 場合に、 この Icon 表示に なります。 notifications_active
通知は許可されていて、 購読も 許可されている 場合、 この Icon 表示に なります。 notifications_paused
通知は許可されていて、 購読は 不許可(停止した)に 設定されている 場合、 この Icon 表示に なります。 notifications_none
通知に対する アクションを ユーザ が 何も とっていないか、 画面上の オペレーション以外の 方法で WebPush 通知の 状態を 編集した 場合に この Icon 表示に なります。
Segment All
で、 APサーバーから 通知する。
記事のAll
に
実装には、
設定方法
インストール
python3 -m pip install onesignal-python
onesignalclient
をsettings.py に 追加する INSTALLED_APPS = [ .... 'onesignalclient' ]
settings.py に、
ONESIGNAL_REST_API_KEY と、 ONESIGNAL_APP_ID を 追加する
OneSignal のアプリケーションを 作成すると、 ONESIGNAL APP ID と、 REST API KEY が 払い 出されます。
払い出された、 ID と KEY を settings.py に 追加します。 これは、ONESIGNAL_APP_ID = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ONESIGNAL_REST_API_KEY = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
後述する Django コマンドで 使用しています。
Django コマンド
新着投稿を
send_notigications.py
# -*- coding: utf-8 -*- from __future__ import print_function from django.core.management.base import BaseCommand from requests.exceptions import HTTPError from onesignalclient.app_client import OneSignalAppClient from onesignalclient.notification import Notification from puput.models import EntryPage from django.conf import settings import datetime class Command(BaseCommand): def handle(self, **options): now = datetime.datetime.now() entries = EntryPage.objects.filter(date__gte=now - datetime.timedelta(hours=4)) if entries: entry = entries.first() content = entry.title # Init the client client = OneSignalClient(app_id=settings.ONESIGNAL_APP_ID, app_api_key=settings.ONESIGNAL_REST_API_KEY) # Creates a new notification notification = Notification(settings.ONESIGNAL_APP_ID, Notification.SEGMENTS_MODE) notification.contents = {"en": content} notification.headings = {"en": "New article"} option = {} option.update({"included_segments" : ["All"]}); try: # Sends it! result = client.create_notification_with_option(notification, option) except HTTPError as e: result = e.response.json() class OneSignalClient(OneSignalAppClient): def create_notification_with_option(self, notification, option): """ Creates a new notification. :param notification: onesignalclient.notification.Notification object """ payload = notification.get_payload_for_request() payload.update(option) print(payload) return self.post(self._url(self.ENDPOINTS['notifications']), payload=payload)
説明
Bodyに
設定している 文字列に ついて WebPush は スペースの 都合なのか あまり 長い 文字列を body 部に 設定できず、 ブラウザに より 設定できる 文字数も 異なるようです。
このため、 1つの 記事の タイトルを 取り出して、 body に 設定するようにしました。 OneSignalClientに
ついて
nesignal-python の、OneSignalAppClient は、 セグメントに 対する 送信が できないため、 OneSignalAppClient を 拡張して、 Segment に 対して、 送信できるようにしました。 コマンドの
スケジューリングに ついて
実行タイミングの4時間前までに 作成された 記事を 取得して、 WebPush 通知を 行うようにしたため、 crontab での スケジュール実行も 4時間 ごとに 実装するようにしました。
ライブラリを
使わないで 実装する Create notification に
Python で 実装された クライアントの サンプルが あります。 ライブラリを 使わずに、 実装しても、 それほど 複雑には ならなそうだったので、 最終的に ライブラリの 使用しない 実装に 書き換えました。 send_notigications.py
余計な継承クラスが ない分、 ライブラリを 使用しない ほうが、 シンプルですね。 # -*- coding: utf-8 -*- from __future__ import print_function from django.core.management.base import BaseCommand from requests.exceptions import HTTPError from puput.models import EntryPage from django.conf import settings import datetime import requests import json class Command(BaseCommand): def handle(self, **options): now = datetime.datetime.now() entries = EntryPage.objects.filter(date__gte=now - datetime.timedelta(hours=4)) if entries: entry = entries.first() content = entry.title header = {"Content-Type": "application/json; charset=utf-8", "Authorization": "Basic " + settings.ONESIGNAL_REST_API_KEY} payload = {"app_id": settings.ONESIGNAL_APP_ID, "included_segments": ["All"], "contents": {"en": content}, "headings": {"en": "New article"} } req = requests.post("https://onesignal.com/api/v1/notifications", headers=header, data=json.dumps(payload))
コメント