WebPush の
確かに
実施した
WebPush 許可の タイミングに ついて
Webサイトと
カテゴリ
- 無料サイト
Blog 等、コンテンツを 誰でもみることができる サイト - 会員サイト
無料会員制のサイト (登録しなくても 見れる コンテンツが あるが、 登録しないと 見れない コンテンツが ある ) - 有料会員サイト
無料/有料の概念の ある サイト (登録しなくても 見れる コンテンツが ある。 会員情報を 登録するとより 多くの 情報が 見れるが、 お金を 払うともっと 見れる )
通知許可の タイミング
許可を
- コンテンツの
読了。 - 会員登録直後とか
何か 自発的な 作業完了。 - サイトの
訪問回数。 - 他ユーザーからの
アクションの 受信のような 受動的な 作業完了。
個人的に 感じる WebPush の 活用法
ユーザーが
OK か NG かは
ですので、
だた、
カテゴリと、 通知許可の タイミングの 個人ベースの 定性的な マトリクス
5段階評価です。1 > ない
、3 > 普通
、5 > ある
と
カテゴリ | コンテンツの読了 | 自発的な作業完了 | サイトの訪問回数 | 受動的な作業完了 |
---|---|---|---|---|
無料サイト | 3 | 2 | 3 | 2 |
会員サイト | 3 | 4 | 3 | 4 |
有料会員サイト | 3 | 5 | 3 | 5 |
コンテンツの
自発的な
機能が
作成した 実装の 技術要素の 説明
作成した
ServiceWorker と、 Browser での メッセージ通信
window.postMessage - Web API インターフェイス | MDN で
ServiceWorker, MessageChannel, & postMessage に、MessageChannel
をMessageChannel
を
IndexDB で 訪問回数を 記録する
ServiceWorker で、
localForage/localForage: 💾 Offline storage, improved. Wraps IndexedDB, WebSQL, or localStorage using a simple but powerful API. とimportScripts('static/js/localforage.min.js');
して
WebPush 許可ダイアログの 表示条件
以下の
GTMで、
スクロールトリガーを 仕込む。 スクロール距離が 90%の 場合、 イベント発火。 イベント発火後、
3日間、 日付を またいだ アクセスが あれば、 WebPush ダイアログ表示。 2
実装の 説明
前提
ServiceWorker の
PageLoad 時に 実行する 処理
PageLoad 時には
ブラウザ側の
- PageConfigure.js
// メッセージ送信用 function sendMessage2ServiceWorker(message) { return new Promise((resolve, reject) => { const channel = new MessageChannel(); channel.port1.onmessage = (e) => { if (e.data.error) { reject(e.data.error); } else { resolve(e.data); } }; // 登録時は、activateしないため、controller は nullになる if (navigator.serviceWorker.controller) { navigator.serviceWorker.controller.postMessage(message, [channel.port2]); } }); } function dispatchEvent(name) { var event; try { event = new CustomEvent(name); } catch (e) { event = document.createEvent('CustomEvent'); event.initCustomEvent(name, false, false); } window.dispatchEvent(event); } export default function configure() { if ('serviceWorker' in navigator) { /* eslint-disable no-unused-vars */ /* 通知をユーザーに求める、eventリスナーでcustomevent登録 */ window.addEventListener('_sendRequestNotification', () => { // dataLayer変数が設定されていない場合、処理を中断する if ( typeof window.blogPostInfo === 'undefined') { return; } if ('Notification' in window) { // 許可を求める Notification.requestPermission().then((permission) => { if (permission === 'denied' || permission === 'default') { // 拒否 // 無視 return; } else if (permission === 'granted') { let args = { 'userAgent': window.navigator.userAgent, 'blogPostId': window.blogPostInfo.blogPostId, 'gaId': window.blogPostInfo.gaId}; sendMessage2ServiceWorker({'command': 'requestNotification', 'args': args}); } else { /* eslint-disable no-console */ console.log('permission is illegal : %s', permission); } }); } }); // GTMからキックする処理 window.addEventListener('_isRepeater', () => { console.log('_isRepeater fired..'); // dataLayer変数が設定されていない場合、処理を中断する if ( typeof window.blogPostInfo === 'undefined') { return; } if ('Notification' in window) { // ServiceWorkerに再訪ユーザーか問い合わせ sendMessage2ServiceWorker({'command': 'isRepeater', 'args': null}).then((data) => { if (data.result) { // 再訪ユーザであれば、eventDispatchする dispatchEvent('_sendRequestNotification'); } else { console.log(' _sendRequestNotification event not fired..'); } }); } }); // 登録時は、activateしないため、controller は nullになる if (navigator.serviceWorker.controller) { // ページロード時に、アクセス日付をIndexDBに記録する // serviceworkerにpostmessage window.addEventListener('load', () => { // アクセス日付を記録 sendMessage2ServiceWorker({'command': 'storeAccessDate', 'args': null}); }); } }
functionの 説明
以下、
_sendRequestNotification
イベント
実際にユーザーに、 通知パーミッションを 求める 処理です。
ユーザーが許可した 場合は、 ID の 払い出しを 行います。 _isRepeater イベント
GTMから発火させる イベントです。
Notification API が使える ブラウザで 且つ、 再訪ユーザーであれば、 _sendRequestNotification
イベントを実行します。 load イベント
ServiceWorker にメッセージを 送信して、 アクセス日付を 記録します。 sendMessage2ServiceWorker
ServiceWorker にメッセージを 送信する function です。 PostMessage を 使って ServiceWorkerに メッセージを 送信します。
ServiceWorker 側の 処理
以下は、
- serviceWorker.js
importScripts('static/js/localforage.min.js'); // 中略........ // Messaging.. Browser側からServiceWorkerへメッセージを送信する self.addEventListener('message', (e) => { let command = e.data.command; let args = e.data.args; switch (command) { case 'requestNotification': // 通知承認要求 requestNotification(args.userAgent, args.blogPostId, args.gaId); break; case 'storeAccessDate': storeAccessDate(); break; case 'isRepeater': isRepeater().then((result) => { e.ports[0].postMessage({'result' : result }); }); break; default: return Promise.resolve(); } }); // 中略........ // ------------------------------------------------------------------- // 日付文字列を作成するユーティリティ // --------------------------------------------------------- const dateFormat = { fmt: { hh: function(date) { return ('0' + date.getHours()).slice(-2); }, h: function(date) { return date.getHours(); }, mm: function(date) { return ('0' + date.getMinutes()).slice(-2); }, m: function(date) { return date.getMinutes(); }, ss: function(date) { return ('0' + date.getSeconds()).slice(-2); }, dd: function(date) { return ('0' + date.getDate()).slice(-2); }, d: function(date) { return date.getDate(); }, s: function(date) { return date.getSeconds(); }, yyyy: function(date) { return date.getFullYear() + ''; }, yy: function(date) { return date.getYear() + ''; }, t: function(date) { return date.getDate()<=3 ? ['st', 'nd', 'rd'][date.getDate()-1]: 'th'; }, w: function(date) { return ['Sun', '$on', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][date.getDay()]; }, MMMM: function(date) { return ['January', 'February', '$arch', 'April', '$ay', 'June', 'July', 'August', 'September', 'October', 'November', 'December'][date.getMonth()]; }, MMM: function(date) { return ['Jan', 'Feb', '$ar', 'Apr', '@ay', 'Jun', 'Jly', 'Aug', 'Spt', 'Oct', 'Nov', 'Dec'][date.getMonth()]; }, MM: function(date) { return ('0' + (date.getMonth() + 1)).slice(-2); }, M: function(date) { return date.getMonth() + 1; }, $: function(date) { return 'M'; }, }, format: function dateFormat(date, format) { let result = format; for (let key in this.fmt) { if (this.fmt.hasOwnProperty(key)) { result = result.replace(key, this.fmt[key](date)); } } return result; }, }; // ----------------------------------------------- // アクセス日付を記録するIndexedDBの定義 // ---------------------- const accessDate = localforage.createInstance({ driver: localforage.INDEXEDDB, // Force WebSQL; same as using setDriver() name: 'swDB', version: 1.0, size: 4980736, // Size of database, in bytes. WebSQL-only for now. storeName: 'accessDate', // Should be alphanumeric, with underscores. description: 'some description', }); // ----------------------------------------------- // アクセスした日付を記録する // ---------------------- const storeAccessDate = function() { let date = dateFormat.format(new Date(), 'yyyyMMdd'); accessDate.getItem(date).then((value) => { let count; if (typeof value === 'undefined' || value === NaN) { count = 1; } else { count = 1 + value; } return accessDate.setItem(date, count).then(() => { return accessDate.length().then((length) => { if (length > 5) { accessDate.key(0).then((key) => { console.log(key); accessDate.delete(key); }).catch((value) => { console.log('Raise error.'); }); } }); }); }); }; // ------------------------------------------------- // Repeaterユーザーか判定して返す。 // -------------------------------------------- const isRepeater = function() { return accessDate.keys().then((keys) => { // 日をまたいで3回以上のアクセスがあるか判断する if (keys.length >= 3) { return true; } return false; }); };
function の 説明
self.addEventListener('message'
ブラウザ側のスクリプトの Postmessage を 受け 取る 処理です。 command 名称を 受け取り、 それで 処理を 分岐させています。 e.ports[0].postMessage({'result' : result });
でブラウザ側の 呼び出し元に、 戻り値を postMessage で 送信しています。
MessageChannel でprivate な postMesage を 送信する 場合は、 port 指定を して メッセージを 返信できます。 storeAccessDate
日付ごとのアクセス回数を 記録する function です。 5日分を 記録し、 5日以上に なったら、 過去の ものを 削除するようにしました。
消えることが 試せてないのでもしかしたら、 期待通りに 動かないかもしれません。 isRepeater
再訪ユーザーかを判定する 処理です。 3日以上の アクセスの ある ユーザを 再訪ユーザーと して true
を返します。
GTM に 設定する カスタムHTMLタグ
HTMLタグ
GTMには、以下の カスタムスクリプトを 設定しました。 _isRepeater カスタムイベントを 発火させています。
try-catch
の記述は、 IE向けの 記述です。 <script> var event; try { event = new CustomEvent('_isRepeater'); } catch (e) { event = document.createEvent('CustomEvent'); event.initCustomEvent('_isRepeater', false, false); } window.dispatchEvent(event); </script>
タグ呼び出しオプション
1 ページに<wbr>つき <wbr>1度
を設定しました。
GTM の トリガー指定
トリガーの
種類
スクロール距離縦方向スクロール距離
割合 90% を指定 この
トリガーの 発生場所
一部のページ
blog 記事ページでのみ発動するように URL 指定しました。
実装した 感想
ブラウザ - ServiceWorker の
メッセージ通信を したくて 実装したが、 もっと シンプルな 作りでよい
PostMessage でのメッセージの 連鎖を 実装したくて このような 作りになりましたが、 実際に 使う 場合は もっとやり方を 考えた ほうが いいかと 思います。
メンテナンス時に何しているのかわからなくなりそうです。 作りこむと
大規模化する ServiceWorker
機能てんこ盛りに していくと、 ServiceWorkerが 大規模化していきます。
importScripts
を有効活用して 役割ごとに ファイルを 分割していくべきだと 思いました。 後そもそも、 大きくなると、 初期ロードの 時間は 長くなるので、 本当に 必要な 機能か どうかは 判断が いるかと 思います。 Promise を
覚える
Promise の仕組みに 慣れる 必要が あります。 デバッグで 止まったり、 止まらなかったりします。
参考
PostMessage
Javascriptの
日付 IndexedDB
作成している
以上です。
コメント